'.$self->get('description').'
' + .''.$error.'
'; + } + $output .= $info->print; + return $output; +} + + +#------------------------------------------------------------------- + +=head2 www_addToCart + +Processes form from view() and then adds to cart. + +=cut + +sub www_addToCart { + my ($self) = @_; + return $self->session->privilege->noAccess() unless $self->getParent->canView; + + # gather badge info + my $form = $self->session->form; + my %badgeInfo = (); + foreach my $field (qw(name address1 address2 address3 city state organization)) { + $badgeInfo{$field} = $form->get($field, "text"); + } + $badgeInfo{'phoneNumber'} = $form->get('phoneNumber', 'phone'); + $badgeInfo{'email'} = $form->get('email', 'email'); + $badgeInfo{'country'} = $form->get('country', 'country'); + $badgeInfo{'zipcode'} = $form->get('zipcode', 'zipcode'); + + + # check for required fields + my $error = ""; + my $i18n = WebGUI::International->new($self->session, 'Asset_EventManagementSystem'); + if ($badgeInfo{name} eq "") { + $error = sprintf $i18n->get('is required'), $i18n->get('name','Shop'); + } + + # return them back to the previous screen if they messed up + if ($error) { + $self->{_errorMessage} = $error; + return $self->www_view($error); + } + + # add it to the cart + $self->addToCart(\%badgeInfo); + return $self->getParent->www_buildBadge($self->getOptions->{badgeId}); +} + + +#------------------------------------------------------------------- + +=head2 www_edit () + +Displays the edit form. + +=cut + +sub www_edit { + my ($self) = @_; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + $self->session->style->setRawHeadTags(q| + + |); + my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem"); + return $self->processStyle(''.$self->get('description').'
'; + + # build the add to cart form + if ($form->get('badgeId') ne '') { + my $addToCart = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl); + $addToCart->hidden(name=>"func", value=>"addToCart"); + $addToCart->hidden(name=>"badgeId", value=>$form->get('badgeId')); + $addToCart->submit(value=>$i18n->get('add to cart','Shop'), label=>$self->getPrice); + $output .= $addToCart->print; + } + + return $output; +} + +#------------------------------------------------------------------- + +=head2 www_addToCart + +Takes form variable badgeId and add the ribbon to the cart. + +=cut + +sub www_addToCart { + my ($self) = @_; + return $self->session->privilege->noAccess() unless $self->getParent->canView; + my $badgeId = $self->session->form->get('badgeId'); + $self->addToCart({badgeId=>$badgeId}); + return $self->getParent->www_buildBadge($badgeId); +} + +#------------------------------------------------------------------- + +=head2 www_delete + +Override to return to appropriate page. + +=cut + +sub www_delete { + my ($self) = @_; + $self->SUPER::www_delete; + return $self->getParent->www_buildBadge(undef,'ribbons'); +} + + +#------------------------------------------------------------------- + +=head2 www_edit () + +Displays the edit form. + +=cut + +sub www_edit { + my ($self) = @_; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + $self->session->style->setRawHeadTags(q| + + |); + my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem"); + my $form = $self->getEditForm; + $form->hidden({name=>'proceed', value=>'viewAll'}); + return $self->processStyle(''.$self->get('description').'
' + .''.$self->get('startDate').'
'; + + # build the add to cart form + if ($form->get('badgeId') ne '') { + my $addToCart = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl); + $addToCart->hidden(name=>"func", value=>"addToCart"); + $addToCart->hidden(name=>"badgeId", value=>$form->get('badgeId')); + $addToCart->submit(value=>$i18n->get('add to cart','Shop'), label=>$self->getPrice); + $output .= $addToCart->print; + } + + return $output; +} + +#------------------------------------------------------------------- + +=head2 www_addToCart + +Takes form variable badgeId and add the ticket to the cart. + +=cut + +sub www_addToCart { + my ($self) = @_; + return $self->session->privilege->noAccess() unless $self->getParent->canView; + my $badgeId = $self->session->form->get('badgeId'); + $self->addToCart({badgeId=>$badgeId}); + return $self->getParent->www_buildBadge($badgeId); +} + +#------------------------------------------------------------------- + +=head2 www_delete + +Override to return to appropriate page. + +=cut + +sub www_delete { + my ($self) = @_; + $self->SUPER::www_delete; + return $self->getParent->www_buildBadge(undef,'tickets'); +} + + +#------------------------------------------------------------------- + +=head2 www_edit () + +Displays the edit form. + +=cut + +sub www_edit { + my ($self) = @_; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + $self->session->style->setRawHeadTags(q| + + |); + my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem"); + my $form = $self->getEditForm; + $form->hidden({name=>'proceed', value=>'viewAll'}); + return $self->processStyle(''.$self->get('description').'
'; + + # build the add to cart form + if ($form->get('badgeId') ne '') { + my $addToCart = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl); + $addToCart->hidden(name=>"func", value=>"addToCart"); + $addToCart->hidden(name=>"badgeId", value=>$form->get('badgeId')); + $addToCart->integer(name=>'quantity', value=>1, label=>$i18n->get('quantity','Shop')); + $addToCart->submit(value=>$i18n->get('add to cart','Shop'), label=>$self->getPrice); + $output .= $addToCart->print; + } + + return $output; +} + +#------------------------------------------------------------------- + +=head2 www_addToCart + +Takes form variable badgeId and add the token to the cart. + +=cut + +sub www_addToCart { + my ($self) = @_; + return $self->session->privilege->noAccess() unless $self->getParent->canView; + my $badgeId = $self->session->form->get('badgeId'); + $self->addToCart({badgeId=>$badgeId}); + return $self->getParent->www_buildBadge($badgeId); +} + +#------------------------------------------------------------------- + +=head2 www_delete + +Override to return to appropriate page. + +=cut + +sub www_delete { + my ($self) = @_; + $self->SUPER::www_delete; + return $self->getParent->www_buildBadge(undef,'tokens'); +} + + +#------------------------------------------------------------------- + +=head2 www_edit () + +Displays the edit form. + +=cut + +sub www_edit { + my ($self) = @_; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + $self->session->style->setRawHeadTags(q| + + |); + my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem"); + my $form = $self->getEditForm; + $form->hidden({name=>'proceed', value=>'viewAll'}); + return $self->processStyle('|.$i18n->get('add a badge group').q| + • |.$i18n->get('view badges').q| +
|; + my $groups = $session->db->read("select badgeGroupId,name from EMSBadgeGroup where emsAssetId=?",[$self->getId]); + my $badgeGroups = $self->getBadgeGroups; + foreach my $id (keys %{$badgeGroups}) { + $output .= q||.$i18n->get('add an event meta field').q| + • |.$i18n->get('view tickets').q| +
|; + my $metadataFields = $self->getEventMetaFields; + my $count = 0; + my $number = scalar(@{$metadataFields}); + if ($number) { + foreach my $row1 (@{$metadataFields}) { + my %row = %{$row1}; + $count++; + $output .= "|.$badge->getTitle.q|
+ |.$i18n->get('print').q|
+ • |.$i18n->get('refund').q|
+ • |;
+ if ($registrant->{hasCheckedIn}) {
+ $output .= q||.$i18n->get('mark as not checked in').q||;
+ }
+ else {
+ $output .= q||.$i18n->get('mark as checked in').q||;
+ }
+ $output .= q|
+ • |.$i18n->get('add more items').q|
+
|.$ticket->getTitle.q|
+ |.$i18n->get('print').q|
+ • |.$i18n->get('refund').q|
+
|.$ribbon->getTitle.q|
+ |.$i18n->get('refund').q|
+
|.$token->getTitle.q| (|.$quantity.q|)
+ |.$i18n->get('refund').q|
+
".Dumper($scratchCart).""); - my %var; - - #If there is no missing event data, return nothing - return undef unless scalar(@$missingEventMessageLoop); - - my $i18n = WebGUI::International->new($self->session, 'Asset_EventManagementSystem'); - - $scratchCart = [split("\n",$self->session->scratch->get('EMS_scratch_cart'))]; - - foreach (@$scratchCart) { - my $details = $self->getEventDetails($_); - push(@$allPrereqsLoop, { - 'form.checkBox' => WebGUI::Form::checkbox($self->session, { - value => 1, - checked => 1, - name => "subEventDisregard", - extras => 'disabled="disabled"', - }), - 'title' => $details->{title}, - 'description' => $details->{description}, - 'price' => $details->{price} - }); - } - - $var{'form.header'} = WebGUI::Form::formHeader($self->session,{action=>$self->getUrl}) - .WebGUI::Form::hidden($self->session,{name=>"func",value=>"addToCart"}) - .WebGUI::Form::hidden($self->session,{name=>"method",value=>"addSubEvents"} - ); - - $var{'form.footer'} = WebGUI::Form::formFooter($self->session); - $var{'form.submit'} = WebGUI::Form::Submit($self->session); - $var{'message'} = $i18n->get('missing prerequisites message'); - - #Set the template vars needed to inform the user of the missing prereqs. - $var{'prereqsAreMissing'} = 1; - $var{'message_loop'} = $missingEventMessageLoop; - $var{'missingEvents_loop'} = $allPrereqsLoop; - return \%var; -} - #------------------------------------------------------------------ =head2 getRequiredFields ( ) @@ -1502,658 +2052,7 @@ sub validateEditEventForm { return $errors; } -#------------------------------------------------------------------- -=head2 www_addToCart ( ) - -Method that will add an event to the users shopping cart. - -=cut - -sub www_addToCart { - my ($self, $pid, @pids, $output, $errors, $conflicts, $errorMessages, $shoppingCart); - $self = shift; - $conflicts = shift; - $pid = shift; - $shoppingCart = WebGUI::Commerce::ShoppingCart->new($self->session); - # $self->session->errorHandler->warn("scratch before:
".Dumper($self->getEventsInScratchCart).Dumper($self->session->db->buildHashRef("select name,value from userSessionScratch where sessionId=?",[$self->session->getId]))."");
- # Check if conflicts were found that the user needs to fix
- $output = $conflicts->[0] if defined $conflicts;
-
- unless ($output) { #Skip this if we have errors
-
- if ($self->session->form->get("method") eq "addSubEvents") { # List of ids from subevent form
- @pids = $self->session->form->process("subEventPID", "checkList");
- }
- else { # A single id, i.e., a master event
- my $newPid = $self->session->form->get("pid") || $pid;
- push(@pids, $newPid) unless ($newPid eq "_noid_");
- }
-
- foreach my $eventId (@pids) {
- $self->addToScratchCart($eventId);
- }
-
- # Check to make sure all the prerequisites for this event have been satisfied
- $output = $self->verifyPrerequisitesForm;
-
- #$output = $self->getSubEventForm(\@pids) unless ($output);
- #$output = $self->getSubEventForm($self->getEventsInScratchCart) unless ($output);
-
- $errors = $self->checkConflicts;
- if (scalar(@$errors) > 0) { return $self->error($errors, "www_addToCart"); }
-
- unless ($output) {
- return $self->saveRegistration;
- }
- }
- # $self->session->errorHandler->warn("scratch after: ".Dumper($self->getEventsInScratchCart).Dumper($self->session->db->buildHashRef("select name,value from userSessionScratch where sessionId=?",[$self->session->getId]))."");
- return $self->processStyle($self->processTemplate($output,$self->getValue("checkoutTemplateId")));
-}
-
-#-------------------------------------------------------------------
-sub www_addToScratchCart {
- my $self = shift;
- my $pid = $self->session->form->get("pid");
- my $nameOfEventAdded = $self->getEventName($pid);
- my $masterEventId = $self->session->form->get("mid");
-
- my $mainEvent = $self->addToScratchCart($pid); #tsc
- if ($masterEventId eq $pid) {
- return $self->processStyle($self->processTemplate($self->getRegistrationInfo(),$self->getValue("checkoutTemplateId")));
- }
- return $self->www_search($nameOfEventAdded);
-}
-
-#-------------------------------------------------------------------
-sub addCartVars {
- my $self = shift;
- my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem');
- my $var = shift;
- $var->{'cart.purchaseLoop'} = [];
- my $purchases = $self->session->db->buildArrayRefOfHashRefs(
- "SELECT purchaseId, badgeId FROM EventManagementSystem_sessionPurchaseRef WHERE sessionId=?",
- [$self->session->getId]
- );
- for my $purchase (@{ $purchases }) {
- # so we don't show the badge we're currently editing
- next if ($purchase->{purchaseId} eq $self->session->scratch->get("currentPurchase"));
- my $theseRegs
- = $self->session->db->buildArrayRefOfHashRefs(
- "select r.*, p.price, q.passId, q.passType
- from EventManagementSystem_registrations as r,
- EventManagementSystem_products as q,
- products as p
- where p.productId=r.productId
- and r.badgeId=?
- and r.returned=0
- and r.purchaseId=?
- and q.productId=r.productId",
- [$purchase->{badgeId},$purchase->{purchaseId}]
- );
- my @currentEvents;
- $purchase->{registrantInfoLoop}
- = $self->session->db->buildArrayRefOfHashRefs(
- "select * from EventManagementSystem_badges where badgeId=?",
- [$purchase->{badgeId}]
- );
- foreach (@$theseRegs) {
- my ($isChild)
- = $self->session->db->quickArray(
- "select prerequisiteId from EventManagementSystem_products where productId = ?",
- [$_->{productId}]
- );
- $purchase->{'purchase.mainEventTitle'} = $self->getEventName($_->{productId}) unless $isChild;
- ($purchase->{alreadyPurchasedBadge})
- = $self->session->db->quickArray(
- "select p.transactionId
- from EventManagementSystem_purchases as p,
- transaction as t
- where p.purchaseId = ?
- and t.transactionId=p.transactionId
- and t.status='Completed'",
- [$_->{productId}]
- ) unless $isChild;
- push @currentEvents,$_->{productId};
- }
- my @pastEvents
- = $self->session->db->buildArray(
- "select r.productId
- from EventManagementSystem_registrations as r,
- EventManagementSystem_purchases as p,
- transaction as t
- where r.returned=0
- and r.badgeId=?
- and t.transactionId=p.transactionId
- and t.status='Completed'
- and p.purchaseId=r.purchaseId
- group by productId",
- [$purchase->{badgeId}]
- );
- push(@currentEvents,@pastEvents);
- $purchase->{newPrice} = 0;
- foreach (@$theseRegs) {
- my @discountPasses = split(/::/,$_->{passId});
- if (scalar(@discountPasses) && ($_->{passType} eq 'member')) {
- my $addlPrice = $_->{price};
- foreach my $eligiblePass (@discountPasses) {
- my @passEvents = $self->session->db->buildArray("select productId from EventManagementSystem_products where passType='defines' and passId=?",[$eligiblePass]);
- next unless isIn($eligiblePass,@currentEvents);
- my $pass = $self->session->db->quickHashRef("select * from EventManagementSystem_discountPasses where passId=?",[$eligiblePass]);
- if ($pass->{type} eq 'newPrice') {
- $addlPrice = (0 + $pass->{amount}) if ($addlPrice > (0 + $pass->{amount}));
- } elsif ($pass->{type} eq 'amountOff') {
- # not yet implemented!
- } elsif ($pass->{type} eq 'percentOff') {
- # not yet implemented!
- }
- }
- $purchase->{newPrice} += $addlPrice;
- } else {
- $purchase->{newPrice} += $_->{price};
- }
- }
- $purchase->{editIcon} = $self->session->icon->edit("func=addEventsToBadge;bid=".$purchase->{badgeId}.";purchaseId=".$purchase->{purchaseId}, $self->get('url'));
- $purchase->{deleteIcon} = $self->session->icon->delete("func=addEventsToBadge;bid=none;purchaseId=".$purchase->{purchaseId},$self->get('url'),$i18n->get('confirm delete purchase'));
- $purchase->{'edit.url'} = $self->getUrl("func=addEventsToBadge;bid=".$purchase->{badgeId}.";purchaseId=".$purchase->{purchaseId});
- $purchase->{'delete.url'} = $self->getUrl("func=addEventsToBadge;bid=none;purchaseId=".$purchase->{purchaseId});
- push(@{$var->{'cart.purchaseLoop'}},$purchase);
- }
- $var->{'checkoutUrl'} = $self->getUrl("func=checkout");
-}
-
-#-------------------------------------------------------------------
-sub www_checkout {
- my $self = shift;
- return WebGUI::Operation::Commerce::www_checkout($self->session);
-}
-
-#-------------------------------------------------------------------
-sub www_emptyCart {
- my $self = shift;
- my $shoppingCart = WebGUI::Commerce::ShoppingCart->new($self->session);
- $shoppingCart->empty;
- $self->session->db->write(
- "DELETE FROM EventManagementSystem_sessionPurchaseRef WHERE sessionId=?",
- [$self->session->getId]
- );
- return $self->www_resetScratchCart();
-}
-
-#-------------------------------------------------------------------
-sub www_editRegistrantInfo {
- my $self = shift;
- return $self->processStyle($self->processTemplate($self->getRegistrationInfo(),$self->getValue("checkoutTemplateId")));
-}
-
-#-------------------------------------------------------------------
-sub www_deleteCartItem {
- my $self = shift;
- my $event1 = $self->session->form->get("event1");
- my $event2 = $self->session->form->get("event2");
- my $eventUserDeleted = $self->session->form->get("productToRemove");
- #my $cart = WebGUI::Commerce::ShoppingCart->new($self->session);
-
- # Delete all of the subevents last added by the user
- #$cart->delete($event1, 'Event');
- #$cart->delete($event2, 'Event');
-
- $self->removeFromScratchCart($event1);
- $self->removeFromScratchCart($event2);
-
- # Add the subevents back to the cart except for the one the user choose to remove.
- # This will re-trigger the conflict/sub-event display code correctly
-
- my $eventToAdd = ($event1 eq $eventUserDeleted) ? $event2 : $event1;
-
- return $self->www_addToCart(undef,$eventToAdd);
-}
-
-#-------------------------------------------------------------------
-
-=head2 www_deleteEvent ( )
-
-Method to delete an event, and to remove the deleted event from all prerequisite definitions
-
-=cut
-
-sub www_deleteEvent {
- my $self = shift;
- return $self->session->privilege->insufficient unless ($self->canAddEvents);
- $self->deleteEvent($self->session->form->get("pid"));
- return $self->www_search;
-}
-
-#-------------------------------------------------------------------
-
-=head2 www_deletePrereqSet ( )
-
-Method to delete a prerequisite assignment of one event to another
-
-=cut
-
-sub www_deletePrereqSet {
- my $self = shift;
- return $self->session->privilege->insufficient unless ($self->canAddEvents);
- $self->deletePrereqSet($self->session->form->get("psid"));
- return $self->www_editEvent;
-}
-
-#-------------------------------------------------------------------
-
-=head2 www_edit ( )
-
-Edit wobject method.
-
-=cut
-
-sub www_edit {
- my $self = shift;
- return $self->session->privilege->insufficient() unless $self->canEdit;
- return $self->session->privilege->locked() unless $self->canEditIfLocked;
- my ($tag) = ($self->get("className") =~ /::(\w+)$/);
- my $tag2 = $tag;
- $tag =~ s/([a-z])([A-Z])/$1 $2/g; #Separate studly caps
- $tag =~ s/([A-Z]+(?![a-z]))/$1 /g; #Separate acronyms
- my $i18n = WebGUI::International->new($self->session,'Asset_Wobject');
- my $i18n2 = WebGUI::International->new($self->session,'Asset_EventManagementSystem');
- $self->getAdminConsole->addSubmenuItem($self->getUrl('func=manageEventMetadata'), $i18n->get('manage event metadata', 'Asset_EventManagementSystem'));
- $self->getAdminConsole->addSubmenuItem($self->getUrl('func=manageEvents'), $i18n->get('manage events', 'Asset_EventManagementSystem'));
- $self->getAdminConsole->addSubmenuItem($self->getUrl('func=importEvents'), $i18n2->get('import events'));
- $self->getAdminConsole->addSubmenuItem($self->getUrl('func=exportEvents'), $i18n2->get('export events'));
- return $self->getAdminConsole->render($self->getEditForm->print, $self->addEditLabel);
-}
-
-#-------------------------------------------------------------------
-
-=head2 www_editEvent ( errors )
-
-Method to generate form to Add or Edit an events properties including prerequisite assignments and event approval.
-
-=head3 errors
-
-An array reference of error messages to display to the user
-
-=cut
-
-sub www_editEvent {
- my $self = shift;
- my $errors = shift;
- my $errorMessages;
-
- return $self->session->privilege->insufficient unless ($self->canAddEvents);
-
- my $pid = shift || $self->session->form->get("pid");
- my ($storageId) = $self->session->db->quickArray("select imageId from EventManagementSystem_products where productId=?",[$pid]) unless ($pid eq "");
-
- my $i18n = WebGUI::International->new($self->session,'Asset_EventManagementSystem');
-
- my $event = $self->session->db->quickHashRef("
- select p.productId, p.title, p.description, p.price, p.useSalesTax, p.weight, p.sku, p.templateId, p.skuTemplate, e.prerequisiteId, e.passType, e.passId,
- e.startDate, e.endDate, e.maximumAttendees, e.approved
- from
- products as p, EventManagementSystem_products as e
- where
- p.productId = e.productId and p.productId=?",[$pid]
- );
-
- my $f = WebGUI::HTMLForm->new($self->session,-action=>$self->getUrl);
-
- # Errors
- foreach (@$errors) {
- $errorMessages .= sprintf "%s: %s You searched for: $query
"; - my $wildQuery = '%'.$query.'%'; - my $badges = $db->read("select badgeId, lastName, firstName, city, state, email from EventManagementSystem_badges - where assetId=? and (lastName like ? or firstName like ? or email like ? or badgeId like ?) - order by lastName, firstName", [$self->getId, $wildQuery, $wildQuery, $wildQuery, $wildQuery]); - $results .= q|| Name | Location | Badge ID | |
|---|---|---|---|
| $last, $first | $city, $state | $badgeId | |
| $sku : $title | -$start - $end | ($gateway) $price | |
| $sku : $title | -$start - $end | -($gateway) $price | |; - if ($isMaster) { - $tickets .= qq| -- | - |; - } - else { - $tickets .= qq| - | - | |; - } - $tickets .= qq| - | - |
'.$text.'';
+}
+
+1;
+
+
diff --git a/lib/WebGUI/Paginator.pm b/lib/WebGUI/Paginator.pm
index d63086e21..1c54e8d1d 100644
--- a/lib/WebGUI/Paginator.pm
+++ b/lib/WebGUI/Paginator.pm
@@ -567,7 +567,7 @@ The number of rows to display per page. If left blank it defaults to 25.
=head3 formVar
-Specify the form variable the paginator should use in it's links. Defaults to "pn".
+Specify the form variable the paginator should use in its links. Defaults to "pn".
=head3 pageNumber
diff --git a/lib/WebGUI/Pluggable.pm b/lib/WebGUI/Pluggable.pm
index 709d6bf77..7fb0c2400 100644
--- a/lib/WebGUI/Pluggable.pm
+++ b/lib/WebGUI/Pluggable.pm
@@ -46,7 +46,7 @@ These functions are available from this package:
#-------------------------------------------------------------------
-=head2 instanciate ( module, method, params )
+=head2 instanciate ( module, sub, params )
Dynamically ensures that a plugin module is loaded into memory. Then instanciates a new object from the module. Croaks on failure.
@@ -54,6 +54,15 @@ Dynamically ensures that a plugin module is loaded into memory. Then instanciate
The name of the module you'd like to load like "WebGUI::Asset::Snippet";
+=head3 sub
+
+The name of the constructor you would like to invoke from the module. Usually "new", or sometimes "create".
+
+=head3 params
+
+An array ref of params to send to the constructor. In WebGUI, the first param should be a WebGUI::Session
+object.
+
=cut
sub instanciate {
diff --git a/lib/WebGUI/SQL.pm b/lib/WebGUI/SQL.pm
index 1675b2ec2..c81151ec2 100644
--- a/lib/WebGUI/SQL.pm
+++ b/lib/WebGUI/SQL.pm
@@ -225,8 +225,7 @@ sub buildArrayRefOfHashRefs {
my $sql = shift;
my $params = shift;
my $sth = $class->read($sql,$params);
- my $data;
- while ($data = $sth->hashRef) {
+ while (my $data = $sth->hashRef) {
push(@array,$data);
}
$sth->finish;
@@ -236,7 +235,47 @@ sub buildArrayRefOfHashRefs {
#-------------------------------------------------------------------
-=head2 buildHashRefOfHashRefs ( sql )
+=head2 buildDataTableStructure ( sql, params )
+
+Builds a data structure that can be converted to JSON and sent
+to a YUI Data Table. This is basically a hash of information about
+the results, with one of the keys being an array ref of hashrefs. It also
+calculates the total records that could have been matched without a limit
+statement, as well as how many were actually matched. It returns a hash.
+
+=head3 sql
+
+An SQL query. The query may select as many columns of data as you wish. The query
+should contain a SQL_CALC_ROWS_FOUND entry so that the total number of available
+rows can be sent to the Data Table.
+
+=head3 params
+
+An array reference containing values for any placeholder params used in the SQL query.
+
+=cut
+
+sub buildDataTableStructure {
+ my $self = shift;
+ my $sql = shift;
+ my $params = shift;
+ my %hash;
+ my @array;
+ ##Note, I need a valid statement handle for doing the rows method on.
+ my $sth = $self->read($sql,$params);
+ while (my $data = $sth->hashRef) {
+ push(@array,$data);
+ }
+ $hash{records} = \@array;
+ $hash{totalRecords} = $self->quickScalar('select found_rows()') + 0; ##Convert to numeric
+ $hash{recordsReturned} = $sth->rows()+0;
+ $sth->finish;
+ return %hash;
+}
+
+#-------------------------------------------------------------------
+
+=head2 buildHashRefOfHashRefs ( sql, params, key )
Builds a hash reference of hash references of data
from a series of rows. Useful for returning many rows at once.
@@ -273,6 +312,53 @@ sub buildHashRefOfHashRefs {
}
+#-------------------------------------------------------------------
+
+=head2 buildSearchQuery ( $sql, $placeholders, $keywords, $columns )
+
+Append information to an existing SQL statement for implementing
+basic search functions. The ammended SQL and an array of placeholder
+variables will be returned.
+
+=head3 $sql
+
+A scalar reference to an SQL query. The clauses to add search-like capabilities will be
+appended to the end of the query.
+
+=head3 $placeholders
+
+An array reference of placeholders already added to the query.
+
+=head3 $keywords
+
+This is the data that will be searched for in columns. An SQL wildcard '%' will
+be added to the beginning and end of $keywords.
+
+=head3 $columns
+
+An arrayref of column names that should be searched for $keywords.
+
+=cut
+
+sub buildSearchQuery {
+ my ($self, $sql, $placeHolders, $keywords, $columns) = @_;
+ if ($$sql =~ m/where/) {
+ $$sql .= ' and (';
+ }
+ else {
+ $$sql .= ' where (';
+ }
+ $keywords = lc('%'.$keywords.'%');
+ my $counter = 0;
+ foreach my $field (@{ $columns }) {
+ $$sql .= ' or' if ($counter > 0);
+ $$sql .= qq{ LOWER( $field ) like ?};
+ push(@{$placeHolders}, $keywords);
+ $counter++;
+ }
+ $$sql .= ')';
+}
+
#-------------------------------------------------------------------
=head2 commit ( )
@@ -758,7 +844,8 @@ sub quoteAndJoin {
=head2 read ( sql [ , placeholders ] )
-This is a convenience method for WebGUI::SQL::ResultSet->read().
+This is a convenience method for WebGUI::SQL::ResultSet->read(). It returns the statement
+handler.
=head3 sql
diff --git a/lib/WebGUI/SQL/ResultSet.pm b/lib/WebGUI/SQL/ResultSet.pm
index 9ddf3a3f9..0e0bf7b28 100644
--- a/lib/WebGUI/SQL/ResultSet.pm
+++ b/lib/WebGUI/SQL/ResultSet.pm
@@ -68,8 +68,8 @@ Returns the next row of data as an array reference. Note that this is 12% faster
=cut
sub arrayRef {
- my $self = shift;
- return $self->sth->fetchrow_arrayref() or $self->db->session->errorHandler->fatal("Couldn't fetch array. ".$self->errorMessage);
+ my $self = shift;
+ return $self->sth->fetchrow_arrayref() or $self->db->session->errorHandler->fatal("Couldn't fetch array. ".$self->errorMessage);
}
@@ -82,8 +82,8 @@ A reference to the current WebGUI::SQL object.
=cut
sub db {
- my $self = shift;
- return $self->{_db};
+ my $self = shift;
+ return $self->{_db};
}
#-------------------------------------------------------------------
@@ -227,7 +227,8 @@ sub prepare {
=head2 read ( sql, db, placeholders )
-Constructor. Returns a result set statement handler.
+Constructor. Returns a result set statement handler after doing a prepare and execute on
+the supplied SQL query and the placeholders.
=head3 sql
diff --git a/lib/WebGUI/Shop/Address.pm b/lib/WebGUI/Shop/Address.pm
new file mode 100644
index 000000000..0674e5f74
--- /dev/null
+++ b/lib/WebGUI/Shop/Address.pm
@@ -0,0 +1,243 @@
+package WebGUI::Shop::Address;
+
+use strict;
+use Class::InsideOut qw{ :std };
+use WebGUI::Exception::Shop;
+
+=head1 NAME
+
+Package WebGUI::Shop::Address
+
+=head1 DESCRIPTION
+
+An address is used to track shipping or payment addresses in the commerce system.
+
+=head1 SYNOPSIS
+
+ use WebGUI::Shop::Address;
+
+ my $address = WebGUI::Shop::Address->new($addressBook, $addressId);
+
+=head1 METHODS
+
+These subroutines are available from this package:
+
+=cut
+
+readonly addressBook => my %addressBook;
+private properties => my %properties;
+
+#-------------------------------------------------------------------
+
+=head2 addressBook ( )
+
+Returns a reference to the Address Book.
+
+=cut
+
+#-------------------------------------------------------------------
+
+=head2 create ( addressBook, address)
+
+Constructor. Adds an address to an address book. Returns a reference to the address.
+
+=head3 addressBook
+
+A reference to a WebGUI::Shop::AddressBook object.
+
+=head3 address
+
+A hash reference containing the properties to set in the address.
+
+=cut
+
+sub create {
+ my ($class, $book, $addressData) = @_;
+ unless (defined $book && $book->isa("WebGUI::Shop::AddressBook")) {
+ WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::AddressBook", got=>(ref $book), error=>"Need an address book.", param=>$book);
+ }
+ unless (defined $addressData && ref $addressData eq "HASH") {
+ WebGUI::Error::InvalidParam->throw(param=>$addressData, error=>"Need a hash reference.");
+ }
+ my $id = $book->session->db->setRow("address","addressId", {addressId=>"new", addressBookId=>$book->getId});
+ my $address = $class->new($book, $id);
+ $address->update($addressData);
+ return $address;
+}
+
+#-------------------------------------------------------------------
+
+=head2 delete ( )
+
+Removes this address from the book.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ $self->addressBook->session->db->deleteRow("address","addressId",$self->getId);
+ undef $self;
+ return undef;
+}
+
+#-------------------------------------------------------------------
+
+=head2 get ( [ property ] )
+
+Returns a duplicated hash reference of this object’s data.
+
+=head3 property
+
+Any field − returns the value of a field rather than the hash reference.
+
+=cut
+
+sub get {
+ my ($self, $name) = @_;
+ if (defined $name) {
+ return $properties{id $self}{$name};
+ }
+ my %copyOfHashRef = %{$properties{id $self}};
+ return \%copyOfHashRef;
+}
+
+#-------------------------------------------------------------------
+
+=head2 getHtmlFormatted ()
+
+Returns an HTML formatted address for display.
+
+=cut
+
+sub getHtmlFormatted {
+ my $self = shift;
+ my $address = $self->get("name") . "| }. $i18n->get("transaction id") .q{ | }. $transaction->getId .q{ | +
|---|---|
| }. $i18n->get("order number") .q{ | }. $transaction->get('orderNumber') .q{ | +
| }. $i18n->get("shipping address") .q{ | }. join(" ",$transaction->get('shippingAddressName'),$transaction->get('shippingAddress1'),$transaction->get('shippingAddress2'),$transaction->get('shippingAddress3'),$transaction->get('shippingCity'),$transaction->get('shippingState'),$transaction->get('shippingCode'),$transaction->get('shippingCountry'),$transaction->get('shippingPhoneNumber')) .q{ | +
| }. $i18n->get("payment address") .q{ | }. join(" ",$transaction->get('paymentAddressName'),$transaction->get('paymentAddress1'),$transaction->get('paymentAddress2'),$transaction->get('paymentAddress3'),$transaction->get('paymentCity'),$transaction->get('paymentState'),$transaction->get('paymentCode'),$transaction->get('paymentCountry'),$transaction->get('paymentPhoneNumber')) .q{ | +
| }. $i18n->get("price") .q{ | }. $transaction->get('amount') .q{ | +
The name of this parameter.
|, + lastUpdated => 1208130267, + }, + + 'edit parameter' => { + message => q|Edit product parameter|, + lastUpdated => 1208130542, + context => q|The name of the editParameter form| + }, + + 'edit option' => { + message => q|Edit product parameter option|, + lastUpdated => 1208144888, + context => q|The name of the editParameter form| + }, + + 'edit option value' => { + message => q|Value|, + lastUpdated => 1208320423, + context => q|The form label for the value field in editParameterOption| + }, + + 'edit option value description' => { + message => q|The value of this option (ie. 'Blue').
|, + lastUpdated => 1208320422, + }, + + 'edit option price modifier' => { + message => q|Price modifier|, + lastUpdated => 1208292477, + context => q|The form label for the priceModifier field in editProductParameterOption| + }, + + 'edit option price modifier description' => { + message => q|The amount this option adds to the default price for product variants containig this option.
|, + lastUpdated => 1146606364, + }, + + 'edit option weight modifier' => { + message => q|Weight modifier|, + lastUpdated => 1208292479, + context => q|The form label for the weightModifier field in editProductParameterOption| + }, + + 'edit option weight modifier description' => { + message => q|The weight this option adds to the default weight for product variants consisting of this option.
|, + lastUpdated => 1208292519, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_Sku.pm b/lib/WebGUI/i18n/English/Asset_Sku.pm new file mode 100644 index 000000000..97d4a2e78 --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_Sku.pm @@ -0,0 +1,80 @@ +package WebGUI::i18n::English::Asset_Sku; + +use strict; + +our $I18N = { + 'shop' => { + message => q|Shop|, + lastUpdated => 0, + context => q|The name of a tab that all Sku based assets have to put their commerce related settings.| + }, + + 'description' => { + message => q|Description|, + lastUpdated => 0, + context => q|The label for the description of the product.| + }, + + 'description help' => { + message => q|Describe the product or service here.|, + lastUpdated => 0, + context => q|help for description field| + }, + + 'sku' => { + message => q|SKU|, + lastUpdated => 0, + context => q|Abbreviation for "Stock Keeping Unit" which is used as a product number or other such record keeping number.| + }, + + 'sku help' => { + message => q|Stands for Stock Keeping Unit, which is just a fancy term for an inventory code or product number.|, + lastUpdated => 0, + context => q|help for sku field| + }, + + 'sales agent' => { + message => q|sales agent|, + lastUpdated => 0, + context => q|asset field relating to who is selling this product| + }, + + 'sales agent help' => { + message => q|Which person/company defined in the commerce system should get credit for selling this item, if any?|, + lastUpdated => 0, + context => q|help for sales agent field| + }, + + 'override tax rate' => { + message => q|Override tax rate?|, + lastUpdated => 0, + context => q|A yes/no field asking whether to override tax rate.| + }, + + 'override tax rate help' => { + message => q|Would you like to override the default tax rate for this item? Usually used in locales that have special or no tax on life essential items like food and clothing.|, + lastUpdated => 0, + context => q|help for override tax rate field| + }, + + 'tax rate override' => { + message => q|Tax Rate Override|, + lastUpdated => 0, + context => q|a field containing the percentage to use to calculate tax for this item| + }, + + 'tax rate override help' => { + message => q|What is the new percentage that should be used to calculate tax on this item?|, + lastUpdated => 0, + context => q|help for tax rate override field| + }, + + 'assetName' => { + message => q|Sku|, + lastUpdated => 0, + context => "The name of this asset." + }, + +}; + +1; diff --git a/lib/WebGUI/i18n/English/PayDriver.pm b/lib/WebGUI/i18n/English/PayDriver.pm new file mode 100644 index 000000000..0557e34c2 --- /dev/null +++ b/lib/WebGUI/i18n/English/PayDriver.pm @@ -0,0 +1,93 @@ +package WebGUI::i18n::English::PayDriver; + +use strict; + +our $I18N = { + 'thank you for your order' => { + message => q|Thank You For Your Order|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'a sale has been made' => { + message => q|A Sale Has Been Made|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'sale notification template' => { + message => q|Sale Notification Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'sale notification template help' => { + message => q|Which template should be used to generate the email that notifies this store owner about a new sale.|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'sale notification group' => { + message => q|Sale Notification Group|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'sale notification group help' => { + message => q|Who should be notified of new transactions?|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'receipt email template' => { + message => q|Receipt Email Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'receipt email template help' => { + message => q|Which template should be used to generate an email that will be sent to the user to acknowledge their purchase?|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'label' => { + message => q|Label|, + lastUpdated => 0, + context => q|Label for the label option.| + }, + + 'label help' => { + message => q|The name by which this pagyment gateway is displayed.|, + lastUpdated => 0, + context => q|Hover help for the label option.| + }, + + 'enabled' => { + message => q|Enabled|, + lastUpdated => 0, + context => q|Label for the enabled option.|, + }, + + 'enabled help' => { + message => q|Sets whether this payment gateway is enabled|, + lastUpdated => 0, + context => q|Hover help for the enabled option.|, + + }, + + 'who can use' => { + message => q|Group to use this gateway|, + lastUpdate => 0, + context => q|Label for the group to use option.|, + }, + + 'who can use help' => { + message => q|Specifies which group is allowed to use this payment gateway.|, + lastUpdated => 0, + context => q|Hover help for the group to use option.|, + }, + +}; + +1; diff --git a/lib/WebGUI/i18n/English/ShipDriver.pm b/lib/WebGUI/i18n/English/ShipDriver.pm new file mode 100644 index 000000000..d5aa44be8 --- /dev/null +++ b/lib/WebGUI/i18n/English/ShipDriver.pm @@ -0,0 +1,31 @@ +package WebGUI::i18n::English::ShipDriver; + +use strict; + +our $I18N = { + + 'label' => { + message => q|Label|, + lastUpdated => 1203569535, + context => q|The name of a ShipDriver, supplied by the user.|, + }, + + 'label help' => { + message => q|A name for your Shipping Driver. Choose something clear, easy to understand, and less than 100 characters.|, + lastUpdated => 1203569511, + }, + + 'enabled' => { + message => q|Enabled?|, + lastUpdated => 1203569584, + context => q|Whether something can or cannot be used.|, + }, + + 'enabled help' => { + message => q|Will people using commerce on your site be able to use this Shipping Driver?|, + lastUpdated => 1203569582, + }, + +}; + +1; diff --git a/lib/WebGUI/i18n/English/ShipDriver_FlatRate.pm b/lib/WebGUI/i18n/English/ShipDriver_FlatRate.pm new file mode 100644 index 000000000..943f49492 --- /dev/null +++ b/lib/WebGUI/i18n/English/ShipDriver_FlatRate.pm @@ -0,0 +1,53 @@ +package WebGUI::i18n::English::ShipDriver_FlatRate; + +use strict; + +our $I18N = { + + 'flatFee' => { + message => q|Flat Fee|, + lastUpdated => 1203569535, + context => q|A fixed amount of money added to a purchase for shipping.|, + }, + + 'flatFee help' => { + message => q|A fixed amount of money added to a purchase for shipping.|, + lastUpdated => 1203569511, + }, + + 'percentageOfPrice' => { + message => q|Percentage of Price|, + lastUpdated => 1203569584, + context => q|A shipping cost added to a cart as a percentage of the total.|, + }, + + 'percenageOfPrice help' => { + message => q|A shipping cost added to a cart as a percentage of the total cost of the cart.|, + lastUpdated => 1203569582, + }, + + 'percentageOfWeight' => { + message => q|Percentage of Weight|, + lastUpdated => 1203569584, + context => q|A shipping cost added to a cart as a percentage of the weight.|, + }, + + 'percentageOfWeight help' => { + message => q|A shipping cost added to a cart as a percentage of the total weight of all items in the cart.|, + lastUpdated => 1203569582, + }, + + 'pricePerItem' => { + message => q|Price Per Item|, + lastUpdated => 1203569584, + context => q|A shipping cost added to a cart based on the number of items in the cart.|, + }, + + 'pricePerItem help' => { + message => q|A shipping cost added to a cart based on the number of items in the cart.|, + lastUpdated => 1203569582, + }, + +}; + +1; diff --git a/lib/WebGUI/i18n/English/Shop.pm b/lib/WebGUI/i18n/English/Shop.pm new file mode 100644 index 000000000..9c730da7f --- /dev/null +++ b/lib/WebGUI/i18n/English/Shop.pm @@ -0,0 +1,344 @@ +package WebGUI::i18n::English::Shop; + +use strict; + +our $I18N = { + 'view cart' => { + message => q|View Cart|, + lastUpdated => 0, + context => q|a link label|, + }, + + 'my purchases template' => { + message => q|My Purchases Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'my purchases template help' => { + message => q|Which template should be used to display a user's order history?|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'my purchases detail template' => { + message => q|My Purchases Detail Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'my purchases detail template help' => { + message => q|Which template should be used to display a user's order history detail? An individual sale rather than the whole transaction list.|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'username' => { + message => q|User|, + lastUpdated => 0, + context => q|field label| + }, + + 'date' => { + message => q|Date|, + lastUpdated => 0, + context => q|field label| + }, + + 'order number' => { + message => q|Order #|, + lastUpdated => 0, + context => q|field label| + }, + + 'status code' => { + message => q|Status Code|, + lastUpdated => 0, + context => q|field label| + }, + + 'status message' => { + message => q|Status Message|, + lastUpdated => 0, + context => q|field label| + }, + + 'payment method' => { + message => q|Payment Method|, + lastUpdated => 0, + context => q|field label| + }, + + 'add shipper' => { + message => q|Add Shipping Method|, + lastUpdated => 0, + context => q|button in shipping manager| + }, + + 'shopping cart template' => { + message => q|Cart Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'shopping cart template help' => { + message => q|Choose the template that you want used to render the shopping cart.|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'address book template' => { + message => q|Address Book Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'address book template help' => { + message => q|Choose the template you want used to render the address book.|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'edit address template' => { + message => q|Edit Address Template|, + lastUpdated => 0, + context => q|commerce setting| + }, + + 'edit address template help' => { + message => q|Choose the template you want used to render the address edit form.|, + lastUpdated => 0, + context => q|commerce setting help| + }, + + 'transactions' => { + message => q|Transactions|, + lastUpdated => 0, + context => q|admin function label| + }, + + 'payment methods' => { + message => q|Payment Methods|, + lastUpdated => 0, + context => q|admin function label| + }, + + 'shipping methods' => { + message => q|Shipping Methods|, + lastUpdated => 0, + context => q|admin function label| + }, + + 'taxes' => { + message => q|Taxes|, + lastUpdated => 0, + context => q|admin function label| + }, + + 'shop settings' => { + message => q|Shop Settings|, + lastUpdated => 0, + context => q|admin function label| + }, + + 'is a required field' => { + message => q|%s is a required field.|, + lastUpdated => 0, + context => q|an error message| + }, + + 'label' => { + message => q|Label|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'label help' => { + message => q|eg: 'Home' or 'Work'|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'name' => { + message => q|Name|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'address' => { + message => q|Address|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'city' => { + message => q|City|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'state' => { + message => q|State / Province|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'code' => { + message => q|Postal / Zip Code|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'country' => { + message => q|Country|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'phone number' => { + message => q|Phone Number|, + lastUpdated => 0, + context => q|a label in the address editor| + }, + + 'add a new address' => { + message => q|Add A New Address|, + lastUpdated => 0, + context => q|a button in the address book| + }, + + 'delete' => { + message => q|Delete|, + lastUpdated => 0, + context => q|a button in the address book| + }, + + 'edit' => { + message => q|Edit|, + lastUpdated => 0, + context => q|a button in the address book| + }, + + 'use this address' => { + message => q|Use This Address|, + lastUpdated => 0, + context => q|a button in the address book| + }, + + 'too many of this item' => { + message => q|Can't add that many %s to your cart.|, + lastUpdated => 0, + context => q|an error message| + }, + + 'subtotal' => { + message => q|Subtotal|, + lastUpdated => 0, + context => q|a summary heading in the cart| + }, + + 'coupon' => { + message => q|Coupon|, + lastUpdated => 0, + context => q|a summary heading in the cart| + }, + + 'tax' => { + message => q|Tax|, + lastUpdated => 0, + context => q|a summary heading in the cart| + }, + + 'total' => { + message => q|Total|, + lastUpdated => 0, + context => q|a summary heading in the cart| + }, + + 'shipping' => { + message => q|Shipping|, + lastUpdated => 0, + context => q|a summary heading in the cart| + }, + + 'not applicable' => { + message => q|N/A|, + lastUpdated => 0, + context => q|shipping not possible on this item because it's not a physical good| + }, + + 'item' => { + message => q|Item|, + lastUpdated => 0, + context => q|a column heading label in the shopping cart| + }, + + 'price' => { + message => q|Price|, + lastUpdated => 0, + context => q|a column heading label in the shopping cart| + }, + + 'quantity' => { + message => q|Quantity|, + lastUpdated => 0, + context => q|a column heading label in the shopping cart| + }, + + 'extended price' => { + message => q|Extended Price|, + lastUpdated => 0, + context => q|a column heading label in the shopping cart| + }, + + 'per item shipping' => { + message => q|Per Item Shipping|, + lastUpdated => 0, + context => q|a column heading label in the shopping cart| + }, + + 'remove button' => { + message => q|Remove|, + lastUpdated => 0, + context => q|a button a user clicks on to remove an item from the cart| + }, + + 'checkout button' => { + message => q|Checkout|, + lastUpdated => 0, + context => q|a button the user clicks on to proceed to payment options| + }, + + 'choose shipping button' => { + message => q|Choose Shipping Address|, + lastUpdated => 0, + context => q|a button the user clicks on to choose shipping information| + }, + + 'update cart button' => { + message => q|Update Cart|, + lastUpdated => 0, + context => q|a button the user clicks on to apply changes to the cart| + }, + + 'continue shopping button' => { + message => q|Continue Shopping|, + lastUpdated => 0, + context => q|a button the user clicks on to go back to shopping after viewing the cart| + }, + + 'shop' => { + message => q|Shop|, + lastUpdated => 0, + context => q|the title of all commerce related stuff in the admin console| + }, + + 'ship to button' => { + message => q|Ship To|, + lastUpdated => 0, + context => q|a button the user clicks on to set shipping information| + }, + +}; + +1; diff --git a/lib/WebGUI/i18n/English/Tax.pm b/lib/WebGUI/i18n/English/Tax.pm new file mode 100644 index 000000000..7fa8c17fc --- /dev/null +++ b/lib/WebGUI/i18n/English/Tax.pm @@ -0,0 +1,61 @@ +package WebGUI::i18n::English::Tax; + +use strict; + +our $I18N = { + + 'country' => { + message => q|Country|, + lastUpdated => 1205120607, + context => q|The name of a country, such as Portugal or Canada.|, + }, + + 'state' => { + message => q|State|, + lastUpdated => 1205120615, + context => q|A political subdivision of a country, such as California.|, + }, + + 'city' => { + message => q|City|, + lastUpdated => 1205120661, + }, + + 'code' => { + message => q|Code|, + lastUpdated => 1205120660, + context => q|A postal code, or zip code.|, + }, + + 'tax rate' => { + message => q|Tax Rate|, + lastUpdated => 1206302052, + context => q|The amount that a person is charged to buy something, a percentage of the price.|, + }, + + 'export' => { + message => q|Export|, + lastUpdated => 1206307669, + context => q|To ship a copy of the tax data out of the server.|, + }, + + 'import' => { + message => q|Import|, + lastUpdated => 1206390280, + context => q|To bring in new tax data that replaces the current data.|, + }, + + 'delete' => { + message => q|delete|, + lastUpdated => 1206385749, + context => q|To remove one tax entry from the tax tables.|, + }, + + 'add a tax' => { + message => q|Add new tax information|, + lastUpdated => 1206395083, + }, + +}; + +1; diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index c17a34d8f..cdadde9d1 100644 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -125,6 +125,7 @@ checkModule("HTML::TagCloud","0.34"); checkModule("Image::ExifTool","7.00"); checkModule("Archive::Any","0.093"); checkModule("Path::Class", '0.16'); +checkModule("Exception::Class","1.23"); ################################### diff --git a/t/Asset/Sku.t b/t/Asset/Sku.t new file mode 100644 index 000000000..9c0ed9d39 --- /dev/null +++ b/t/Asset/Sku.t @@ -0,0 +1,86 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2008 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 +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# This tests WebGUI::Asset::Sku, which is the base class for commerce items + +use FindBin; +use strict; +use lib "$FindBin::Bin/../lib"; +use Test::More; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Asset; +use WebGUI::Asset::Sku; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 19; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# put your tests here +my $root = WebGUI::Asset->getRoot($session); +my $sku = $root->addChild({ + className=>"WebGUI::Asset::Sku", + title=>"Test Sku", + }); +isa_ok($sku, "WebGUI::Asset::Sku"); + +$sku->addToCart; + +$sku->applyOptions({ + test1 => "YY" + }); + +my $options = $sku->getOptions; +is($options->{test1}, "YY", "Can set and get an option."); + + +is($sku->getMaxAllowedInCart, 99999999, "Got a valid default max in cart."); +is($sku->getQuantityAvailable, 99999999, "skus should have an unlimited quantity by default"); +is($sku->getQuantityAvailable, $sku->getMaxAllowedInCart, "quantity available and max allowed in cart should be the same"); +is($sku->getPrice, 0.00, "Got a valid default price."); +is($sku->getWeight, 0, "Got a valid default weight."); +is($sku->getTaxRate, undef, "Tax rate is not overridden."); +$sku->update({overrideTaxRate=>1, taxRateOverride=>5}); +is($sku->getTaxRate, 5, "Tax rate is overridden."); +isnt($sku->processStyle, "", "Got some style information."); +is($sku->onAdjustQuantityInCart, undef, "onAdjustQuantityInCart should exist and return undef"); +is($sku->onCompletePurchase, undef, "onCompletePurchase should exist and return undef"); +is($sku->onRemoveFromCart, undef, "onRemoveFromCart should exist and return undef"); +is($sku->isRecurring, 0, "skus are not recurring by default"); +is($sku->isShippingRequired, 0, "skus are not shippable by default"); +is($sku->getConfiguredTitle, $sku->getTitle, "configured title and title should be the same by default"); + +isa_ok($sku->getCart, "WebGUI::Shop::Cart", "can get a cart object"); +my $item = $sku->addToCart; +isa_ok($item, "WebGUI::Shop::CartItem", "can add to cart"); +$item->cart->delete; + +my $loadSku = WebGUI::Asset::Sku->newBySku($session, $sku->get("sku")); +is($loadSku->getId, $sku->getId, "newBySku() works."); + +$sku->purge; + +#---------------------------------------------------------------------------- +# Cleanup +END { + +} + +1; diff --git a/t/Asset/Sku/Donation.t b/t/Asset/Sku/Donation.t new file mode 100644 index 000000000..39a28e137 --- /dev/null +++ b/t/Asset/Sku/Donation.t @@ -0,0 +1,63 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2008 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 +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# This tests WebGUI::Asset::Sku::Donation + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Asset; +use WebGUI::Asset::Sku::Donation; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 4; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# put your tests here +my $root = WebGUI::Asset->getRoot($session); +my $sku = $root->addChild({ + className=>"WebGUI::Asset::Sku::Donation", + title=>"Test Donation", + defaultPrice => 50.00, + }); +isa_ok($sku, "WebGUI::Asset::Sku::Donation"); + +is($sku->getPrice, 50.00, "Price should be 50.00"); + +$sku->applyOptions({ + price => 200.00 + }); +is($sku->getPrice, 200.00, "Price should be 200.00"); + +is($sku->getConfiguredTitle, "Test Donation (200)", "getConfiguredTitle()"); + +$sku->purge; + + +#---------------------------------------------------------------------------- +# Cleanup +END { + +} + +1; diff --git a/t/Macro/ViewCart.t b/t/Macro/ViewCart.t new file mode 100644 index 000000000..f4e6b36a4 --- /dev/null +++ b/t/Macro/ViewCart.t @@ -0,0 +1,91 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2008 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 HTML::TokeParser; +use Data::Dumper; + +use Test::More; # increment this value for each test you create + +my $session = WebGUI::Test->session; + + + +my @testSets = ( + { + comment => 'default', + label => q!!, + output => '
View Cart',
+ },
+ {
+ comment => 'custom text',
+ label => q!A Rock Hammer!,
+ output => '
A Rock Hammer',
+ },
+);
+
+my $numTests = 0;
+foreach my $testSet (@testSets) {
+ $numTests += 1 + (ref $testSet->{output} eq 'CODE');
+}
+
+$numTests += 1; #For the use_ok
+
+plan tests => $numTests;
+
+my $macro = 'WebGUI::Macro::ViewCart';
+my $loaded = use_ok($macro);
+
+SKIP: {
+
+skip "Unable to load $macro", $numTests-1 unless $loaded;
+
+foreach my $testSet (@testSets) {
+ my $output = WebGUI::Macro::ViewCart::process( $session, $testSet->{label});
+ if (ref $testSet->{output} eq 'CODE') {
+ my ($url, $label) = $testSet->{output}->($output);
+ is($label, $testSet->{label}, $testSet->{comment}.", label");
+ is($url, $testSet->{url}, $testSet->{comment}.", url");
+ }
+ else {
+ is($output, $testSet->{output}, $testSet->{comment});
+ }
+}
+
+}
+
+
+sub simpleHTMLParser {
+ my ($text) = @_;
+ my $p = HTML::TokeParser->new(\$text);
+
+ my $token = $p->get_tag("a");
+ my $url = $token->[1]{href} || "-";
+ my $label = $p->get_trimmed_text("/a");
+
+ return ($url, $label);
+}
+
+sub simpleTextParser {
+ my ($text) = @_;
+
+ my ($url) = $text =~ /^HREF=(.+)$/m;
+ my ($label) = $text =~ /^LABEL=(.+)$/m;
+
+ return ($url, $label);
+}
+
+END {
+}
diff --git a/t/SQL.t b/t/SQL.t
index c6a47bdf0..1a591e85a 100644
--- a/t/SQL.t
+++ b/t/SQL.t
@@ -17,7 +17,7 @@ use WebGUI::Session;
use Data::Dumper;
use Test::Deep;
-use Test::More tests => 52; # increment this value for each test you create
+use Test::More tests => 53; # increment this value for each test you create
my $session = WebGUI::Test->session;
@@ -263,6 +263,27 @@ $hrefHref = $session->db->buildHashRefOfHashRefs('select message, myIndex from t
grep { $_->[2] eq 'B' } @tableData;
cmp_deeply($hrefHref, \%expected, 'buildHashRefOfHashRefs, 2 columns, 1 param');
+#######################################################################
+#
+# buildDataTableStructure
+#
+# Uses the testTable data from the preceeding *RefOf*Ref tests above
+#
+#######################################################################
+
+my %tableStruct = $session->db->buildDataTableStructure('select * from testTable');
+
+my @hashedTableData = map { { myIndex=>$_->[0], message=>$_->[1], myKey=>$_->[2]} } @tableData;
+
+cmp_deeply(
+ \%tableStruct,
+ {
+ totalRecords => 8,
+ recordsReturned => 8,
+ records => \@hashedTableData,
+ },
+ 'Check table structure',
+);
END: {
$session->db->dbh->do('DROP TABLE IF EXISTS testTable');
diff --git a/t/Shop/Address.t b/t/Shop/Address.t
new file mode 100644
index 000000000..89a98d68d
--- /dev/null
+++ b/t/Shop/Address.t
@@ -0,0 +1,249 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use Exception::Class;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Shop::AddressBook;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 27;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $loaded = use_ok('WebGUI::Shop::Address');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::Address', $tests unless $loaded;
+my $e;
+my $address;
+
+#######################################################################
+#
+# create
+#
+#######################################################################
+
+eval { $address = WebGUI::Shop::Address->create(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidObject', 'create takes exception to not giving it an address book');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an address book.',
+ expected => 'WebGUI::Shop::AddressBook',
+ got => '',
+ param => undef,
+ ),
+ 'create takes exception to not giving it address book',
+);
+
+eval { $address = WebGUI::Shop::Address->create($session); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidObject', 'create takes exception to not giving it a session variable');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an address book.',
+ expected => 'WebGUI::Shop::AddressBook',
+ got => 'WebGUI::Session',
+ param => $session,
+ ),
+ 'create takes exception to giving it a session variable',
+);
+
+my $book = WebGUI::Shop::AddressBook->create($session);
+
+eval { $address = WebGUI::Shop::Address->create($book); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it address data');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need a hash reference.',
+ param => undef,
+ ),
+ 'create takes exception to giving it address data',
+);
+
+$address = WebGUI::Shop::Address->create($book, {});
+isa_ok($address, 'WebGUI::Shop::Address', 'create returns an Address object with an empty hashref');
+
+#######################################################################
+#
+# addressBook
+#
+#######################################################################
+
+cmp_deeply(
+ $address->addressBook,
+ $book,
+ 'The address has a reference back to the book used to create it'
+);
+
+#######################################################################
+#
+# getId
+#
+#######################################################################
+
+ok( $session->id->valid($address->getId), 'Address has a valid GUID');
+
+#######################################################################
+#
+# get
+#
+#######################################################################
+
+ok( $session->id->valid($address->getId), 'Address has a valid GUID');
+is($address->getId, $address->get('addressId'), 'getId is an alias for get addressId');
+cmp_deeply(
+ $address->get,
+ {
+ label => undef,
+ name => undef,
+ address1 => undef,
+ address2 => undef,
+ address3 => undef,
+ city => undef,
+ state => undef,
+ country => undef,
+ code => undef,
+ phoneNumber => undef,
+ addressId => ignore(), #checked elsewhere
+ addressBookId => $book->getId,
+ },
+ 'get the whole thing and check a new, blank object'
+);
+
+my $addressGuts = $address->get();
+$addressGuts->{'label'} = 'hacked';
+is($address->get('label'), undef, 'get returns a safe copy of the hash');
+
+#######################################################################
+#
+# update
+#
+#######################################################################
+
+$address->update({ label => 'home'});
+is($address->get('label'), 'home', 'update updates the object properties cache');
+$address->update({ address1 => 'Shawshank Prison', 'state' => 'Maine'});
+is($address->get('address1'), 'Shawshank Prison', 'update updates the object properties cache for more than one key');
+is($address->get('state'), 'Maine', 'update updates the object properties cache for more than one key');
+
+#######################################################################
+#
+# new
+#
+#######################################################################
+
+eval { $address = WebGUI::Shop::Address->new(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidObject', 'new takes exception to not giving it an address book');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an address book.',
+ expected => 'WebGUI::Shop::AddressBook',
+ got => '',
+ param => ignore,
+ ),
+ 'new takes exception to not giving it address book',
+);
+
+eval { $address = WebGUI::Shop::Address->new($session); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidObject', 'new takes exception to not giving it a session variable');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an address book.',
+ expected => 'WebGUI::Shop::AddressBook',
+ got => 'WebGUI::Session',
+ param => ignore,
+ ),
+ 'new takes exception to giving it a session variable',
+);
+
+eval { $address = WebGUI::Shop::Address->new($book); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it an address to instanciate');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an addressId.',
+ param => undef,
+ ),
+ 'new takes exception to giving it an address to instanciate',
+);
+
+eval { $address = WebGUI::Shop::Address->new($book, 'neverAnId'); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::ObjectNotFound', 'new takes exception to not giving it a bad address instanciate');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Address not found.',
+ id => 'neverAnId',
+ ),
+ 'new takes exception to giving it a bad address to instanciate',
+);
+
+TODO: {
+ local $TODO = 'More tests for new';
+ ok(0, 'Make a second address book, add an address to it, then try to call a valid address from the wrong book');
+}
+
+my $addressCopy = WebGUI::Shop::Address->new($book, $address->getId);
+cmp_deeply(
+ $address,
+ $addressCopy,
+ 'new: gets an exact copy of the object from the db. Also checks that update writes to the db correctly.'
+);
+
+#######################################################################
+#
+# delete
+#
+#######################################################################
+
+$address->delete;
+my $check = $session->db->quickScalar('select count(*) from address where addressId=?',[$address->getId]);
+is( $check, 0, 'delete worked');
+
+}
+
+END: {
+ $session->db->write('delete from addressBook');
+ $session->db->write('delete from address');
+}
diff --git a/t/Shop/AddressBook.t b/t/Shop/AddressBook.t
new file mode 100644
index 000000000..7ead6a085
--- /dev/null
+++ b/t/Shop/AddressBook.t
@@ -0,0 +1,211 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use Exception::Class;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Text;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 22;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $loaded = use_ok('WebGUI::Shop::AddressBook');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::AddressBook', $tests unless $loaded;
+my $e;
+my $book;
+
+#######################################################################
+#
+# new
+#
+#######################################################################
+
+eval { $book = WebGUI::Shop::AddressBook->new(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a session object');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need a session.',
+ expected => 'WebGUI::Session',
+ got => '',
+ ),
+ 'new takes exception to not giving it a session object',
+);
+
+eval { $book = WebGUI::Shop::AddressBook->new($session); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a addressBookId');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an addressBookId.',
+ ),
+ 'new takes exception to not giving it a addressBook Id',
+);
+
+eval { $book = WebGUI::Shop::AddressBook->new($session, 'neverAGUID'); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::ObjectNotFound', 'new takes exception to not giving it an existing addressBookId');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'No such address book.',
+ id => 'neverAGUID',
+ ),
+ 'new takes exception to not giving it a addressBook Id',
+);
+
+
+#######################################################################
+#
+# create
+#
+#######################################################################
+
+eval { $book = WebGUI::Shop::AddressBook->create(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a session object');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need a session.',
+ expected => 'WebGUI::Session',
+ got => '',
+ ),
+ 'create takes exception to not giving it a session object',
+);
+
+$session->user({userId => 1});
+
+$book = WebGUI::Shop::AddressBook->create($session);
+isa_ok($book, 'WebGUI::Shop::AddressBook', 'create returns the right kind of object');
+
+isa_ok($book->session, 'WebGUI::Session', 'session method returns a session object');
+
+is($session->getId, $book->session->getId, 'session method returns OUR session object');
+
+ok($session->id->valid($book->getId), 'create makes a valid GUID style addressBookId');
+
+is(undef, $book->get('userId'), 'create does not automatically set the userId');
+
+my $bookCount = $session->db->quickScalar('select count(*) from addressBook');
+is($bookCount, 1, 'only 1 address book was created');
+
+my $alreadyHaveBook = WebGUI::Shop::AddressBook->create($session);
+is($book->getId, $alreadyHaveBook->getId, 'creating an addressbook as visitor, when you already have one, returns the one already created');
+
+#######################################################################
+#
+# getId
+#
+#######################################################################
+
+is($book->getId, $book->get('addressBookId'), 'getId is a shortcut for ->get');
+
+#######################################################################
+#
+# addAddress
+#
+#######################################################################
+
+my $address1 = $book->addAddress({ label => q{Red's cell} });
+isa_ok($address1, 'WebGUI::Shop::Address', 'addAddress returns an object');
+
+my $address2 = $book->addAddress({ label => q{Norton's office} });
+
+#######################################################################
+#
+# getAddresses
+#
+#######################################################################
+
+my @addresses = @{ $book->getAddresses() };
+
+cmp_deeply(
+ \@addresses,
+ [$address1, $address2],
+ 'getAddresses returns all address objects for this book'
+);
+
+#######################################################################
+#
+# update
+#
+#######################################################################
+
+$book->update({ lastShipId => $address1->getId, lastPayId => $address2->getId});
+
+cmp_deeply(
+ $book->get(),
+ {
+ userId => ignore,
+ sessionId => ignore,
+ addressBookId => ignore,
+ lastShipId => $address1->getId,
+ lastPayId => $address2->getId,
+ },
+ 'update updates the object properties cache'
+);
+
+my $bookClone = WebGUI::Shop::AddressBook->new($session, $book->getId);
+
+cmp_deeply(
+ $bookClone,
+ $book,
+ 'update updates the db, too'
+);
+
+#######################################################################
+#
+# delete
+#
+#######################################################################
+
+$bookClone->delete();
+$bookCount = $session->db->quickScalar('select count(*) from addressBook');
+my $addrCount = $session->db->quickScalar('select count(*) from address');
+
+is($bookCount, 0, 'delete: book deleted');
+is($addrCount, 0, 'delete: also deletes addresses in the book');
+undef $book;
+
+}
+
+END: {
+ $session->db->write('delete from addressBook');
+ $session->db->write('delete from address');
+}
diff --git a/t/Shop/Cart.t b/t/Shop/Cart.t
new file mode 100644
index 000000000..ba8f088a3
--- /dev/null
+++ b/t/Shop/Cart.t
@@ -0,0 +1,102 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Asset;
+use WebGUI::Shop::Cart;
+use WebGUI::TestException;
+
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+
+#----------------------------------------------------------------------------
+# Tests
+
+plan tests => 20; # Increment this number for each test you create
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+throws_deeply ( sub { my $cart = WebGUI::Shop::Cart->getCartBySession(); },
+ 'WebGUI::Error::InvalidObject',
+ {
+ error => 'Need a session.',
+ got => '',
+ expected => 'WebGUI::Session',
+ },
+ 'newBySession takes an exception to not giving it a session variable'
+);
+
+my $cart = WebGUI::Shop::Cart->getCartBySession($session);
+
+isa_ok($cart, "WebGUI::Shop::Cart");
+isa_ok($cart->session, "WebGUI::Session");
+
+my $root = WebGUI::Asset->getRoot($session);
+my $product = $root->addChild({
+ className=>"WebGUI::Asset::Sku::Donation",
+ title=>"Test Product",
+ });
+$product->applyOptions({price=>50.25});
+my $item = $cart->addItem($product);
+isa_ok($item, "WebGUI::Shop::CartItem");
+isa_ok($item->cart, "WebGUI::Shop::Cart", "Does the item have a cart?");
+is(ref($item->get), "HASH", "Do we have a hash of properties?");
+
+is($item->get("quantity"), 1, "Should have 1 of these in the cart.");
+is($item->adjustQuantity(2), 3, "adjustQuantity() should tell us how many items of this type are in the cart");
+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.");
+
+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.");
+is($cart->get("sessionId"), $session->getId, "Can retrieve a value from the cart properties.");
+
+is($cart->formatCurrency(11.1), "11.10", "can format currency");
+
+is($cart->calculateSubtotal, 150.75, "can determine the price of the items in the cart");
+
+$cart->update({shippingAddressId => "XXXX"});
+is($cart->get("shippingAddressId"), "XXXX", "Can set values to the cart properties.");
+
+isa_ok($cart->getAddressBook, "WebGUI::Shop::AddressBook", "can get an address book");
+
+$cart->empty;
+is($session->db->quickScalar("select count(*) from cartItem where cartId=?",[$cart->getId]), 0, "Items are removed from cart.");
+
+
+$cart->delete;
+is($cart->delete, undef, "Can destroy cart.");
+
+
+$product->purge;
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+
+}
diff --git a/t/Shop/Pay.t b/t/Shop/Pay.t
new file mode 100644
index 000000000..cff4ee13e
--- /dev/null
+++ b/t/Shop/Pay.t
@@ -0,0 +1,246 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+#use Test::Exception;
+use JSON;
+use HTML::Form;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::TestException;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 18;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $loaded = use_ok('WebGUI::Shop::Pay');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::Pay', $tests unless $loaded;
+
+#######################################################################
+#
+# new
+#
+#######################################################################
+
+my $e;
+my $pay;
+
+
+throws_deeply ( sub { $pay = WebGUI::Shop::Pay->new(); },
+ 'WebGUI::Error::InvalidObject',
+ {
+ error => 'Must provide a session variable',
+ got => '',
+ expected => 'WebGUI::Session',
+ },
+ 'new takes an exception to not giving it a session variable'
+);
+
+$pay = WebGUI::Shop::Pay->new($session);
+isa_ok($pay, 'WebGUI::Shop::Pay', 'new returned the right kind of object');
+
+#######################################################################
+#
+# session
+#
+#######################################################################
+
+isa_ok($pay->session, 'WebGUI::Session', 'session method returns a session object');
+is($session->getId, $pay->session->getId, 'session method returns OUR session object');
+
+#######################################################################
+#
+# addPaymentGateway
+#
+#######################################################################
+
+my $gateway;
+
+throws_deeply ( sub { $gateway = $pay->addPaymentGateway(); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => 'Must provide a class to create an object'
+ },
+ 'addPaymentGateway croaks without a class',
+);
+
+throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::NoSuchDriver'); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => 'The requested class is not enabled in your WebGUI configuration file',
+ param => 'WebGUI::Shop::PayDriver::NoSuchDriver',
+ },
+ 'addPaymentGateway croaks without a configured class',
+);
+
+throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash'); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => 'Must provide a label to create an object',
+ },
+ 'addPaymentGateway requires a label',
+);
+
+
+throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL'); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => 'You must pass a hashref of options to create a new PayDriver object',
+ },
+ 'addPaymentGateway croaks without options to build a object with',
+);
+
+throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL', {}); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => 'You must pass a hashref of options to create a new PayDriver object',
+ },
+ 'addPaymentGateway croaks without options to build a object with',
+);
+
+my $options = {
+ enabled => 1,
+ label => 'Cold, stone hard cash',
+};
+my $newDriver = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL', $options);
+isa_ok($newDriver, 'WebGUI::Shop::PayDriver::Cash', 'added a new, configured Cash driver');
+is($newDriver->label, 'JAL', 'label passed correctly to paydriver');
+
+
+#TODO: check if options are stored.
+
+
+#######################################################################
+#
+# getDrivers
+#
+#######################################################################
+
+my $drivers = $pay->getDrivers();
+
+my $defaultPayDrivers = {
+ 'WebGUI::Shop::PayDriver::Cash' => 'Cash',
+};
+
+cmp_deeply( $drivers, $defaultPayDrivers, 'getDrivers returns the default PayDrivers');
+
+#######################################################################
+#
+# getOptions
+#
+#######################################################################
+
+throws_deeply( sub { $drivers = $pay->getOptions(); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => 'Need a cart.',
+ },
+ 'getOptions takes exception to not giving it a cart',
+);
+
+#TODO: Check th crap getOptions returns
+
+#######################################################################
+#
+# getPaymentGateway
+#
+#######################################################################
+
+throws_deeply( sub { $gateway = $pay->getPaymentGateway(); },
+ 'WebGUI::Error::InvalidParam',
+ {
+ error => q{Must provide a paymentGatewayId},
+ },
+ 'getPaymentGateway throws exception without paymentGatewayId',
+);
+
+throws_deeply( sub { $gateway = $pay->getPaymentGateway('NoSuchThing'); },
+ 'WebGUI::Error::ObjectNotFound',
+ {
+ error => q{payment gateway not found in db},
+ id => 'NoSuchThing',
+ },
+ 'getPaymentGateway throws exception when called with a non-existant paymentGatewayId',
+);
+
+$gateway = $pay->getPaymentGateway( $newDriver->getId );
+isa_ok($gateway, 'WebGUI::Shop::PayDriver::Cash', 'returned payment gateway has correct class');
+is($gateway->getId, $newDriver->getId, 'getPaymentGateway instantiated the requested driver');
+
+#######################################################################
+#
+# getPaymentGateways
+#
+#######################################################################
+
+# Create an extra driver for testing purposes
+my $otherOptions = {
+ enabled => 1,
+ label => 'Even harder cash',
+};
+my $anotherDriver = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'Pomade', $otherOptions);
+
+my $gateways = $pay->getPaymentGateways;
+my @returnedIds = map {$_->getId} @{ $gateways };
+cmp_bag(
+ \@returnedIds,
+ [
+ $newDriver->getId,
+ $anotherDriver->getId,
+ ],
+ 'getPaymentGateways returns all create payment drivers',
+);
+
+#######################################################################
+#
+# www_do
+#
+#######################################################################
+
+
+
+#######################################################################
+#
+# www_manage
+#
+#######################################################################
+
+
+}
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ $session->db->write('delete from paymentGateway');
+}
diff --git a/t/Shop/PayDriver.t b/t/Shop/PayDriver.t
new file mode 100644
index 000000000..e2393e168
--- /dev/null
+++ b/t/Shop/PayDriver.t
@@ -0,0 +1,472 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use JSON;
+use HTML::Form;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 46;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# figure out if the test can actually run
+
+my $e;
+
+my $loaded = use_ok('WebGUI::Shop::PayDriver');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::PayDriver', $tests unless $loaded;
+
+#######################################################################
+#
+# definition
+#
+#######################################################################
+
+my $definition;
+
+eval { $definition = WebGUI::Shop::PayDriver->definition(); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'definition takes an exception to not giving it a session variable');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'definition: requires a session variable',
+);
+
+$definition = WebGUI::Shop::PayDriver->definition($session);
+
+cmp_deeply (
+ $definition,
+ [ {
+ name => 'Payment Driver',
+ properties => {
+ label => {
+ fieldType => 'text',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => "Credit Card",
+ },
+ enabled => {
+ fieldType => 'yesNo',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 1,
+ },
+ groupToUse => {
+ fieldType => 'group',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 1,
+ },
+ receiptMessage => {
+ fieldType => 'text',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => undef,
+ },
+ }
+ } ],
+ ,
+ 'Definition returns an array of hashrefs',
+);
+
+$definition = WebGUI::Shop::PayDriver->definition($session, [ { name => 'Red' }]);
+
+cmp_deeply (
+ $definition,
+ [
+ {
+ name => 'Red',
+ },
+ {
+ name => 'Payment Driver',
+ properties => ignore(),
+ }
+ ],
+ ,
+ 'New data is appended correctly',
+);
+
+#######################################################################
+#
+# create
+#
+#######################################################################
+
+my $driver;
+
+# Test incorrect for parameters
+
+eval { $driver = WebGUI::Shop::PayDriver->create(); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a session object');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'create takes exception to not giving it a session object',
+);
+
+eval { $driver = WebGUI::Shop::PayDriver->create($session); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a label');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a human readable label in the hashref of options',
+ ),
+ 'create takes exception to not giving it a hashref of options',
+);
+
+eval { $driver = WebGUI::Shop::PayDriver->create($session, 'Very human readable label'); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a hashref of options');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a hashref of options',
+ ),
+ 'create takes exception to not giving it a hashref of options',
+);
+
+eval { $driver = WebGUI::Shop::PayDriver->create($session, 'Very human readable label', {}); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it an empty hashref of options');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a hashref of options',
+ ),
+ 'create takes exception to not giving it an empty hashref of options',
+);
+
+# Test functionality
+
+my $label = 'Human Readable Label';
+my $options = {
+ label => 'Fast and harmless',
+ enabled => 1,
+ group => 3,
+ receiptMessage => 'Pannenkoeken zijn nog lekkerder met spek',
+};
+
+$driver = WebGUI::Shop::PayDriver->create( $session, $label, $options );
+
+isa_ok ($driver, 'WebGUI::Shop::PayDriver', 'create creates WebGUI::Shop::PayDriver object');
+
+my $dbData = $session->db->quickHashRef('select * from paymentGateway where paymentGatewayId=?', [ $driver->getId ]);
+
+#diag ($driver->getId);
+cmp_deeply (
+ $dbData,
+ {
+ paymentGatewayId => $driver->getId,
+ className => ref $driver,
+ label => $driver->label,
+ options => q|{"group":3,"receiptMessage":"Pannenkoeken zijn nog lekkerder met spek","label":"Fast and harmless","enabled":1}|,
+ },
+ 'Correct data written to the db',
+);
+
+
+
+
+#######################################################################
+#
+# session
+#
+#######################################################################
+
+isa_ok ($driver->session, 'WebGUI::Session', 'session method returns a session object');
+is ($session->getId, $driver->session->getId, 'session method returns OUR session object');
+
+#######################################################################
+#
+# paymentGatewayId, getId
+#
+#######################################################################
+
+like ($driver->paymentGatewayId, $session->id->getValidator, 'got a valid GUID for paymentGatewayId');
+is ($driver->getId, $driver->paymentGatewayId, 'getId returns the same thing as paymentGatewayId');
+
+#######################################################################
+#
+# className
+#
+#######################################################################
+
+is ($driver->className, ref $driver, 'className property set correctly');
+
+#######################################################################
+#
+# options
+#
+#######################################################################
+
+cmp_deeply ($driver->options, $options, 'options accessor works');
+
+#######################################################################
+#
+# getName
+#
+#######################################################################
+
+eval { WebGUI::Shop::PayDriver->getName(); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'getName requires a session object passed to it');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'getName requires a session object passed to it',
+);
+
+is (WebGUI::Shop::PayDriver->getName($session), 'Payment Driver', 'getName returns the human readable name of this driver');
+
+#######################################################################
+#
+# get
+#
+#######################################################################
+
+cmp_deeply ($driver->get, $driver->options, 'get works like the options method with no param passed');
+is ($driver->get('enabled'), 1, 'get the enabled entry from the options');
+is ($driver->get('label'), 'Fast and harmless', 'get the label entry from the options');
+
+my $optionsCopy = $driver->get;
+$optionsCopy->{label} = 'And now for something completely different';
+isnt ($driver->get('label'), 'And now for something completely different',
+ 'hashref returned by get() is a copy of the internal hashref');
+
+#######################################################################
+#
+# getCart
+#
+#######################################################################
+
+my $cart = $driver->getCart;
+isa_ok ($cart, 'WebGUI::Shop::Cart', 'getCart returns an instantiated WebGUI::Shop::Cart object');
+
+#######################################################################
+#
+# getEditForm
+#
+#######################################################################
+
+my $form = $driver->getEditForm;
+
+isa_ok ($form, 'WebGUI::HTMLForm', 'getEditForm returns an HTMLForm object');
+
+my $html = $form->print;
+
+##Any URL is fine, really
+my @forms = HTML::Form->parse($html, 'http://www.webgui.org');
+is (scalar @forms, 1, 'getEditForm generates just 1 form');
+
+my @inputs = $forms[0]->inputs;
+is (scalar @inputs, 10, 'getEditForm: the form has 10 controls');
+
+my @interestingFeatures;
+foreach my $input (@inputs) {
+ my $name = $input->name;
+ my $type = $input->type;
+ push @interestingFeatures, { name => $name, type => $type };
+}
+
+cmp_deeply(
+ \@interestingFeatures,
+ [
+ {
+ name => undef,
+ type => 'submit',
+ },
+ {
+ name => 'shop',
+ type => 'hidden',
+ },
+ {
+ name => 'method',
+ type => 'hidden',
+ },
+ {
+ name => 'do',
+ type => 'hidden',
+ },
+ {
+ name => 'paymentGatewayId',
+ type => 'hidden',
+ },
+ {
+ name => 'className',
+ type => 'hidden',
+ },
+ {
+ name => 'label',
+ type => 'text',
+ },
+ {
+ name => 'enabled',
+ type => 'radio',
+ },
+ {
+ name => 'groupToUse',
+ type => 'option',
+ },
+ {
+ name => 'receiptMessage',
+ type => 'text',
+ },
+ ],
+ 'getEditForm made the correct form with all the elements'
+
+);
+
+
+#######################################################################
+#
+# new
+#
+#######################################################################
+
+my $oldDriver;
+
+eval { $oldDriver = WebGUI::Shop::PayDriver->new(); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a session object');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'new takes exception to not giving it a session object',
+);
+
+eval { $oldDriver = WebGUI::Shop::PayDriver->new($session); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a paymentGatewayId');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'Must provide a paymentGatewayId',
+ ),
+ 'new takes exception to not giving it a paymentGatewayId',
+);
+
+eval { $oldDriver = WebGUI::Shop::PayDriver->new($session, 'notEverAnId'); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::ObjectNotFound', 'new croaks unless the requested paymentGatewayId object exists in the db');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'paymentGatewayId not found in db',
+ id => 'notEverAnId',
+ ),
+ 'new croaks unless the requested paymentGatewayId object exists in the db',
+);
+
+my $driverCopy = WebGUI::Shop::PayDriver->new($session, $driver->getId);
+
+is ($driver->getId, $driverCopy->getId, 'same id');
+is ($driver->className, $driverCopy->className, 'same className');
+cmp_deeply ($driver->options, $driverCopy->options, 'same options');
+
+TODO: {
+ local $TODO = 'tests for new';
+ ok(0, 'Test broken options in the db');
+}
+
+#######################################################################
+#
+# update
+#
+#######################################################################
+
+eval { $driver->update(); };
+$e = Exception::Class->caught();
+isa_ok ($e, 'WebGUI::Error::InvalidParam', 'update takes exception to not giving it a hashref of options');
+cmp_deeply (
+ $e,
+ methods(
+ error => 'update was not sent a hashref of options to store in the database',
+ ),
+ 'update takes exception to not giving it a hashref of options',
+);
+
+my $newOptions = {
+ label => 'Yet another label',
+ enabled => 0,
+ group => 4,
+ receiptMessage => 'Dropjes!',
+};
+
+$driver->update($newOptions);
+my $storedOptions = $session->db->quickScalar('select options from paymentGateway where paymentGatewayId=?', [
+ $driver->getId,
+]);
+cmp_deeply(
+ $newOptions,
+ from_json($storedOptions),
+ ,
+ 'update() actually stores data',
+);
+
+
+#######################################################################
+#
+# delete
+#
+#######################################################################
+
+$driver->delete;
+
+my $count = $session->db->quickScalar('select count(*) from paymentGateway where paymentGatewayId=?', [
+ $driver->paymentGatewayId
+]);
+
+is ($count, 0, 'delete deleted the object');
+
+undef $driver;
+
+
+}
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ $session->db->write('delete from paymentGateway');
+}
diff --git a/t/Shop/Ship.t b/t/Shop/Ship.t
new file mode 100644
index 000000000..51a50cf77
--- /dev/null
+++ b/t/Shop/Ship.t
@@ -0,0 +1,191 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use JSON;
+use HTML::Form;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 19;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $loaded = use_ok('WebGUI::Shop::Ship');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::Ship', $tests unless $loaded;
+
+#######################################################################
+#
+# new
+#
+#######################################################################
+
+my $e;
+my $ship;
+
+eval { $ship = WebGUI::Shop::Ship->new(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes an exception to not giving it a session variable');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ got => '',
+ expected => 'WebGUI::Session',
+ ),
+ 'new: requires a session variable',
+);
+
+$ship = WebGUI::Shop::Ship->new($session);
+isa_ok($ship, 'WebGUI::Shop::Ship', 'new returned the right kind of object');
+
+isa_ok($ship->session, 'WebGUI::Session', 'session method returns a session object');
+
+is($session->getId, $ship->session->getId, 'session method returns OUR session object');
+
+#######################################################################
+#
+# getDrivers
+#
+#######################################################################
+
+my $drivers;
+
+$drivers = $ship->getDrivers();
+my @driverClasses = keys %{$drivers};
+cmp_deeply(
+ \@driverClasses,
+ [ 'WebGUI::Shop::ShipDriver::FlatRate' ],
+ 'getDrivers: WebGUI only ships with 1 default shipping driver',
+);
+
+#######################################################################
+#
+# addShipper
+#
+#######################################################################
+
+my $shipper;
+
+eval { $shipper = $ship->addShipper(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'addShipper croaks without a class');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a class to create an object',
+ ),
+ 'addShipper croaks without a class',
+);
+
+eval { $shipper = $ship->addShipper('WebGUI::Shop::ShipDriver::FreeShipping'); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'addShipper croaks without a configured class');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'The requested class is not enabled in your WebGUI configuration file',
+ param => 'WebGUI::Shop::ShipDriver::FreeShipping',
+ ),
+ 'addShipper croaks without a configured class',
+);
+
+eval { $shipper = $ship->addShipper('WebGUI::Shop::ShipDriver::FlatRate'); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'addShipper croaks without options to build a object with');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'You must pass a hashref of options to create a new ShipDriver object',
+ ),
+ 'addShipper croaks without options to build a object with',
+);
+
+eval { $shipper = $ship->addShipper('WebGUI::Shop::ShipDriver::FlatRate', {}); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'addShipper croaks without options to build a object with');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'You must pass a hashref of options to create a new ShipDriver object',
+ ),
+ 'addShipper croaks without options to build a object with',
+);
+
+my $driver = $ship->addShipper('WebGUI::Shop::ShipDriver::FlatRate', { enabled=>1, label=>q{Jake's Jailbird Airmail}});
+isa_ok($driver, 'WebGUI::Shop::ShipDriver::FlatRate', 'added a new, configured FlatRate driver');
+
+#######################################################################
+#
+# getShippers
+#
+#######################################################################
+
+my $shippers;
+my $driver2 = $ship->addShipper('WebGUI::Shop::ShipDriver::FlatRate', { enabled=>1, label=>q{Tommy's cut-rate shipping}});
+
+$shippers = $ship->getShippers();
+is(scalar @{$shippers}, 2, 'getShippers: got both shippers');
+
+my @shipperNames = map { $_->get("label") } @{ $shippers };
+cmp_bag(
+ \@shipperNames,
+ [q{Jake's Jailbird Airmail},q{Tommy's cut-rate shipping}],
+ 'Returned shippers have the right data'
+);
+
+#######################################################################
+#
+# getOptions
+#
+#######################################################################
+
+eval { $shippers = $ship->getOptions(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'getOptions takes exception to not giving it a cart');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Need a cart.',
+ ),
+ 'getOptions takes exception to not giving it a cart',
+);
+
+}
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ $session->db->write('delete from shipper');
+}
diff --git a/t/Shop/ShipDriver.t b/t/Shop/ShipDriver.t
new file mode 100644
index 000000000..2b06a3931
--- /dev/null
+++ b/t/Shop/ShipDriver.t
@@ -0,0 +1,358 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use JSON;
+use HTML::Form;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 35;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $e;
+
+my $loaded = use_ok('WebGUI::Shop::ShipDriver');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::ShipDriver', $tests unless $loaded;
+
+#######################################################################
+#
+# definition
+#
+#######################################################################
+
+my $definition;
+
+eval { $definition = WebGUI::Shop::ShipDriver->definition(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'definition takes an exception to not giving it a session variable');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'definition: requires a session variable',
+);
+
+$definition = WebGUI::Shop::ShipDriver->definition($session);
+
+cmp_deeply(
+ $definition,
+ [ {
+ name => 'Shipper Driver',
+ properties => {
+ label => {
+ fieldType => 'text',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => undef,
+ },
+ enabled => {
+ fieldType => 'yesNo',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 1,
+ }
+ }
+ } ],
+ ,
+ 'Definition returns an array of hashrefs',
+);
+
+$definition = WebGUI::Shop::ShipDriver->definition($session, [ { name => 'Red' }]);
+
+cmp_deeply(
+ $definition,
+ [
+ {
+ name => 'Red',
+ },
+ {
+ name => 'Shipper Driver',
+ properties => ignore(),
+ }
+ ],
+ ,
+ 'New data is appended correctly',
+);
+
+#######################################################################
+#
+# create
+#
+#######################################################################
+
+my $driver;
+
+eval { $driver = WebGUI::Shop::ShipDriver->create(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a session object');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'create takes exception to not giving it a session object',
+);
+
+eval { $driver = WebGUI::Shop::ShipDriver->create($session); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it a hashref of options');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a hashref of options',
+ ),
+ 'create takes exception to not giving it a hashref of options',
+);
+
+
+eval { $driver = WebGUI::Shop::ShipDriver->create($session, {}); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'create takes exception to not giving it an empty hashref of options');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a hashref of options',
+ ),
+ 'create takes exception to not giving it an empty hashref of options',
+);
+
+my $options = {
+ label => 'Slow and dangerous',
+ enabled => 1,
+ };
+
+$driver = WebGUI::Shop::ShipDriver->create( $session, $options );
+
+isa_ok($driver, 'WebGUI::Shop::ShipDriver');
+
+isa_ok($driver->session, 'WebGUI::Session', 'session method returns a session object');
+
+is($session->getId, $driver->session->getId, 'session method returns OUR session object');
+
+like($driver->getId, $session->id->getValidator, 'got a valid GUID for shipperId');
+
+
+cmp_deeply($driver->get, $options, 'options accessor works');
+
+my $dbData = $session->db->quickHashRef('select * from shipper where shipperId=?',[$driver->getId]);
+cmp_deeply(
+ $dbData,
+ {
+ shipperId => $driver->getId,
+ className => ref($driver),
+ options => q|{"label":"Slow and dangerous","enabled":1}|,
+ },
+ 'Correct data written to the db',
+);
+
+#######################################################################
+#
+# getName
+#
+#######################################################################
+
+is (WebGUI::Shop::ShipDriver->getName($session), 'Shipper Driver', 'getName returns the human readable name of this driver');
+
+#######################################################################
+#
+# get
+#
+#######################################################################
+
+is($driver->get('enabled'), 1, 'get the enabled entry from the options');
+is($driver->get('label'), 'Slow and dangerous', 'get the label entry from the options');
+
+#######################################################################
+#
+# getEditForm
+#
+#######################################################################
+
+my $form = $driver->getEditForm;
+
+isa_ok($form, 'WebGUI::HTMLForm', 'getEditForm returns an HTMLForm object');
+
+my $html = $form->print;
+
+##Any URL is fine, really
+my @forms = HTML::Form->parse($html, 'http://www.webgui.org');
+is (scalar @forms, 1, 'getEditForm generates just 1 form');
+
+my @inputs = $forms[0]->inputs;
+is (scalar @inputs, 7, 'getEditForm: the form has 7 controls');
+
+my @interestingFeatures;
+foreach my $input (@inputs) {
+ my $name = $input->name;
+ my $type = $input->type;
+ push @interestingFeatures, { name => $name, type => $type };
+}
+
+cmp_deeply(
+ \@interestingFeatures,
+ [
+ {
+ name => undef,
+ type => 'submit',
+ },
+ {
+ name => 'driverId',
+ type => 'hidden',
+ },
+ {
+ name => 'shop',
+ type => 'hidden',
+ },
+ {
+ name => 'method',
+ type => 'hidden',
+ },
+ {
+ name => 'do',
+ type => 'hidden',
+ },
+ {
+ name => 'label',
+ type => 'text',
+ },
+ {
+ name => 'enabled',
+ type => 'radio',
+ },
+ ],
+ 'getEditForm made the correct form with all the elements'
+
+);
+
+
+#######################################################################
+#
+# new
+#
+#######################################################################
+
+my $oldDriver;
+
+eval { $oldDriver = WebGUI::Shop::ShipDriver->new(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a session object');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'new takes exception to not giving it a session object',
+);
+
+eval { $oldDriver = WebGUI::Shop::ShipDriver->new($session); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes exception to not giving it a shipperId');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a shipperId',
+ ),
+ 'new takes exception to not giving it a shipperId',
+);
+
+eval { $oldDriver = WebGUI::Shop::ShipDriver->new($session, 'notEverAnId'); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::ObjectNotFound', 'new croaks unless the requested shipperId object exists in the db');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'shipperId not found in db',
+ id => 'notEverAnId',
+ ),
+ 'new croaks unless the requested shipperId object exists in the db',
+);
+
+my $driverCopy = WebGUI::Shop::ShipDriver->new($session, $driver->getId);
+
+is($driver->getId, $driverCopy->getId, 'same id');
+is(ref $driver, ref $driverCopy, 'same className');
+cmp_deeply($driver->get, $driverCopy->get, 'same options');
+
+
+
+#######################################################################
+#
+# calculate
+#
+#######################################################################
+
+eval { $driver->calculate; };
+like ($@, qr/^You must override the calculate method/, 'calculate croaks to force overriding it in the child classes');
+
+#######################################################################
+#
+# set
+#
+#######################################################################
+
+eval { $driver->update(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'update takes exception to not giving it a hashref of options');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'update was not sent a hashref of options to store in the database',
+ ),
+ 'update takes exception to not giving it a hashref of options',
+);
+
+#######################################################################
+#
+# delete
+#
+#######################################################################
+
+#$driver->delete;
+#
+#my $count = $session->db->quickScalar('select count(*) from shipper where shipperId=?',[$driver->shipperId]);
+#is($count, 0, 'delete deleted the object');
+#
+#undef $driver;
+
+
+}
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ #$session->db->write('delete from shipper');
+}
diff --git a/t/Shop/ShipDriver/FlatRate.t b/t/Shop/ShipDriver/FlatRate.t
new file mode 100644
index 000000000..b158f65ec
--- /dev/null
+++ b/t/Shop/ShipDriver/FlatRate.t
@@ -0,0 +1,256 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../../lib";
+use Test::More;
+use Test::Deep;
+use JSON;
+use HTML::Form;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $tests = 11;
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $loaded = use_ok('WebGUI::Shop::ShipDriver::FlatRate');
+
+my $storage;
+
+SKIP: {
+
+skip 'Unable to load module WebGUI::Shop::ShipDriver::FlatRate', $tests unless $loaded;
+
+#######################################################################
+#
+# definition
+#
+#######################################################################
+
+my $definition;
+my $e; ##Exception variable, used throughout the file
+
+eval { $definition = WebGUI::Shop::ShipDriver::FlatRate->definition(); };
+$e = Exception::Class->caught();
+isa_ok($e, 'WebGUI::Error::InvalidParam', 'definition takes an exception to not giving it a session variable');
+cmp_deeply(
+ $e,
+ methods(
+ error => 'Must provide a session variable',
+ ),
+ 'definition: requires a session variable',
+);
+
+
+$definition = WebGUI::Shop::ShipDriver::FlatRate->definition($session);
+
+cmp_deeply(
+ $definition,
+ [ {
+ name => 'Flat Rate',
+ properties => {
+ flatFee => {
+ fieldType => 'float',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 0,
+ },
+ percentageOfPrice => {
+ fieldType => 'float',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 0,
+ },
+ pricePerWeight => {
+ fieldType => 'float',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 0,
+ },
+ pricePerItem => {
+ fieldType => 'float',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 0,
+ },
+ }
+ },
+ {
+ name => 'Shipper Driver',
+ properties => {
+ label => {
+ fieldType => 'text',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => undef,
+ },
+ enabled => {
+ fieldType => 'yesNo',
+ label => ignore(),
+ hoverHelp => ignore(),
+ defaultValue => 1,
+ },
+ }
+ } ],
+ 'Definition returns an array of hashrefs',
+);
+
+#######################################################################
+#
+# create
+#
+#######################################################################
+
+my $driver;
+
+my $options = {
+ label => 'flat rate, ship weight, items in the cart',
+ enabled => 1,
+ flatFee => 1.00,
+ percentageOfPrice => 5,
+ pricePerWeight => 0.5,
+ pricePerItem => 0.1,
+ };
+
+$driver = WebGUI::Shop::ShipDriver::FlatRate->create($session, $options);
+
+isa_ok($driver, 'WebGUI::Shop::ShipDriver::FlatRate');
+
+isa_ok($driver, 'WebGUI::Shop::ShipDriver');
+
+#######################################################################
+#
+# getName
+#
+#######################################################################
+
+is (WebGUI::Shop::ShipDriver::FlatRate->getName($session), 'Flat Rate', 'getName returns the human readable name of this driver');
+
+#######################################################################
+#
+# getEditForm
+#
+#######################################################################
+
+my $form = $driver->getEditForm;
+
+isa_ok($form, 'WebGUI::HTMLForm', 'getEditForm returns an HTMLForm object');
+
+my $html = $form->print;
+
+##Any URL is fine, really
+my @forms = HTML::Form->parse($html, 'http://www.webgui.org');
+is (scalar @forms, 1, 'getEditForm generates just 1 form');
+
+my @inputs = $forms[0]->inputs;
+is (scalar @inputs, 11, 'getEditForm: the form has 11 controls');
+
+my @interestingFeatures;
+foreach my $input (@inputs) {
+ my $name = $input->name;
+ my $type = $input->type;
+ push @interestingFeatures, { name => $name, type => $type };
+}
+
+cmp_deeply(
+ \@interestingFeatures,
+ [
+ {
+ name => undef,
+ type => 'submit',
+ },
+ {
+ name => 'driverId',
+ type => 'hidden',
+ },
+ {
+ name => 'shop',
+ type => 'hidden',
+ },
+ {
+ name => 'method',
+ type => 'hidden',
+ },
+ {
+ name => 'do',
+ type => 'hidden',
+ },
+ {
+ name => 'label',
+ type => 'text',
+ },
+ {
+ name => 'enabled',
+ type => 'radio',
+ },
+ {
+ name => 'flatFee',
+ type => 'text',
+ },
+ {
+ name => 'percentageOfPrice',
+ type => 'text',
+ },
+ {
+ name => 'pricePerWeight',
+ type => 'text',
+ },
+ {
+ name => 'pricePerItem',
+ type => 'text',
+ },
+ ],
+ 'getEditForm made the correct form with all the elements'
+
+);
+
+#######################################################################
+#
+# delete
+#
+#######################################################################
+
+$driver->delete;
+
+my $count = $session->db->quickScalar('select count(*) from shipper where shipperId=?',[$driver->getId]);
+is($count, 0, 'delete deleted the object');
+
+undef $driver;
+
+#######################################################################
+#
+# calculate
+#
+#######################################################################
+
+}
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ $session->db->write('delete from shipper');
+}
diff --git a/t/Shop/Tax.t b/t/Shop/Tax.t
new file mode 100644
index 000000000..7f1ec6d61
--- /dev/null
+++ b/t/Shop/Tax.t
@@ -0,0 +1,690 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use Exception::Class;
+use Data::Dumper;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Text;
+use WebGUI::Shop::Cart;
+use WebGUI::Shop::AddressBook;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# Tests
+
+my $addExceptions = getAddExceptions($session);
+
+my $tests = 79 + 2*scalar(@{$addExceptions});
+plan tests => 1 + $tests;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $loaded = use_ok('WebGUI::Shop::Tax');
+
+my $storage;
+
+SKIP: {
+
+ skip 'Unable to load module WebGUI::Shop::Tax', $tests unless $loaded;
+
+ #######################################################################
+ #
+ # new
+ #
+ #######################################################################
+
+ my $taxer = WebGUI::Shop::Tax->new($session);
+
+ isa_ok($taxer, 'WebGUI::Shop::Tax');
+
+ isa_ok($taxer->session, 'WebGUI::Session', 'session method returns a session object');
+
+ is($session->getId, $taxer->session->getId, 'session method returns OUR session object');
+
+ #######################################################################
+ #
+ # getItems
+ #
+ #######################################################################
+
+ my $taxIterator = $taxer->getItems;
+
+ isa_ok($taxIterator, 'WebGUI::SQL::ResultSet');
+
+ is($taxIterator->rows, 0, 'WebGUI ships with no predefined tax data');
+
+ #######################################################################
+ #
+ # add
+ #
+ #######################################################################
+
+ my $e;
+
+ eval{$taxer->add()};
+
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'add: correct type of exception thrown for missing hashref');
+ is($e->error, 'Must pass in a hashref of params', 'add: correct message for a missing hashref');
+
+ foreach my $inputSet ( @{ $addExceptions } ){
+ eval{$taxer->add($inputSet->{args})};
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'add: '.$inputSet->{comment});
+ cmp_deeply(
+ $e,
+ methods(
+ error => $inputSet->{error},
+ param => $inputSet->{param},
+ ),
+ 'add: '.$inputSet->{comment},
+ );
+ }
+
+ my $taxData = {
+ country => 'USA',
+ state => 'OR',
+ taxRate => '0',
+ };
+
+ my $oregonTaxId = $taxer->add($taxData);
+
+ ok($session->id->valid($oregonTaxId), 'add method returns a valid GUID');
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 1, 'add added only 1 row to the tax table');
+
+ my $addedData = $taxIterator->hashRef;
+ $taxData->{taxId} = $oregonTaxId;
+ $taxData->{city} = undef;
+ $taxData->{code} = undef;
+
+ cmp_deeply($addedData, $taxData, 'add put the right data into the database for Oregon');
+
+ $taxData = {
+ country => 'USA',
+ state => 'Wisconsin',
+ city => 'Madcity',
+ code => '53702',
+ taxRate => '5',
+ };
+
+ my $wisconsinTaxId = $taxer->add($taxData);
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 2, 'add added another row to the tax table');
+
+ $taxData = {
+ country => 'USA',
+ state => 'Oregon',
+ taxRate => '0.1',
+ };
+
+ my $dupId = $taxer->add($taxData);
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 3, 'add permits adding duplicate information.');
+
+ ##Madison zip codes:
+ ##53701-53709
+ ##city rate: 0.5%
+ ##Wisconsin rate 5.0%
+
+ #######################################################################
+ #
+ # getAllItems
+ #
+ #######################################################################
+
+ my $expectedTaxData = [
+ {
+ country => 'USA',
+ state => 'OR',
+ city => undef,
+ code => undef,
+ taxRate => 0,
+ },
+ {
+ country => 'USA',
+ state => 'Wisconsin',
+ city => 'Madcity',
+ code => '53702',
+ taxRate => 5,
+ },
+ {
+ country => 'USA',
+ state => 'Oregon',
+ city => undef,
+ code => undef,
+ taxRate => 0.1,
+ },
+ ];
+
+ cmp_bag(
+ $taxer->getAllItems,
+ $expectedTaxData,
+ 'getAllItems returns the whole set of tax data',
+ );
+
+ #######################################################################
+ #
+ # delete
+ #
+ #######################################################################
+
+ eval{$taxer->delete()};
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'delete: error handling for missing hashref');
+ is($e->error, 'Must pass in a hashref of params', 'delete: error message for missing hashref');
+
+ eval{$taxer->delete({})};
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'delete: error handling for missing key in hashref');
+ is($e->error, 'Hash ref must contain a taxId key with a defined value', 'delete: error message for missing key in hashref');
+
+ eval{$taxer->delete({ taxId => undef })};
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'delete: error handling for an undefined taxId value');
+ is($e->error, 'Hash ref must contain a taxId key with a defined value', 'delete: error message for an undefined taxId value');
+
+ $taxer->delete({ taxId => $dupId });
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 2, 'One row was deleted from the tax table, even though another row has duplicate information');
+
+ $taxer->delete({ taxId => $oregonTaxId });
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 1, 'Another row was deleted from the tax table');
+
+ $taxer->delete({ taxId => $session->id->generate });
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 1, 'No rows were deleted from the table since the requested id does not exist');
+ is($taxIterator->hashRef->{taxId}, $wisconsinTaxId, 'The correct tax information was deleted');
+
+ ########################################################################
+ ##
+ ## exportTaxData
+ ##
+ ########################################################################
+
+ $storage = $taxer->exportTaxData();
+ isa_ok($storage, 'WebGUI::Storage', 'exportTaxData returns a WebGUI::Storage object');
+ is($storage->{_part1}, 'temp', 'The storage object is in the temporary area');
+ ok(-e $storage->getPath('siteTaxData.csv'), 'siteTaxData.csv file exists in the storage object');
+ cmp_ok($storage->getFileSize('siteTaxData.csv'), '!=', 0, 'CSV file is not empty');
+ my @fileLines = split /\n+/, $storage->getFileContentsAsScalar('siteTaxData.csv');
+ #my @fileLines = ();
+ my @header = WebGUI::Text::splitCSV($fileLines[0]);
+ my @expectedHeader = qw/country state city code taxRate/;
+ cmp_deeply(\@header, \@expectedHeader, 'exportTaxData: header line is correct');
+ my @row1 = WebGUI::Text::splitCSV($fileLines[1]);
+ my $wiData = $taxer->getItems->hashRef;
+ ##Need to ignore the taxId from the database
+ cmp_bag([ @{ $wiData }{ @expectedHeader } ], \@row1, 'exportTaxData: first line of data is correct');
+
+ my $newTaxId = $taxer->add({
+ country => 'USA|U.S.A.',
+ state => 'washington|WA',
+ taxRate => '7',
+ code => '',
+ city => '',
+ });
+ $taxer->delete({taxId => $wisconsinTaxId});
+ $storage = $taxer->exportTaxData();
+ @fileLines = split /\n+/, $storage->getFileContentsAsScalar('siteTaxData.csv');
+ my @row1 = WebGUI::Text::splitCSV($fileLines[1]);
+ my $wiData = $taxer->getItems->hashRef;
+ ##Need to ignore the taxId from the database
+ cmp_bag([ @{ $wiData }{ @expectedHeader } ], \@row1, 'exportTaxData: first line of data is correct');
+
+ $taxer->delete({taxId => $newTaxId});
+
+ #######################################################################
+ #
+ # import
+ #
+ #######################################################################
+
+ eval { $taxer->importTaxData(); };
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'importTaxData: error handling for an undefined taxId value');
+ is($e->error, 'Must provide the path to a file', 'importTaxData: error handling for an undefined taxId value');
+
+ eval { $taxer->importTaxData('/path/to/nowhere'); };
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidFile', 'importTaxData: error handling for file that does not exist in the filesystem');
+ is($e->error, 'File could not be found', 'importTaxData: error handling for file that does not exist in the filesystem');
+ cmp_deeply(
+ $e,
+ methods(
+ brokenFile => '/path/to/nowhere',
+ ),
+ 'importTaxData: error handling for file that does not exist in the filesystem',
+ );
+
+ my $taxFile = WebGUI::Test->getTestCollateralPath('taxTables/goodTaxTable.csv');
+
+ SKIP: {
+ skip 'Root will cause this test to fail since it does not obey file permissions', 1
+ if $< == 0;
+
+ my $originalChmod = (stat $taxFile)[2];
+ chmod oct(0000), $taxFile;
+
+ eval { $taxer->importTaxData($taxFile); };
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidFile', 'importTaxData: error handling for file that cannot be read');
+ is($e->error, 'File is not readable', 'importTaxData: error handling for file that that cannot be read');
+ cmp_deeply(
+ $e,
+ methods(
+ brokenFile => $taxFile,
+ ),
+ 'importTaxData: error handling for file that that cannot be read',
+ );
+
+ chmod $originalChmod, $taxFile;
+
+ }
+
+ my $expectedTaxData = [
+ {
+ country => 'USA',
+ state => '',
+ city => '',
+ code => '',
+ taxRate => 0,
+ },
+ {
+ country => 'USA',
+ state => 'Wisconsin',
+ city => '',
+ code => '',
+ taxRate => 5,
+ },
+ {
+ country => 'USA',
+ state => 'Wisconsin',
+ city => 'Madison',
+ code => '53701',
+ taxRate => 0.5,
+ },
+ ];
+
+ ok(
+ $taxer->importTaxData(
+ $taxFile
+ ),
+ 'Good tax data inserted',
+ );
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 3, 'import: Old data deleted, new data imported');
+ cmp_bag(
+ $taxer->getAllItems,
+ $expectedTaxData,
+ 'Correct data inserted.',
+ );
+
+ ok(
+ $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/orderedTaxTable.csv')
+ ),
+ 'Reordered tax data inserted',
+ );
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 3, 'import: Old data deleted, new data imported again');
+ cmp_bag(
+ $taxer->getAllItems,
+ $expectedTaxData,
+ 'Correct data inserted, with CSV in different columnar order.',
+ );
+
+ ok(
+ $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/commentedTaxTable.csv')
+ ),
+ 'Commented tax data inserted',
+ );
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 3, 'import: Old data deleted, new data imported the third time');
+ cmp_bag(
+ $taxer->getAllItems,
+ $expectedTaxData,
+ 'Correct data inserted, with comments in the CSV file',
+ );
+
+ ok(
+ ! $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/emptyTaxTable.csv')
+ ),
+ 'Empty tax data not inserted',
+ );
+
+ $taxIterator = $taxer->getItems;
+ is($taxIterator->rows, 3, 'import: Old data still exists and was not deleted');
+
+ my $failure;
+ eval {
+ $failure = $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/badTaxTable.csv')
+ );
+ };
+ ok (!$failure, 'Tax data not imported');
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidFile', 'importTaxData: a file with an error on 1 line');
+ cmp_deeply(
+ $e,
+ methods(
+ error => 'Error found in the CSV file',
+ brokenFile => WebGUI::Test->getTestCollateralPath('taxTables/badTaxTable.csv'),
+ brokenLine => 1,
+ ),
+ 'importTaxData: error handling for file with errors in the CSV data',
+ );
+
+ eval {
+ $failure = $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/missingHeaders.csv')
+ );
+ };
+ ok (!$failure, 'Tax data not imported when headers are missing');
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidFile', 'importTaxData: a file with a missing header column');
+ cmp_deeply(
+ $e,
+ methods(
+ error => 'Bad header found in the CSV file',
+ brokenFile => WebGUI::Test->getTestCollateralPath('taxTables/missingHeaders.csv'),
+ ),
+ 'importTaxData: error handling for a file with a missing header',
+ );
+
+ eval {
+ $failure = $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/badHeaders.csv')
+ );
+ };
+ ok (!$failure, 'Tax data not imported when headers are wrong');
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidFile', 'importTaxData: a file with a bad header column');
+ cmp_deeply(
+ $e,
+ methods(
+ error => 'Bad header found in the CSV file',
+ brokenFile => WebGUI::Test->getTestCollateralPath('taxTables/badHeaders.csv'),
+ ),
+ 'importTaxData: error handling for a file with a bad header',
+ );
+
+ ok(
+ $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/alternations.csv')
+ ),
+ 'Tax data with alternations inserted',
+ );
+
+ my $altData = $taxer->getItems->hashRef; ##Just 1 row
+ cmp_deeply(
+ $altData,
+ {
+ taxId => ignore,
+ country => q{U.S.A.,USA},
+ state => q{WI,Wisconsin},
+ city => q{Madison},
+ code => 53701,
+ taxRate => 0.5,
+ },
+ 'import: Data correctly loaded with alternations'
+ );
+
+ #######################################################################
+ #
+ # getTaxRates
+ #
+ #######################################################################
+
+ ##Set up the tax information
+ $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/largeTaxTable.csv')
+ ),
+ my $book = WebGUI::Shop::AddressBook->create($session);
+ my $taxingAddress = $book->addAddress({
+ label => 'taxing',
+ city => 'Madison',
+ state => 'WI',
+ code => '53701',
+ country => 'USA',
+ });
+ my $taxFreeAddress = $book->addAddress({
+ label => 'no tax',
+ city => 'Portland',
+ state => 'OR',
+ code => '97123',
+ country => 'USA',
+ });
+ my $alternateAddress = $book->addAddress({
+ label => 'using alternations',
+ city => 'Los Angeles',
+ state => 'CalifornIA',
+ code => '92801',
+ country => 'USA',
+ });
+
+ eval { $taxer->getTaxRates(); };
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidObject', 'calculate: error handling for not sending a cart');
+ cmp_deeply(
+ $e,
+ methods(
+ error => 'Need an address.',
+ got => '',
+ expected => 'WebGUI::Shop::Address',
+ ),
+ 'importTaxData: error handling for file that does not exist in the filesystem',
+ );
+
+ cmp_deeply(
+ $taxer->getTaxRates($taxingAddress),
+ [0, 5, 0.5],
+ 'getTaxRates: return correct data for a state with tax data'
+ );
+
+ cmp_deeply(
+ $taxer->getTaxRates($taxFreeAddress),
+ [0,0],
+ 'getTaxRates: return correct data for a state with no tax data'
+ );
+
+ cmp_deeply(
+ $taxer->getTaxRates($alternateAddress),
+ [0.0, 8.25], #Hits USA and Los Angeles, California using the alternate spelling of the state
+ 'getTaxRates: return correct data for a state when the address has alternations'
+ );
+
+ #######################################################################
+ #
+ # calculate
+ #
+ #######################################################################
+
+ eval { $taxer->calculate(); };
+ $e = Exception::Class->caught();
+ isa_ok($e, 'WebGUI::Error::InvalidParam', 'calculate: error handling for not sending a cart');
+ is($e->error, 'Must pass in a WebGUI::Shop::Cart object', 'calculate: error handling for not sending a cart');
+
+ ##Build a cart, add some Donation SKUs to it. Set one to be taxable.
+
+ my $cart = WebGUI::Shop::Cart->getCartBySession($session);
+
+ is($taxer->calculate($cart), 0, 'calculate returns 0 if there is no shippingAddressId in the cart');
+
+ $cart->update({ shippingAddressId => $taxingAddress->getId});
+
+ ##Set up the tax information
+ $taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/largeTaxTable.csv')
+ ),
+
+ my $taxableDonation = WebGUI::Asset->getRoot($session)->addChild({
+ className => 'WebGUI::Asset::Sku::Donation',
+ title => 'Taxable donation',
+ defaultPrice => 100.00,
+ });
+
+ $cart->addItem($taxableDonation);
+
+ foreach my $item (@{ $cart->getItems }) {
+ $item->setQuantity(1);
+ }
+
+ my $tax = $taxer->calculate($cart);
+ is($tax, 5.5, 'calculate: simple tax calculation on 1 item in the cart');
+
+ $cart->update({ shippingAddressId => $taxFreeAddress->getId});
+ is($taxer->calculate($cart), 0, 'calculate: simple tax calculation on 1 item in the cart, tax free location');
+
+ foreach my $item (@{ $cart->getItems }) {
+ $item->setQuantity(2);
+ }
+
+ $cart->update({ shippingAddressId => $taxingAddress->getId});
+ is($taxer->calculate($cart), 11, 'calculate: simple tax calculation on 1 item in the cart, qty 2');
+
+ my $taxFreeDonation = WebGUI::Asset->getRoot($session)->addChild({
+ className => 'WebGUI::Asset::Sku::Donation',
+ title => 'Tax Free Donation',
+ defaultPrice => 100.00,
+ overrideTaxRate => 1,
+ taxRateOverride => 0,
+ });
+
+ $cart->addItem($taxFreeDonation);
+
+ foreach my $item (@{ $cart->getItems }) {
+ $item->setQuantity(1);
+ }
+ is($taxer->calculate($cart), 5.5, 'calculate: simple tax calculation on 2 items in the cart, 1 without taxes');
+
+ my $remoteItem = $cart->addItem($taxableDonation);
+ $remoteItem->update({shippingAddressId => $taxFreeAddress->getId});
+
+ foreach my $item (@{ $cart->getItems }) {
+ $item->setQuantity(1);
+ }
+ is($taxer->calculate($cart), 5.5, 'calculate: simple tax calculation on 2 items in the cart, 1 without taxes, 1 shipped to a location with no taxes');
+
+ #######################################################################
+ #
+ # www_getTaxesAsJson
+ #
+ #######################################################################
+
+ $session->user({userId=>3});
+ my $json = $taxer->www_getTaxesAsJson();
+ ok($json, 'www_getTaxesAsJson returned something');
+ my $jsonTax = JSON::from_json($json);
+ cmp_deeply(
+ $jsonTax,
+ {
+ sort => undef,
+ startIndex => 0,
+ totalRecords => 1778,
+ recordsReturned => 25,
+ dir => 'desc',
+ records => array_each({
+ taxId=>ignore,
+ country => 'USA',
+ state=>ignore,
+ city=>ignore,
+ code=>ignore,
+ taxRate=>re('^\d+\.\d+$')
+ }),
+ },
+ 'Check major elements of tax JSON',
+ );
+
+ TODO: {
+ local $TODO = 'More getTaxesAsJson tests';
+ ok(0, 'test group privileges to this method');
+ ok(0, 'test startIndex variable');
+ ok(0, 'test results form variable');
+ ok(0, 'test keywords');
+ }
+
+ $cart->delete;
+ $book->delete;
+ $taxableDonation->purge;
+ $taxFreeDonation->purge;
+}
+
+sub getAddExceptions {
+ my $session = shift;
+ my $inputValidion = [
+ {
+ args => {},
+ error => q{Missing required information.},
+ param => q{country},
+ comment => q{missing country},
+ },
+ {
+ args => {country => undef},
+ error => q{Missing required information.},
+ param => q{country},
+ comment => q{undef country},
+ },
+ {
+ args => {country => ''},
+ error => q{Missing required information.},
+ param => q{country},
+ comment => q{empty country},
+ },
+ {
+ args => {country => 'USA'},
+ error => q{Missing required information.},
+ param => q{taxRate},
+ comment => q{missing taxRate},
+ },
+ {
+ args => {country => 'USA', taxRate => undef},
+ error => q{Missing required information.},
+ param => q{taxRate},
+ comment => q{empty taxRate},
+ },
+ ];
+}
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ $session->db->write('delete from tax');
+ $session->db->write('delete from cart');
+ $session->db->write('delete from addressBook');
+ $session->db->write('delete from address');
+ #$storage->delete;
+}
diff --git a/t/Shop/Transaction.t b/t/Shop/Transaction.t
new file mode 100644
index 000000000..6d7a2674b
--- /dev/null
+++ b/t/Shop/Transaction.t
@@ -0,0 +1,240 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Tests the transaction backend for the shop.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More;
+use Test::Deep;
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Shop::Transaction;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+
+#----------------------------------------------------------------------------
+# Tests
+
+plan tests => 67; # Increment this number for each test you create
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+my $transaction = WebGUI::Shop::Transaction->create($session,{
+ amount => 40,
+ shippingAddressId => 'xxx1',
+ shippingAddressName => 'abc',
+ shippingAddress1 => 'def',
+ shippingAddress2 => 'hij',
+ shippingAddress3 => 'lmn',
+ shippingCity => 'opq',
+ shippingState => 'wxy',
+ shippingCountry => 'z',
+ shippingCode => '53333',
+ shippingPhoneNumber => '123456',
+ shippingDriverId => 'xxx2',
+ shippingDriverLabel => 'foo',
+ shippingPrice => 5,
+ paymentAddressId => 'xxx3',
+ paymentAddressName => 'abc1',
+ paymentAddress1 => 'def1',
+ paymentAddress2 => 'hij1',
+ paymentAddress3 => 'lmn1',
+ paymentCity => 'opq1',
+ paymentState => 'wxy1',
+ paymentCountry => 'z1',
+ paymentCode => '66666',
+ paymentPhoneNumber => '908765',
+ paymentDriverId => 'xxx4',
+ paymentDriverLabel => 'kkk',
+ taxes => 7,
+ });
+
+# objects work
+isa_ok($transaction, "WebGUI::Shop::Transaction");
+isa_ok($transaction->session, "WebGUI::Session");
+
+
+# basic transaction properties
+is($transaction->get("amount"), 40, "set and get amount");
+is($transaction->get("shippingAddressId"), 'xxx1', "set and get shipping address id");
+is($transaction->get("shippingAddressName"), 'abc', "set and get shipping address name");
+is($transaction->get("shippingAddress1"), 'def', "set and get shipping address 1");
+is($transaction->get("shippingAddress2"), 'hij', "set and get shipping address 2");
+is($transaction->get("shippingAddress3"), 'lmn', "set and get shipping address 3");
+is($transaction->get("shippingCity"), 'opq', "set and get shipping city");
+is($transaction->get("shippingState"), 'wxy', "set and get shipping state");
+is($transaction->get("shippingCountry"), 'z', "set and get shipping country");
+is($transaction->get("shippingCode"), '53333', "set and get shipping code");
+is($transaction->get("shippingPhoneNumber"), '123456', "set and get shipping phone number");
+is($transaction->get("shippingDriverId"), 'xxx2', "set and get shipping driver id");
+is($transaction->get("shippingDriverLabel"), 'foo', "set and get shipping driver label");
+is($transaction->get("shippingPrice"), 5, "set and get shipping price");
+is($transaction->get("paymentAddressId"), 'xxx3', "set and get payment address id");
+is($transaction->get("paymentAddressName"), 'abc1', "set and get payment address name");
+is($transaction->get("paymentAddress1"), 'def1', "set and get payment address 1");
+is($transaction->get("paymentAddress2"), 'hij1', "set and get payment address 2");
+is($transaction->get("paymentAddress3"), 'lmn1', "set and get payment address 3");
+is($transaction->get("paymentCity"), 'opq1', "set and get payment city");
+is($transaction->get("paymentState"), 'wxy1', "set and get payment state");
+is($transaction->get("paymentCountry"), 'z1', "set and get payment country");
+is($transaction->get("paymentCode"), '66666', "set and get payment code");
+is($transaction->get("paymentPhoneNumber"), '908765', "set and get payment phone number");
+is($transaction->get("paymentDriverId"), 'xxx4', "set and get payment driver id");
+is($transaction->get("paymentDriverLabel"), 'kkk', "set and get payment driver label");
+is($transaction->get("taxes"), 7, "set and get taxes");
+
+
+$transaction->update({
+ isSuccessful => 1,
+ transactionCode => 'yyy',
+ statusCode => 'jd31',
+ statusMessage => 'was a success',
+});
+
+is($transaction->get("isSuccessful"), 1,"update and get isSuccessful");
+is($transaction->get("transactionCode"), 'yyy',"update and get transaction code");
+is($transaction->get("statusCode"), 'jd31',"update and get status code");
+is($transaction->get("statusMessage"), 'was a success',"update and get status message");
+
+# make sure new() works
+my $tcopy = WebGUI::Shop::Transaction->new($session, $transaction->getId);
+
+isa_ok($tcopy, "WebGUI::Shop::Transaction");
+is($tcopy->getId, $transaction->getId, "is it the same object");
+
+
+# basic item properties
+my $item = $transaction->addItem({
+ assetId => 'a',
+ configuredTitle => 'b',
+ options => {color=>'blue'},
+ shippingAddressId => 'c',
+ shippingName => 'd',
+ shippingAddress1 => 'e',
+ shippingAddress2 => 'f',
+ shippingAddress3 => 'g',
+ shippingCity => 'h',
+ shippingState => 'i',
+ shippingCountry => 'j',
+ shippingCode => 'k',
+ shippingPhoneNumber => 'l',
+ quantity => 5,
+ price => 33,
+});
+
+isa_ok($item, "WebGUI::Shop::TransactionItem");
+isa_ok($item->transaction, "WebGUI::Shop::Transaction");
+
+is($item->get("assetId"), 'a', "set and get asset id");
+is($item->get("configuredTitle"), 'b', "set and get configured title");
+cmp_deeply($item->get("options"), {color=>'blue'}, "set and get options");
+is($item->get("shippingAddressId"), 'c', "set and get shipping address id");
+is($item->get("shippingName"), 'd', "set and get shipping name");
+is($item->get("shippingAddress1"), 'e', "set and get shipping address 1");
+is($item->get("shippingAddress2"), 'f', "set and get shipping address 2");
+is($item->get("shippingAddress3"), 'g', "set and get shipping address 3");
+is($item->get("shippingCity"), 'h', "set and get shipping city");
+is($item->get("shippingState"), 'i', "set and get shipping state");
+is($item->get("shippingCountry"), 'j', "set and get shipping country");
+is($item->get("shippingCode"), 'k', "set and get shipping code");
+is($item->get("shippingPhoneNumber"), 'l', "set and get shipping phone number");
+is($item->get("quantity"), 5, "set and get quantity");
+is($item->get("price"), 33, "set and get price");
+
+$item->update({
+ shippingTrackingNumber => 'adfs',
+ shippingStatus => 'BackOrdered',
+});
+
+is($item->get("shippingTrackingNumber"), 'adfs', "update and get shipping tracking number");
+is($item->get("shippingStatus"), 'BackOrdered', "update and get shipping status");
+
+# make sure shipping date is updated when shipping status is changed
+like($item->get("shippingDate"), qr/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/, "shipping date is set");
+my $dateNow = $item->get('shippingDate');
+sleep(1);
+$item->update({shippingStatus=>'Cancelled'});
+isnt($item->get('shippingDate'), $dateNow, 'shipping date is updated');
+
+# make sure new() works
+my $icopy = $transaction->getItem($item->getId);
+isa_ok($icopy, "WebGUI::Shop::TransactionItem");
+is($icopy->getId, $item->getId, "items are the same");
+
+# get items
+is(scalar @{$transaction->getItems}, 1, "can retrieve items");
+
+# delete
+$item->delete;
+is(scalar @{$transaction->getItems}, 0, "can delete items");
+
+#######################################################################
+#
+# www_getTaxesAsJson
+#
+#######################################################################
+
+$session->user({userId=>3});
+my $json = WebGUI::Shop::Transaction->www_getTransactionsAsJson($session);
+ok($json, 'www_getTransactionsAsJson returned something');
+diag $json;
+my $jsonTransactions = JSON::from_json($json);
+cmp_deeply(
+ $jsonTransactions,
+ {
+ sort => undef,
+ startIndex => 0,
+ totalRecords => 1,
+ recordsReturned => 1,
+ dir => 'desc',
+ records => array_each({
+ orderNumber=>ignore,
+ transactionId=>ignore,
+ transactionCode=>ignore,
+ paymentDriverLabel=>ignore,
+ dateOfPurchase=>ignore,
+ username=>ignore,
+ amount=>ignore,
+ isSuccessful=>ignore,
+ statusCode=>ignore,
+ statusMessage=>ignore,
+ }),
+ },
+ 'Check major elements of transaction JSON',
+);
+
+TODO: {
+ local $TODO = 'More getTaxesAsJson tests';
+ ok(0, 'test group privileges to this method');
+ ok(0, 'test startIndex variable');
+ ok(0, 'test results form variable');
+ ok(0, 'test keywords');
+}
+
+
+$transaction->delete;
+is($session->db->quickScalar("select transactionId from transaction where transactionId=?",[$transaction->getId]), undef, "can delete transactions");
+
+
+
+#----------------------------------------------------------------------------
+# Cleanup
+END {
+ $session->db->write('delete from transaction');
+}
diff --git a/t/Shop/loadProducts.pl b/t/Shop/loadProducts.pl
new file mode 100644
index 000000000..dc6c2a9b5
--- /dev/null
+++ b/t/Shop/loadProducts.pl
@@ -0,0 +1,74 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More qw(no_plan);
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Shop::Tax;
+use WebGUI::Asset::Wobject::Product;
+use WebGUI::VersionTag;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+##Create products by hand
+
+my $tag = WebGUI::VersionTag->getWorking($session);
+
+my $properties1 = {
+ className => 'WebGUI::Asset::Wobject::Product',
+ url => 'one',
+ price => 10.00,
+ productNumber => '#1',
+ title => 'product 1',
+ description => 'First product',
+};
+
+my $root = WebGUI::Asset->getRoot($session);
+my $product1 = $root->addChild($properties1);
+
+diag ref $product1;
+
+my $properties2 = {
+ className => 'WebGUI::Asset::Wobject::Product',
+ url => 'two',
+ price => 20.00,
+ productNumber => '#2',
+ title => 'product 2',
+ description => 'Second product',
+};
+
+my $product2 = $root->addChild($properties2);
+
+diag ref $product2;
+
+$tag->commit;
+sleep 2;
+
+$tag = WebGUI::VersionTag->getWorking($session);
+
+my $product1a = $product1->addRevision({price => 11.11});
+
+$tag->commit;
+
+diag "Done.";
diff --git a/t/Shop/loadTaxes.pl b/t/Shop/loadTaxes.pl
new file mode 100644
index 000000000..1a893249e
--- /dev/null
+++ b/t/Shop/loadTaxes.pl
@@ -0,0 +1,41 @@
+# vim:syntax=perl
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2008 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
+#------------------------------------------------------------------
+
+# Write a little about what this script tests.
+#
+#
+
+use FindBin;
+use strict;
+use lib "$FindBin::Bin/../lib";
+use Test::More qw(no_plan);
+use Test::Deep;
+use Exception::Class;
+use Data::Dumper;
+
+use WebGUI::Test; # Must use this before any other WebGUI modules
+use WebGUI::Session;
+use WebGUI::Text;
+use WebGUI::Shop::Cart;
+use WebGUI::Shop::AddressBook;
+
+#----------------------------------------------------------------------------
+# Init
+my $session = WebGUI::Test->session;
+
+#----------------------------------------------------------------------------
+# put your tests here
+
+##Set up the tax information
+my $taxer = WebGUI::Shop::Tax->new($session);
+$taxer->importTaxData(
+ WebGUI::Test->getTestCollateralPath('taxTables/largeTaxTable.csv')
+);
diff --git a/t/lib/WebGUI/TestException.pm b/t/lib/WebGUI/TestException.pm
new file mode 100644
index 000000000..7deae51fb
--- /dev/null
+++ b/t/lib/WebGUI/TestException.pm
@@ -0,0 +1,123 @@
+package WebGUI::TestException;
+
+use strict;
+
+use Test::Builder;
+use WebGUI::Exception;
+use Sub::Uplevel qw( uplevel );
+
+our @EXPORT = qw( throws_deeply );
+
+=head1 NAME
+
+Package WebGUI::TestException
+
+=head1 DESCRIPTION
+
+This module provides a convenient way to test for thrown exceptions. The idea is based on Test::Exception, which
+does provide a means to test for a specific exception class, but cannot test attributes of that class, which is
+necessary in the WebGUI test suite. This module can do that.
+
+=head1 CAVEATS
+
+This module uses Sub::Uplevel. In Test::Exception some hocus pocus is being done with the caller() function. The
+functions _quiet_caller and _try_as_caller are directly copied from Test::Exception. I do not know why this
+hocuspocus is being in that module however, since doing 'eval { uplevel 1, $codeRef }' seems to work too. On my
+platform at least =). For the time being, I leave those subs in here so that they may be used. They are commented
+out by default, though.
+=cut
+
+#----------------------------------------------------------------------------
+sub _quiet_caller (;$) { ## no critic Prototypes
+ my $height = $_[0];
+ $height++;
+ if( wantarray and !@_ ) {
+ return (CORE::caller($height))[0..2];
+ }
+ else {
+ return CORE::caller($height);
+ }
+}
+
+#----------------------------------------------------------------------------
+sub _try_as_caller {
+ my $coderef = shift;
+
+ # local works here because Sub::Uplevel has already overridden caller
+ local *CORE::GLOBAL::caller;
+ { no warnings 'redefine'; *CORE::GLOBAL::caller = \&_quiet_caller; }
+
+ eval { uplevel 3, $coderef };
+ return $@;
+};
+
+=head2 throws_deeply ( $codeRef, $expectClass, $fields, $message )
+
+Executes the code ref and verifies it throws an exception of the given class with the given fields.
+
+=head3 $codeRef
+
+The code ref containing the code to be evalled.
+
+=head3 $expectClass
+
+The class name the thrown exception should have.
+
+=head3 $fields
+
+Hashref containg the exception fields and their expected values.
+
+=head3 $message
+
+The message that should be displayed by prove for this test.
+
+=cut
+
+#----------------------------------------------------------------------------
+sub throws_deeply {
+ my $evalBlock = shift;
+ my $expectClass = shift;
+ my $fields = shift;
+ my $message = shift;
+ my $testBuilder = Test::Builder->new;
+
+ # Dunno why uplevel 1 might not work and why caller is redefined.
+ # Copied _try_as_caller and _quiet_caller are from Test::Exception.
+ # Uplevel 1 seems to work though.
+ #_try_as_caller( $evalBlock );
+ eval { uplevel 1, $evalBlock };
+
+ my $e = Exception::Class->caught();
+ my $gotClass = ref $e;
+
+ # Check class
+ unless ($gotClass eq $expectClass) {
+ $testBuilder->ok(0, $message);
+ $testBuilder->diag("Wrong class:\n\texpected : '$expectClass'\n\t got : '$gotClass'");
+
+ return 0;
+ }
+
+ # Check fields
+ my $errors;
+ foreach (keys %$fields) {
+ my $result = $e->$_;
+
+ unless ( $result eq $fields->{$_} ) {
+ $errors .= "'$_' => \n\texpected : '".$fields->{$_}."'\n\t got : '$result'\n";
+ }
+ }
+ if ($errors) {
+ $testBuilder->ok(0, $message);
+ $testBuilder->diag("Fields do not match:\n$errors");
+
+ return 0;
+ }
+
+ # Test passed.
+ $testBuilder->ok(1, $message);
+ return 1;
+}
+
+1;
+
diff --git a/t/supporting_collateral/taxTables/alternations.csv b/t/supporting_collateral/taxTables/alternations.csv
new file mode 100644
index 000000000..b0db2bbc5
--- /dev/null
+++ b/t/supporting_collateral/taxTables/alternations.csv
@@ -0,0 +1,2 @@
+country,state,city,code,taxRate
+U.S.A.|USA,WI|Wisconsin,Madison,53701,0.5
diff --git a/t/supporting_collateral/taxTables/badHeaders.csv b/t/supporting_collateral/taxTables/badHeaders.csv
new file mode 100644
index 000000000..7af14beb6
--- /dev/null
+++ b/t/supporting_collateral/taxTables/badHeaders.csv
@@ -0,0 +1,7 @@
+country,state,city,zip,taxRate
+USA,,,,0.0
+USA,Wisconsin,,,5.0
+USA,Wisconsin,Madison,53701,0.5
+where,value,taxRates
+state,5.0
+code,53701,0.5
diff --git a/t/supporting_collateral/taxTables/badTaxTable.csv b/t/supporting_collateral/taxTables/badTaxTable.csv
new file mode 100644
index 000000000..56f40f257
--- /dev/null
+++ b/t/supporting_collateral/taxTables/badTaxTable.csv
@@ -0,0 +1,4 @@
+country,state,city,code,taxRate
+USA,,,,0.0,
+USA,Wisconsin,,,5.0
+USA,Wisconsin,Madison,53701,0.5
diff --git a/t/supporting_collateral/taxTables/commentedTaxTable.csv b/t/supporting_collateral/taxTables/commentedTaxTable.csv
new file mode 100644
index 000000000..7e3d32c19
--- /dev/null
+++ b/t/supporting_collateral/taxTables/commentedTaxTable.csv
@@ -0,0 +1,9 @@
+country,state,city,code,taxRate
+#header lines above
+
+#This is just a country.
+USA,,,,0.0
+USA,Wisconsin,,,5.0 #Wisconsin is expensive
+
+
+USA,Wisconsin,Madison,53701,0.5
diff --git a/t/supporting_collateral/taxTables/emptyTaxTable.csv b/t/supporting_collateral/taxTables/emptyTaxTable.csv
new file mode 100644
index 000000000..d163db1b9
--- /dev/null
+++ b/t/supporting_collateral/taxTables/emptyTaxTable.csv
@@ -0,0 +1,3 @@
+country,state,city,code,taxRate
+#state,Wisconsin,5.0
+#code,53701,0.5
diff --git a/t/supporting_collateral/taxTables/goodTaxTable.csv b/t/supporting_collateral/taxTables/goodTaxTable.csv
new file mode 100644
index 000000000..1f2934110
--- /dev/null
+++ b/t/supporting_collateral/taxTables/goodTaxTable.csv
@@ -0,0 +1,4 @@
+country,state,city,code,taxRate
+USA,,,,0.0
+USA,Wisconsin,,,5.0
+USA,Wisconsin,Madison,53701,0.5
diff --git a/t/supporting_collateral/taxTables/largeTaxTable.csv b/t/supporting_collateral/taxTables/largeTaxTable.csv
new file mode 100644
index 000000000..a890aa3e2
--- /dev/null
+++ b/t/supporting_collateral/taxTables/largeTaxTable.csv
@@ -0,0 +1,1779 @@
+country,state,city,code,taxRate
+USA,,,,0.0
+USA,WI,,,5.0
+USA,WI,,53701,0.5
+USA,WI,,53702,0.5
+USA,WI,,53703,0.5
+USA,WI,,53704,0.5
+USA,,,97123,0.0
+USA,CA,Acampo,,7.750
+USA,CA,Acton,,8.250
+USA,CA,Adelaida,,7.250
+USA,CA,Adelanto,,7.750
+USA,CA,Adin,,7.250
+USA,CA,Agoura,,8.250
+USA,CA,Agoura Hills,,8.250
+USA,CA,Agua Caliente,,7.750
+USA,CA,Agua Caliente Springs,,7.750
+USA,CA,Agua Dulce,,8.250
+USA,CA,Aguanga,,7.750
+USA,CA,Ahwahnee,,7.750
+USA,CA,Al Tahoe,,7.250
+USA,CA,Alameda,,8.750
+USA,CA,Alamo,,8.250
+USA,CA,Albany,,8.750
+USA,CA,Alberhill (Lake Elsinore),,7.750
+USA,CA,Albion,,7.250
+USA,CA,Alderpoint,,7.250
+USA,CA,Alhambra,,8.250
+USA,CA,Aliso Viejo,,7.750
+USA,CA,Alleghany,,7.250
+USA,CA,Almaden Valley,,8.250
+USA,CA,Almanor,,7.250
+USA,CA,Almondale,,8.250
+USA,CA,Alondra,,8.250
+USA,CA,Alpaugh,,7.750
+USA,CA,Alpine,,7.750
+USA,CA,Alta,,7.250
+USA,CA,Alta Loma (Rancho Cucamonga),,7.750
+USA,CA,Altadena,,8.250
+USA,CA,Altaville,,7.250
+USA,CA,Alton,,7.250
+USA,CA,Alturas,,7.250
+USA,CA,Alviso (San Jose),,8.250
+USA,CA,Amador City,,7.250
+USA,CA,Amargosa (Death Valley),,7.750
+USA,CA,Amboy,,7.750
+USA,CA,American Canyon,,7.750
+USA,CA,Anaheim,,7.750
+USA,CA,Anderson,,7.250
+USA,CA,Angels Camp,,7.250
+USA,CA,Angelus Oaks,,7.750
+USA,CA,Angwin,,7.750
+USA,CA,Annapolis,,7.750
+USA,CA,Antelope,,7.750
+USA,CA,Antelope Acres,,8.250
+USA,CA,Antioch,,8.250
+USA,CA,Anza,,7.750
+USA,CA,Apple Valley,,7.750
+USA,CA,Applegate,,7.250
+USA,CA,Aptos,,8.000
+USA,CA,Arbuckle,,7.250
+USA,CA,Arcadia,,8.250
+USA,CA,Arcata,,7.250
+USA,CA,Argus,,7.750
+USA,CA,Arleta (Los Angeles),,8.250
+USA,CA,Arlington (Riverside),,7.750
+USA,CA,Armona,,7.250
+USA,CA,Army Terminal,,8.750
+USA,CA,Arnold,,7.250
+USA,CA,Aromas,,7.250
+USA,CA,Arrowbear Lake,,7.750
+USA,CA,Arrowhead Highlands,,7.750
+USA,CA,Arroyo Grande,,7.750
+USA,CA,Artesia,,8.250
+USA,CA,Artois,,7.250
+USA,CA,Arvin,,7.250
+USA,CA,Ashland,,8.750
+USA,CA,Asti,,7.750
+USA,CA,Atascadero,,7.250
+USA,CA,Athens,,8.250
+USA,CA,Atherton,,8.250
+USA,CA,Atwater,,7.250
+USA,CA,Atwood,,7.750
+USA,CA,Auberry,,7.975
+USA,CA,Auburn,,7.250
+USA,CA,Avalon,,8.750
+USA,CA,Avenal,,7.250
+USA,CA,Avery,,7.250
+USA,CA,Avila Beach,,7.250
+USA,CA,Azusa,,8.250
+USA,CA,Badger,,7.750
+USA,CA,Bailey,,8.250
+USA,CA,Baker,,7.750
+USA,CA,Bakersfield,,7.250
+USA,CA,Balboa (Newport Beach),,7.750
+USA,CA,Balboa Island (Newport Beach),,7.750
+USA,CA,Balboa Park (San Diego),,7.750
+USA,CA,Baldwin Park,,8.250
+USA,CA,Ballard,,7.750
+USA,CA,Ballico,,7.250
+USA,CA,Ballroad,,7.750
+USA,CA,Bangor,,7.250
+USA,CA,Banning,,7.750
+USA,CA,Banta,,7.750
+USA,CA,Bard,,7.750
+USA,CA,Barrington,,8.250
+USA,CA,Barstow,,7.750
+USA,CA,Bartlett,,7.750
+USA,CA,Barton,,7.975
+USA,CA,Base Line,,7.750
+USA,CA,Bass Lake,,7.750
+USA,CA,Bassett,,8.250
+USA,CA,Baxter,,7.250
+USA,CA,Bayside,,7.250
+USA,CA,Baywood Park,,7.250
+USA,CA,Beale A.F.B.,,7.250
+USA,CA,Bear River Lake,,7.250
+USA,CA,Bear Valley,,7.250
+USA,CA,Bear Valley,,7.750
+USA,CA,Beaumont,,7.750
+USA,CA,Beckwourth,,7.250
+USA,CA,Bel Air Estates,,8.250
+USA,CA,Belden,,7.250
+USA,CA,Bell Gardens,,8.250
+USA,CA,Bell,,8.250
+USA,CA,Bella Vista,,7.250
+USA,CA,Bellflower,,8.250
+USA,CA,Belmont,,8.250
+USA,CA,Belvedere,,7.750
+USA,CA,Ben Lomond,,8.000
+USA,CA,Benicia,,7.375
+USA,CA,Benton,,7.250
+USA,CA,Berkeley,,8.750
+USA,CA,Bermuda Dunes,,7.750
+USA,CA,Berry Creek,,7.250
+USA,CA,Bethel Island,,8.250
+USA,CA,Betteravia,,7.750
+USA,CA,Beverly Hills,,8.250
+USA,CA,Bieber,,7.250
+USA,CA,Big Bar,,7.250
+USA,CA,Big Basin,,8.000
+USA,CA,Big Bear City,,7.750
+USA,CA,Big Bear Lake,,7.750
+USA,CA,Big Bend,,7.250
+USA,CA,Big Creek,,7.975
+USA,CA,Big Oak Flat,,7.250
+USA,CA,Big Pine,,7.750
+USA,CA,Big River,,7.750
+USA,CA,Big Sur,,7.250
+USA,CA,Biggs,,7.250
+USA,CA,Bijou,,7.250
+USA,CA,Biola,,7.975
+USA,CA,Biola College (La Mirada),,8.250
+USA,CA,Birds Landing,,7.375
+USA,CA,Bishop,,7.750
+USA,CA,Black Hawk,,8.250
+USA,CA,Blairsden,,7.250
+USA,CA,Blocksburg,,7.250
+USA,CA,Bloomington,,7.750
+USA,CA,Blossom Hill,,8.250
+USA,CA,Blossom Valley,,8.250
+USA,CA,Blue Jay,,7.750
+USA,CA,Blue Lake,,7.250
+USA,CA,Blythe,,7.750
+USA,CA,Bodega,,7.750
+USA,CA,Bodega Bay,,7.750
+USA,CA,Bodfish,,7.250
+USA,CA,Bolinas,,7.750
+USA,CA,Bolsa,,7.750
+USA,CA,Bombay Beach,,7.750
+USA,CA,Bonita,,7.750
+USA,CA,Bonny Doon,,8.000
+USA,CA,Bonsall,,7.750
+USA,CA,Boonville,,7.250
+USA,CA,Boron,,7.250
+USA,CA,Borrego Springs,,7.750
+USA,CA,Bostonia,,7.750
+USA,CA,Boulder Creek,,8.000
+USA,CA,Boulevard,,7.750
+USA,CA,Bouquet Canyon (Santa Clarita),,8.250
+USA,CA,Bowman,,7.250
+USA,CA,Boyes Hot Springs,,7.750
+USA,CA,Bradbury,,8.250
+USA,CA,Bradford,,8.750
+USA,CA,Bradley,,7.250
+USA,CA,Branscomb,,7.250
+USA,CA,Brawley,,7.750
+USA,CA,Brea,,7.750
+USA,CA,Brents Junction,,8.250
+USA,CA,Brentwood (Los Angeles),,8.250
+USA,CA,Brentwood,,8.250
+USA,CA,Briceland,,7.250
+USA,CA,Bridgeport,,7.750
+USA,CA,Bridgeport,,7.250
+USA,CA,Bridgeville,,7.250
+USA,CA,Brisbane,,8.250
+USA,CA,Broderick (West Sacramento),,7.750
+USA,CA,Brookdale,,8.000
+USA,CA,Brookhurst Center,,7.750
+USA,CA,Brooks,,7.250
+USA,CA,Browns Valley,,7.250
+USA,CA,Brownsville,,7.250
+USA,CA,Bryn Mawr,,7.750
+USA,CA,Bryte (West Sacramento),,7.750
+USA,CA,Buellton,,7.750
+USA,CA,Buena Park,,7.750
+USA,CA,Burbank,,8.250
+USA,CA,Burlingame,,8.250
+USA,CA,Burney,,7.250
+USA,CA,Burnt Ranch,,7.250
+USA,CA,Burrel,,7.975
+USA,CA,Burson,,7.250
+USA,CA,Butte City,,7.250
+USA,CA,Butte Meadows,,7.250
+USA,CA,Buttonwillow,,7.250
+USA,CA,Byron,,8.250
+USA,CA,Cabazon,,7.750
+USA,CA,Cabrillo,,8.250
+USA,CA,Cadiz,,7.750
+USA,CA,Calabasas Highlands,,8.250
+USA,CA,Calabasas Park,,8.250
+USA,CA,Calabasas,,8.250
+USA,CA,Calexico,,7.750
+USA,CA,Caliente,,7.250
+USA,CA,California City,,7.250
+USA,CA,California Hot Springs,,7.750
+USA,CA,California Valley,,7.250
+USA,CA,Calimesa,,7.750
+USA,CA,Calipatria,,7.750
+USA,CA,Calistoga,,7.750
+USA,CA,Callahan,,7.250
+USA,CA,Calpella,,7.250
+USA,CA,Calpine,,7.250
+USA,CA,Calwa,,7.975
+USA,CA,Camarillo,,7.250
+USA,CA,Cambria,,7.250
+USA,CA,Cambrian Park,,8.250
+USA,CA,Cameron Park,,7.250
+USA,CA,Camino,,7.250
+USA,CA,Camp Beale,,7.250
+USA,CA,Camp Connell,,7.250
+USA,CA,Camp Curry,,7.750
+USA,CA,Camp Kaweah,,7.750
+USA,CA,Camp Meeker,,7.750
+USA,CA,Camp Nelson,,7.750
+USA,CA,Camp Pendleton,,7.750
+USA,CA,Camp Roberts,,7.250
+USA,CA,Campbell,,8.250
+USA,CA,Campo,,7.750
+USA,CA,Campo Seco,,7.250
+USA,CA,Camptonville,,7.250
+USA,CA,Canby,,7.250
+USA,CA,Canoga Annex,,8.250
+USA,CA,Canoga Park (Los Angeles),,8.250
+USA,CA,Cantil,,7.250
+USA,CA,Cantua Creek,,7.975
+USA,CA,Canyon,,8.250
+USA,CA,Canyon Country (Santa Clarita),,8.250
+USA,CA,Canyon Lake,,7.750
+USA,CA,Canyondam,,7.250
+USA,CA,Capay,,7.250
+USA,CA,Capistrano Beach (Dana Point),,7.750
+USA,CA,Capitola,,8.250
+USA,CA,Cardiff By The Sea (Encinitas),,7.750
+USA,CA,Cardwell,,7.975
+USA,CA,Carlotta,,7.250
+USA,CA,Carlsbad,,7.750
+USA,CA,Carmel Rancho,,7.250
+USA,CA,Carmel Valley,,7.250
+USA,CA,Carmel,,7.250
+USA,CA,Carmichael,,7.750
+USA,CA,Carnelian Bay,,7.250
+USA,CA,Carpinteria,,7.750
+USA,CA,Carson,,8.250
+USA,CA,Cartago,,7.750
+USA,CA,Caruthers,,7.975
+USA,CA,Casitas Springs,,7.250
+USA,CA,Casmalia,,7.750
+USA,CA,Caspar,,7.250
+USA,CA,Cassel,,7.250
+USA,CA,Castaic,,8.250
+USA,CA,Castella,,7.250
+USA,CA,Castle A.F.B.,,7.250
+USA,CA,Castro Valley,,8.750
+USA,CA,Castroville,,7.250
+USA,CA,Cathedral City,,7.750
+USA,CA,Catheys Valley,,7.750
+USA,CA,Cayucos,,7.250
+USA,CA,Cazadero,,7.750
+USA,CA,Cecilville,,7.250
+USA,CA,Cedar,,8.250
+USA,CA,Cedar Crest,,7.975
+USA,CA,Cedar Glen,,7.750
+USA,CA,Cedar Ridge,,7.375
+USA,CA,Cedarpines Park,,7.750
+USA,CA,Cedarville,,7.250
+USA,CA,Central Valley,,7.250
+USA,CA,Century City,,8.250
+USA,CA,Ceres,,7.875
+USA,CA,Cerritos,,8.250
+USA,CA,Challenge,,7.250
+USA,CA,Chambers Lodge,,7.250
+USA,CA,Charter Oak,,8.250
+USA,CA,Chatsworth (Los Angeles),,8.250
+USA,CA,Cherry Valley,,7.750
+USA,CA,Chester,,7.250
+USA,CA,Chicago Park,,7.375
+USA,CA,Chico,,7.250
+USA,CA,Chilcoot,,7.250
+USA,CA,China Lake NWC (Ridgecrest),,7.250
+USA,CA,Chinese Camp,,7.250
+USA,CA,Chino Hills,,7.750
+USA,CA,Chino,,7.750
+USA,CA,Chiriaco Summit,,7.750
+USA,CA,Cholame,,7.250
+USA,CA,Chowchilla,,7.750
+USA,CA,Chualar,,7.250
+USA,CA,Chula Vista,,7.750
+USA,CA,Cima,,7.750
+USA,CA,Citrus Heights,,7.750
+USA,CA,City of Commerce,,8.250
+USA,CA,City of Industry,,8.250
+USA,CA,City Terrace,,8.250
+USA,CA,Claremont,,8.250
+USA,CA,Clarksburg,,7.250
+USA,CA,Clayton,,8.250
+USA,CA,Clear Creek,,7.250
+USA,CA,Clearlake Highlands,,7.250
+USA,CA,Clearlake Oaks,,7.250
+USA,CA,Clearlake Park (Clearlake),,7.750
+USA,CA,Clearlake,,7.750
+USA,CA,Clements,,7.750
+USA,CA,Clinter,,7.975
+USA,CA,Clio,,7.250
+USA,CA,Clipper Mills,,7.250
+USA,CA,Cloverdale,,7.750
+USA,CA,Clovis,,8.275
+USA,CA,Coachella,,7.750
+USA,CA,Coalinga,,7.975
+USA,CA,Coarsegold,,7.750
+USA,CA,Cobb,,7.250
+USA,CA,Cohasset,,7.250
+USA,CA,Cole,,8.250
+USA,CA,Coleville,,7.250
+USA,CA,Colfax,,7.250
+USA,CA,College City,,7.250
+USA,CA,College Grove Center,,7.750
+USA,CA,Colma,,8.250
+USA,CA,Coloma,,7.250
+USA,CA,Colorado,,7.750
+USA,CA,Colton,,7.750
+USA,CA,Columbia,,7.250
+USA,CA,Colusa,,7.250
+USA,CA,Commerce,,8.250
+USA,CA,Comptche,,7.250
+USA,CA,Compton,,8.250
+USA,CA,Concord,,8.250
+USA,CA,Cool,,7.250
+USA,CA,Copperopolis,,7.250
+USA,CA,Corcoran,,7.250
+USA,CA,Cornell,,8.250
+USA,CA,Corning,,7.250
+USA,CA,Corona Del Mar (Newport Beach),,7.750
+USA,CA,Corona,,7.750
+USA,CA,Coronado,,7.750
+USA,CA,Corralitos (Watsonville),,8.250
+USA,CA,Corte Madera,,7.750
+USA,CA,Coso Junction,,7.750
+USA,CA,Costa Mesa,,7.750
+USA,CA,Cotati,,7.750
+USA,CA,Cottonwood,,7.250
+USA,CA,Coulterville,,7.750
+USA,CA,Courtland,,7.750
+USA,CA,Covelo,,7.250
+USA,CA,Covina,,8.250
+USA,CA,Cowan Heights,,7.750
+USA,CA,Coyote,,8.250
+USA,CA,Crannell,,7.250
+USA,CA,Crenshaw,,8.250
+USA,CA,Crescent City,,7.250
+USA,CA,Crescent Mills,,7.250
+USA,CA,Cressey,,7.250
+USA,CA,Crest Park,,7.750
+USA,CA,Cresta Blanca,,8.750
+USA,CA,Crestline,,7.750
+USA,CA,Creston,,7.250
+USA,CA,Crockett,,8.250
+USA,CA,Cromberg,,7.250
+USA,CA,Cross Roads,,7.750
+USA,CA,Crowley Lake (Mammoth Lake),,7.250
+USA,CA,Crows Landing,,7.375
+USA,CA,Cucamonga (Rancho Cucamonga),,7.750
+USA,CA,Cudahy,,8.250
+USA,CA,Culver City,,8.250
+USA,CA,Cummings,,7.250
+USA,CA,Cupertino,,8.250
+USA,CA,Curry Village,,7.750
+USA,CA,Cutler,,7.750
+USA,CA,Cutten,,7.250
+USA,CA,Cuyama,,7.750
+USA,CA,Cypress,,7.750
+USA,CA,Daggett,,7.750
+USA,CA,Dairy Farm,,7.375
+USA,CA,Daly City,,8.250
+USA,CA,Dana Point,,7.750
+USA,CA,Danville,,8.250
+USA,CA,Dardanelle,,7.250
+USA,CA,Darwin,,7.750
+USA,CA,Davenport,,8.000
+USA,CA,Davis Creek,,7.250
+USA,CA,Davis,,7.750
+USA,CA,Death Valley,,7.750
+USA,CA,Death Valley Junction,,7.750
+USA,CA,Deer Park,,7.750
+USA,CA,Del Kern (Bakersfield),,7.250
+USA,CA,Del Mar Heights (Morro Bay),,7.750
+USA,CA,Del Mar,,7.750
+USA,CA,Del Monte Park (Monterey),,7.250
+USA,CA,Del Rey,,7.975
+USA,CA,Del Rey Oaks,,8.250
+USA,CA,Del Rosa,,7.750
+USA,CA,Del Sur,,8.250
+USA,CA,Delano,,8.250
+USA,CA,Deleven,,7.250
+USA,CA,Delhi,,7.250
+USA,CA,Denair,,7.375
+USA,CA,Denny,,7.250
+USA,CA,Descanso,,7.750
+USA,CA,Desert Center,,7.750
+USA,CA,Desert Hot Springs,,7.750
+USA,CA,Di Giorgio,,7.250
+USA,CA,Diablo,,8.250
+USA,CA,Diamond Bar,,8.250
+USA,CA,Diamond Springs,,7.250
+USA,CA,Dillon Beach,,7.750
+USA,CA,Dinkey Creek,,7.975
+USA,CA,Dinuba,,8.500
+USA,CA,Dixon,,7.375
+USA,CA,Dobbins,,7.250
+USA,CA,Dogtown,,7.750
+USA,CA,Dollar Ranch,,8.250
+USA,CA,Dorris,,7.250
+USA,CA,Dos Palos,,7.250
+USA,CA,Dos Rios,,7.250
+USA,CA,Douglas City,,7.250
+USA,CA,Douglas Flat,,7.250
+USA,CA,Downey,,8.250
+USA,CA,Downieville,,7.250
+USA,CA,Doyle,,7.250
+USA,CA,Drytown,,7.250
+USA,CA,Duarte,,8.250
+USA,CA,Dublin,,8.750
+USA,CA,Ducor,,7.750
+USA,CA,Dulzura,,7.750
+USA,CA,Duncans Mills,,7.750
+USA,CA,Dunlap,,7.975
+USA,CA,Dunnigan,,7.250
+USA,CA,Dunsmuir,,7.250
+USA,CA,Durham,,7.250
+USA,CA,Dutch Flat,,7.250
+USA,CA,Eagle Mountain,,7.750
+USA,CA,Eagle Rock (Los Angeles),,8.250
+USA,CA,Eagleville,,7.250
+USA,CA,Earlimart,,7.750
+USA,CA,Earp,,7.750
+USA,CA,East Highlands (Highland),,7.750
+USA,CA,East Irvine (Irvine),,7.750
+USA,CA,East Los Angeles,,8.250
+USA,CA,East Lynwood (Lynwood),,8.250
+USA,CA,East Nicolaus,,7.250
+USA,CA,East Palo Alto,,8.250
+USA,CA,East Porterville (Porterville),,8.250
+USA,CA,East Rancho Dominguez,,8.250
+USA,CA,East San Pedro (Los Angeles),,8.250
+USA,CA,Eastgate,,8.250
+USA,CA,Easton,,7.975
+USA,CA,Eastside,,7.750
+USA,CA,Echo Lake,,7.250
+USA,CA,Echo Park (Los Angeles),,8.250
+USA,CA,Edgemont (Moreno Valley),,7.750
+USA,CA,Edgewood,,7.250
+USA,CA,Edison,,7.250
+USA,CA,Edwards,,7.250
+USA,CA,Edwards A.F.B.,,7.250
+USA,CA,El Cajon,,8.250
+USA,CA,El Centro,,7.750
+USA,CA,El Cerrito,,8.250
+USA,CA,El Dorado,,7.250
+USA,CA,El Dorado Hills,,7.250
+USA,CA,El Granada,,8.250
+USA,CA,El Macero,,7.250
+USA,CA,El Modena,,7.750
+USA,CA,El Monte,,8.250
+USA,CA,El Nido,,7.250
+USA,CA,El Portal,,7.750
+USA,CA,El Segundo,,8.250
+USA,CA,El Sobrante,,8.250
+USA,CA,El Toro (Lake Forest),,7.750
+USA,CA,El Toro M.C.A.S.,,7.750
+USA,CA,El Verano,,7.750
+USA,CA,El Viejo,,7.375
+USA,CA,Eldridge,,7.750
+USA,CA,Elizabeth Lake,,8.250
+USA,CA,Elk,,7.250
+USA,CA,Elk Creek,,7.250
+USA,CA,Elk Grove,,7.750
+USA,CA,Elmira,,7.375
+USA,CA,Elmwood,,8.750
+USA,CA,Elverta,,7.750
+USA,CA,Emerald Hills (Redwood City),,8.250
+USA,CA,Emeryville,,8.750
+USA,CA,Emigrant Gap,,7.250
+USA,CA,Empire,,7.375
+USA,CA,Encinitas,,7.750
+USA,CA,Encino (Los Angeles),,8.250
+USA,CA,Enterprise,,7.250
+USA,CA,Escalon,,7.750
+USA,CA,Escondido,,7.750
+USA,CA,Esparto,,7.250
+USA,CA,Essex,,7.750
+USA,CA,Etiwanda (Rancho Cucamonga),,7.750
+USA,CA,Etna,,7.250
+USA,CA,Ettersburg,,7.250
+USA,CA,Eureka,,7.250
+USA,CA,Exeter,,7.750
+USA,CA,Fair Oaks,,7.750
+USA,CA,Fairfax,,7.750
+USA,CA,Fairfield,,7.375
+USA,CA,Fairmount,,8.250
+USA,CA,Fall River Mills,,7.250
+USA,CA,Fallbrook,,7.750
+USA,CA,Fallbrook Junction,,7.750
+USA,CA,Fallen Leaf,,7.250
+USA,CA,Fallon,,7.750
+USA,CA,Fancher,,7.975
+USA,CA,Farmersville,,8.250
+USA,CA,Farmington,,7.750
+USA,CA,Fawnskin,,7.750
+USA,CA,Feather Falls,,7.250
+USA,CA,Fellows,,7.250
+USA,CA,Felton,,8.000
+USA,CA,Fenner,,7.750
+USA,CA,Fernbridge (Fortuna),,7.250
+USA,CA,Ferndale,,7.250
+USA,CA,Fiddletown,,7.250
+USA,CA,Fields Landing,,7.250
+USA,CA,Fig Garden Village (Fresno),,7.975
+USA,CA,Fillmore,,7.250
+USA,CA,Finley,,7.250
+USA,CA,Firebaugh,,7.975
+USA,CA,Fish Camp,,7.750
+USA,CA,Five Points,,7.975
+USA,CA,Flinn Springs,,7.750
+USA,CA,Flintridge (LaCanada/ Flintridge),,8.250
+USA,CA,Florence,,8.250
+USA,CA,Floriston,,7.375
+USA,CA,Flournoy,,7.250
+USA,CA,Folsom,,7.750
+USA,CA,Fontana,,7.750
+USA,CA,Foothill Ranch,,7.750
+USA,CA,Forbestown,,7.250
+USA,CA,Forest Falls,,7.750
+USA,CA,Forest Glen,,7.250
+USA,CA,Forest Knolls,,7.750
+USA,CA,Forest Park,,8.250
+USA,CA,Forest Ranch,,7.250
+USA,CA,Foresthill,,7.250
+USA,CA,Forestville,,7.750
+USA,CA,Forks of Salmon,,7.250
+USA,CA,Fort Bidwell,,7.250
+USA,CA,Fort Bragg,,7.750
+USA,CA,Fort Dick,,7.250
+USA,CA,Fort Irwin,,7.750
+USA,CA,Fort Jones,,7.250
+USA,CA,Fort Ord (Seaside),,7.250
+USA,CA,Fort Seward,,7.250
+USA,CA,Fortuna,,7.250
+USA,CA,Foster City,,8.250
+USA,CA,Fountain Valley,,7.750
+USA,CA,Fowler,,7.975
+USA,CA,Frazier Park,,7.250
+USA,CA,Freedom,,8.000
+USA,CA,Freeport,,7.750
+USA,CA,Freestone,,7.750
+USA,CA,Fremont,,8.750
+USA,CA,French Camp,,7.750
+USA,CA,French Gulch,,7.250
+USA,CA,Freshwater,,7.250
+USA,CA,Fresno,,7.975
+USA,CA,Friant,,7.975
+USA,CA,Friendly Valley (Santa Clarita),,8.250
+USA,CA,Frontera,,7.750
+USA,CA,Fullerton,,7.750
+USA,CA,Fulton,,7.750
+USA,CA,Galt,,7.750
+USA,CA,Garberville,,7.250
+USA,CA,Garden Grove,,7.750
+USA,CA,Garden Valley,,7.250
+USA,CA,Gardena,,8.250
+USA,CA,Garey,,7.750
+USA,CA,Garnet,,7.750
+USA,CA,Gasquet,,7.250
+USA,CA,Gaviota,,7.750
+USA,CA,Gazelle,,7.250
+USA,CA,George A.F.B.,,7.750
+USA,CA,Georgetown,,7.250
+USA,CA,Gerber,,7.250
+USA,CA,Geyserville,,7.750
+USA,CA,Giant Forest,,7.750
+USA,CA,Gillman Hot Springs,,7.750
+USA,CA,Gilroy,,8.250
+USA,CA,Glassell Park (Los Angeles),,8.250
+USA,CA,Glen Avon,,7.750
+USA,CA,Glen Ellen,,7.750
+USA,CA,Glenburn,,7.250
+USA,CA,Glencoe,,7.250
+USA,CA,Glendale,,8.250
+USA,CA,Glendora,,8.250
+USA,CA,Glenhaven,,7.250
+USA,CA,Glenn,,7.250
+USA,CA,Glennville,,7.250
+USA,CA,Gold River (Rancho Cordova),,7.750
+USA,CA,Gold Run,,7.250
+USA,CA,Golden Hills,,7.250
+USA,CA,Goleta,,7.750
+USA,CA,Gonzales,,7.250
+USA,CA,Goodyears Bar,,7.250
+USA,CA,Gorman,,8.250
+USA,CA,Goshen,,7.750
+USA,CA,Government Island,,8.750
+USA,CA,Graeagle,,7.250
+USA,CA,Granada Hills (Los Angeles),,8.250
+USA,CA,Grand Terrace,,7.750
+USA,CA,Granite Bay,,7.250
+USA,CA,Grass Valley,,7.375
+USA,CA,Graton,,7.750
+USA,CA,Green Valley,,8.250
+USA,CA,Green Valley Lake,,7.750
+USA,CA,Greenacres,,7.250
+USA,CA,Greenbrae (Larkspur),,7.750
+USA,CA,Greenfield,,7.250
+USA,CA,Greenview,,7.250
+USA,CA,Greenville,,7.250
+USA,CA,Greenwood,,7.250
+USA,CA,Grenada,,7.250
+USA,CA,Gridley,,7.250
+USA,CA,Grimes,,7.250
+USA,CA,Grizzly Flats,,7.250
+USA,CA,Groveland,,7.250
+USA,CA,Grover Beach,,7.750
+USA,CA,Guadalupe,,7.750
+USA,CA,Gualala,,7.250
+USA,CA,Guasti (Ontario),,7.750
+USA,CA,Guatay,,7.750
+USA,CA,Guerneville,,7.750
+USA,CA,Guinda,,7.250
+USA,CA,Gustine,,7.250
+USA,CA,Hacienda Heights,,8.250
+USA,CA,Halcyon,,7.250
+USA,CA,Half Moon Bay,,8.250
+USA,CA,Hamilton A.F.B. (Novato),,7.750
+USA,CA,Hamilton City,,7.250
+USA,CA,Hanford,,7.250
+USA,CA,Happy Camp,,7.250
+USA,CA,Harbor City (Los Angeles),,8.250
+USA,CA,Harmony,,7.250
+USA,CA,Harris,,7.250
+USA,CA,Hat Creek,,7.250
+USA,CA,Hathaway Pines,,7.250
+USA,CA,Havasu Lake,,7.750
+USA,CA,Hawaiian Gardens,,8.250
+USA,CA,Hawthorne,,8.250
+USA,CA,Hayfork,,7.250
+USA,CA,Hayward,,8.750
+USA,CA,Hazard,,8.250
+USA,CA,Healdsburg,,7.750
+USA,CA,Heber,,7.750
+USA,CA,Helena,,7.250
+USA,CA,Helendale,,7.750
+USA,CA,Helm,,7.975
+USA,CA,Hemet,,7.750
+USA,CA,Herald,,7.750
+USA,CA,Hercules,,8.250
+USA,CA,Herlong,,7.250
+USA,CA,Hermosa Beach,,8.250
+USA,CA,Herndon,,7.975
+USA,CA,Hesperia,,7.750
+USA,CA,Heyer,,8.750
+USA,CA,Hickman,,7.375
+USA,CA,Hidden Hills,,8.250
+USA,CA,Highgrove,,7.750
+USA,CA,Highland Park (Los Angeles),,8.250
+USA,CA,Highland,,7.750
+USA,CA,Highway City (Fresno),,7.975
+USA,CA,Hillcrest (San Diego),,7.750
+USA,CA,Hillsborough,,8.250
+USA,CA,Hillsdale (San Mateo),,8.250
+USA,CA,Hilmar,,7.250
+USA,CA,Hilt,,7.250
+USA,CA,Hinkley,,7.750
+USA,CA,Hobergs,,7.250
+USA,CA,Hollister,,8.250
+USA,CA,Hollywood (Los Angeles),,8.250
+USA,CA,Holmes,,7.250
+USA,CA,Holt,,7.750
+USA,CA,Holtville,,7.750
+USA,CA,Holy City,,8.250
+USA,CA,Homeland,,7.750
+USA,CA,Homestead,,7.250
+USA,CA,Homestead,,7.750
+USA,CA,Homewood,,7.250
+USA,CA,Honby,,8.250
+USA,CA,Honeydew,,7.250
+USA,CA,Hood,,7.750
+USA,CA,Hoopa,,7.250
+USA,CA,Hope Valley (Forest Camp),,7.250
+USA,CA,Hopland,,7.250
+USA,CA,Hornbrook,,7.250
+USA,CA,Hornitos,,7.750
+USA,CA,Horse Creek,,7.250
+USA,CA,Horse Lake,,7.250
+USA,CA,Hughson,,7.375
+USA,CA,Hume,,7.975
+USA,CA,Huntington,,7.750
+USA,CA,Huntington Beach,,7.750
+USA,CA,Huntington Lake,,7.975
+USA,CA,Huntington Park,,8.250
+USA,CA,Huron,,7.975
+USA,CA,Hyampom,,7.250
+USA,CA,Hyde Park (Los Angeles),,8.250
+USA,CA,Hydesville,,7.250
+USA,CA,Idria,,7.250
+USA,CA,Idyllwild,,7.750
+USA,CA,Ignacio (Novato),,7.750
+USA,CA,Igo,,7.250
+USA,CA,Imola (Napa),,7.750
+USA,CA,Imperial Beach,,7.750
+USA,CA,Imperial,,7.750
+USA,CA,Independence,,7.750
+USA,CA,Indian Wells,,7.750
+USA,CA,Indio,,7.750
+USA,CA,Industry,,8.250
+USA,CA,Inglewood,,8.750
+USA,CA,Inverness,,7.750
+USA,CA,Inyo,,7.750
+USA,CA,Inyokern,,7.250
+USA,CA,Ione,,7.250
+USA,CA,Iowa Hill,,7.250
+USA,CA,Irvine,,7.750
+USA,CA,Irwindale,,8.250
+USA,CA,Isla Vista,,7.750
+USA,CA,Island Mountain,,7.250
+USA,CA,Isleton,,7.750
+USA,CA,Ivanhoe,,7.750
+USA,CA,Ivanpah,,7.750
+USA,CA,Jackson,,7.250
+USA,CA,Jacumba,,7.750
+USA,CA,Jamacha,,7.750
+USA,CA,Jamestown,,7.250
+USA,CA,Jamul,,7.750
+USA,CA,Janesville,,7.250
+USA,CA,Jenner,,7.750
+USA,CA,Johannesburg,,7.250
+USA,CA,Johnsondale,,7.750
+USA,CA,Johnstonville,,7.250
+USA,CA,Johnstown,,7.750
+USA,CA,Jolon,,7.250
+USA,CA,Joshua Tree,,7.750
+USA,CA,Julian,,7.750
+USA,CA,Junction City,,7.250
+USA,CA,June Lake,,7.250
+USA,CA,Juniper,,7.250
+USA,CA,Kagel Canyon,,8.250
+USA,CA,Kaweah,,7.750
+USA,CA,Keddie,,7.250
+USA,CA,Keeler,,7.750
+USA,CA,Keene,,7.250
+USA,CA,Kelsey,,7.250
+USA,CA,Kelseyville,,7.250
+USA,CA,Kelso,,7.750
+USA,CA,Kensington,,8.250
+USA,CA,Kentfield,,7.750
+USA,CA,Kenwood,,7.750
+USA,CA,Kerman,,7.975
+USA,CA,Kernville,,7.250
+USA,CA,Keswick,,7.250
+USA,CA,Kettleman City,,7.250
+USA,CA,Keyes,,7.375
+USA,CA,King City,,7.250
+USA,CA,Kings Beach,,7.250
+USA,CA,Kings Canyon National Park,,7.750
+USA,CA,Kingsburg,,7.975
+USA,CA,Kinyon,,7.250
+USA,CA,Kirkwood,,7.250
+USA,CA,Kit Carson,,7.250
+USA,CA,Klamath,,7.250
+USA,CA,Klamath River,,7.250
+USA,CA,Kneeland,,7.250
+USA,CA,Knights Ferry,,7.375
+USA,CA,Knights Landing,,7.250
+USA,CA,Knightsen,,8.250
+USA,CA,Korbel,,7.250
+USA,CA,Korbel,,7.750
+USA,CA,Kyburz,,7.250
+USA,CA,L.A. Airport (Los Angeles),,8.250
+USA,CA,La Canada- Flintridge,,8.250
+USA,CA,La Crescenta,,8.250
+USA,CA,La Cresta Village,,7.250
+USA,CA,La Grange,,7.375
+USA,CA,La Habra Heights,,8.250
+USA,CA,La Habra,,7.750
+USA,CA,La Honda,,8.250
+USA,CA,La Jolla (San Diego),,7.750
+USA,CA,La Mesa,,7.750
+USA,CA,La Mirada,,8.250
+USA,CA,La Palma,,7.750
+USA,CA,La Porte,,7.250
+USA,CA,La Puente,,8.250
+USA,CA,La Quinta,,7.750
+USA,CA,La Selva Beach,,8.000
+USA,CA,La Verne,,8.250
+USA,CA,La Vina,,8.250
+USA,CA,Ladera,,8.250
+USA,CA,Ladera Heights,,8.250
+USA,CA,Ladera Ranch,,7.750
+USA,CA,Lafayette,,8.250
+USA,CA,Laguna Beach,,8.250
+USA,CA,Laguna Hills,,7.750
+USA,CA,Laguna Niguel,,7.750
+USA,CA,Laguna Woods,,7.750
+USA,CA,Lagunitas,,7.750
+USA,CA,Lake Alpine,,7.250
+USA,CA,Lake Arrowhead,,7.750
+USA,CA,Lake City,,7.250
+USA,CA,Lake City,,7.375
+USA,CA,Lake Elsinore,,7.750
+USA,CA,Lake Forest (El Toro),,7.750
+USA,CA,Lake Hughes,,8.250
+USA,CA,Lake Isabella,,7.250
+USA,CA,Lake Los Angeles,,8.250
+USA,CA,Lake Mary,,7.250
+USA,CA,Lake San Marcos,,7.750
+USA,CA,Lake Shastina,,7.250
+USA,CA,Lake Sherwood,,7.250
+USA,CA,Lakehead,,7.250
+USA,CA,Lakeport,,7.750
+USA,CA,Lakeshore,,7.975
+USA,CA,Lakeside,,7.750
+USA,CA,Lakeview,,7.750
+USA,CA,Lakeview Terrace (Los Angeles),,8.250
+USA,CA,Lakewood,,8.250
+USA,CA,Lamont,,7.250
+USA,CA,Lancaster,,8.250
+USA,CA,Landers,,7.750
+USA,CA,Landscape,,8.750
+USA,CA,Lang,,8.250
+USA,CA,Larkfield,,7.750
+USA,CA,Larkspur,,7.750
+USA,CA,Larwin Plaza,,7.375
+USA,CA,Lathrop,,7.750
+USA,CA,Laton,,7.975
+USA,CA,Lawndale,,8.250
+USA,CA,Laws,,7.750
+USA,CA,Laytonville,,7.250
+USA,CA,Le Grand (Also Legrand),,7.250
+USA,CA,Lebec,,7.250
+USA,CA,Lee Vining,,7.250
+USA,CA,Leggett,,7.250
+USA,CA,Leisure World,,7.750
+USA,CA,Leisure World (Seal Beach),,7.750
+USA,CA,Lemon Grove,,7.750
+USA,CA,Lemoncove,,7.750
+USA,CA,Lemoore,,7.250
+USA,CA,Lennox,,8.250
+USA,CA,Lenwood,,7.750
+USA,CA,Leona Valley,,8.250
+USA,CA,Leucadia (Encinitas),,7.750
+USA,CA,Lewiston,,7.250
+USA,CA,Liberty Farms,,7.375
+USA,CA,Likely,,7.250
+USA,CA,Lincoln Acres,,7.750
+USA,CA,Lincoln Heights (Los Angeles),,8.250
+USA,CA,Lincoln Village,,7.750
+USA,CA,Lincoln,,7.250
+USA,CA,Linda,,7.250
+USA,CA,Linden,,7.750
+USA,CA,Lindsay,,7.750
+USA,CA,Linnell,,7.750
+USA,CA,Litchfield,,7.250
+USA,CA,Little Lake,,7.750
+USA,CA,Little Norway,,7.250
+USA,CA,Little Valley,,7.250
+USA,CA,Littleriver,,7.250
+USA,CA,Littlerock (Also Little Rock),,8.250
+USA,CA,Live Oak,,7.250
+USA,CA,Livermore,,8.750
+USA,CA,Livingston,,7.250
+USA,CA,Llano,,8.250
+USA,CA,Loch Lomond,,7.250
+USA,CA,Locke,,7.750
+USA,CA,Lockeford,,7.750
+USA,CA,Lockheed,,8.000
+USA,CA,Lockwood,,7.250
+USA,CA,Lodi,,7.750
+USA,CA,Loleta,,7.250
+USA,CA,Loma Linda,,7.750
+USA,CA,Loma Mar,,8.250
+USA,CA,Loma Rica,,7.250
+USA,CA,Lomita,,8.250
+USA,CA,Lompoc,,7.750
+USA,CA,London,,7.750
+USA,CA,Lone Pine,,7.750
+USA,CA,Long Barn,,7.250
+USA,CA,Long Beach,,8.250
+USA,CA,Longview,,8.250
+USA,CA,Lookout,,7.250
+USA,CA,Loomis,,7.250
+USA,CA,Lorre Estates,,8.250
+USA,CA,Los Alamitos,,7.750
+USA,CA,Los Alamos,,7.750
+USA,CA,Los Altos Hills,,8.250
+USA,CA,Los Altos,,8.250
+USA,CA|California,Los Angeles,,8.250
+USA,CA,Los Banos,,7.750
+USA,CA,Los Gatos,,8.250
+USA,CA,Los Molinos,,7.250
+USA,CA,Los Nietos,,8.250
+USA,CA,Los Olivos,,7.750
+USA,CA,Los Osos,,7.250
+USA,CA,Los Padres,,7.250
+USA,CA,Los Serranos (Chino Hills),,7.750
+USA,CA,Lost Hills,,7.250
+USA,CA,Lost Lake,,7.750
+USA,CA,Lotus,,7.250
+USA,CA,Lower Lake,,7.250
+USA,CA,Loyalton,,7.250
+USA,CA,Lucerne,,7.250
+USA,CA,Lucerne Valley,,7.750
+USA,CA,Lucia,,7.250
+USA,CA,Ludlow,,7.750
+USA,CA,Lugo,,8.250
+USA,CA,Luther Burbank (Santa Rosa),,8.000
+USA,CA,Lynwood,,8.250
+USA,CA,Lytle Creek,,7.750
+USA,CA,Macdoel,,7.250
+USA,CA,Maclay,,8.250
+USA,CA,Mad River,,7.250
+USA,CA,Madeline,,7.250
+USA,CA,Madera,,7.750
+USA,CA,Madison,,7.250
+USA,CA,Magalia,,7.250
+USA,CA,Malaga,,7.975
+USA,CA,Malibu,,8.250
+USA,CA,Mammoth Lakes,,7.250
+USA,CA,Manhattan Beach,,8.250
+USA,CA,Manteca,,8.250
+USA,CA,Manton,,7.250
+USA,CA,Manzanita Lake,,7.250
+USA,CA,Mar Vista,,8.250
+USA,CA,Marcelina,,8.250
+USA,CA,March A.F.B.,,7.750
+USA,CA,Mare Island (Vallejo),,7.375
+USA,CA,Maricopa,,7.250
+USA,CA,Marin City,,7.750
+USA,CA,Marina Del Rey,,8.250
+USA,CA,Marina,,7.250
+USA,CA,Marine Corps (Twentynine Palms),,7.750
+USA,CA,Mariner,,7.750
+USA,CA,Mariposa,,7.750
+USA,CA,Markleeville,,7.250
+USA,CA,Marsh Manor,,8.250
+USA,CA,Marshall,,7.750
+USA,CA,Martell,,7.250
+USA,CA,Martinez,,8.250
+USA,CA,Marysville,,7.250
+USA,CA,Mather,,7.250
+USA,CA,Mather,,7.750
+USA,CA,Maxwell,,7.250
+USA,CA,Maywood,,8.250
+USA,CA,McArthur,,7.250
+USA,CA,McClellan,,7.750
+USA,CA,McCloud,,7.250
+USA,CA,McFarland,,7.250
+USA,CA,McKinleyville,,7.250
+USA,CA,McKittrick,,7.250
+USA,CA,Mead Valley,,7.750
+USA,CA,Meadow Valley,,7.250
+USA,CA,Meadow Vista,,7.250
+USA,CA,Meadowbrook,,7.750
+USA,CA,Mecca,,7.750
+USA,CA,Meeks Bay,,7.250
+USA,CA,Meiners Oaks,,7.250
+USA,CA,Mendocino,,7.250
+USA,CA,Mendota,,7.975
+USA,CA,Menifee,,7.750
+USA,CA,Menlo Park,,8.250
+USA,CA,Mentone,,7.750
+USA,CA,Merced,,7.750
+USA,CA,Meridian,,7.250
+USA,CA,Mettler,,7.250
+USA,CA,Meyers,,7.250
+USA,CA,Middletown,,7.250
+USA,CA,Midland,,7.750
+USA,CA,Midpines,,7.750
+USA,CA,Midway City,,7.750
+USA,CA,Milford,,7.250
+USA,CA,Mill Creek,,7.250
+USA,CA,Mill Valley,,7.750
+USA,CA,Millbrae,,8.250
+USA,CA,Millville,,7.250
+USA,CA,Milpitas,,8.250
+USA,CA,Mineral,,7.250
+USA,CA,Mineral King,,7.750
+USA,CA,Mint Canyon,,8.250
+USA,CA,Mira Loma,,7.750
+USA,CA,Mira Vista,,8.250
+USA,CA,Miracle Hot Springs,,7.250
+USA,CA,Miramar (San Diego),,7.750
+USA,CA,Miramonte,,7.975
+USA,CA,Miranda,,7.250
+USA,CA,Mission Hills (Los Angeles),,8.250
+USA,CA,Mission Viejo,,7.750
+USA,CA,Mi-Wuk Village,,7.250
+USA,CA,Moccasin,,7.250
+USA,CA,Modesto,,7.375
+USA,CA,Moffett Field,,8.250
+USA,CA,Mojave,,7.250
+USA,CA,Mokelumne Hill,,7.250
+USA,CA,Moneta,,8.250
+USA,CA,Mono Hot Springs,,7.975
+USA,CA,Mono Lake,,7.250
+USA,CA,Monolith,,7.250
+USA,CA,Monrovia,,8.250
+USA,CA,Monta Vista,,8.250
+USA,CA,Montague,,7.250
+USA,CA,Montalvo (Ventura),,7.250
+USA,CA,Montara,,8.250
+USA,CA,Montclair,,8.000
+USA,CA,Monte Rio,,7.750
+USA,CA,Monte Sereno,,8.250
+USA,CA,Montebello,,8.250
+USA,CA,Montecito,,7.750
+USA,CA,Monterey Bay Academy,,8.000
+USA,CA,Monterey Park,,8.250
+USA,CA,Monterey,,7.250
+USA,CA,Montgomery Creek,,7.250
+USA,CA,Montrose,,8.250
+USA,CA,Mooney,,7.750
+USA,CA,Moonridge,,7.750
+USA,CA,Moorpark,,7.250
+USA,CA,Moraga,,8.250
+USA,CA,Moreno Valley,,7.750
+USA,CA,Morgan Hill,,8.250
+USA,CA,Morongo Valley,,7.750
+USA,CA,Morro Bay,,7.750
+USA,CA,Morro Plaza,,7.250
+USA,CA,Moss Beach,,8.250
+USA,CA,Moss Landing,,7.250
+USA,CA,Mount Hamilton,,8.250
+USA,CA,Mount Hebron,,7.250
+USA,CA,Mount Hermon,,8.000
+USA,CA,Mount Laguna,,7.750
+USA,CA,Mount Shasta,,7.250
+USA,CA,Mount Wilson,,8.250
+USA,CA,Mountain Center,,7.750
+USA,CA,Mountain Mesa,,7.250
+USA,CA,Mountain Pass,,7.750
+USA,CA,Mountain Ranch,,7.250
+USA,CA,Mountain View,,8.250
+USA,CA,Mt. Aukum,,7.250
+USA,CA,Mt. Baldy,,7.750
+USA,CA,Murphys,,7.250
+USA,CA,Murrieta,,7.750
+USA,CA,Muscoy,,7.750
+USA,CA,Myers Flat,,7.250
+USA,CA,Napa,,7.750
+USA,CA,Naples,,8.250
+USA,CA,Nashville,,7.250
+USA,CA,National City,,8.750
+USA,CA,Naval (Port Hueneme),,7.250
+USA,CA,Naval (San Diego),,7.750
+USA,CA,Naval Air Station (Alameda),,8.750
+USA,CA,Naval Air Station (Coronado),,7.750
+USA,CA,Naval Air Station (Lemoore),,7.250
+USA,CA,Naval Hospital (Oakland),,8.750
+USA,CA,Naval Hospital (San Diego),,7.750
+USA,CA,Naval Supply Center (Oakland),,8.750
+USA,CA,Naval Training Center (San Diego),,7.750
+USA,CA,Navarro,,7.250
+USA,CA,Needles,,7.750
+USA,CA,Nelson,,7.250
+USA,CA,Nevada City,,7.875
+USA,CA,New Almaden,,8.250
+USA,CA,New Cuyama,,7.750
+USA,CA,New Idria,,7.250
+USA,CA,Newark,,8.750
+USA,CA,Newberry,,7.750
+USA,CA,Newberry Springs,,7.750
+USA,CA,Newbury Park (Thousand Oaks),,7.250
+USA,CA,Newcastle,,7.250
+USA,CA,Newhall (Santa Clarita),,8.250
+USA,CA,Newman,,7.375
+USA,CA,Newport Beach,,7.750
+USA,CA,Nicasio,,7.750
+USA,CA,Nice,,7.250
+USA,CA,Nicolaus,,7.250
+USA,CA,Niland,,7.750
+USA,CA,Nipomo,,7.250
+USA,CA,Nipton,,7.750
+USA,CA,Norco,,7.750
+USA,CA,Norden,,7.375
+USA,CA,North Edwards,,7.250
+USA,CA,North Fork,,7.750
+USA,CA,North Gardena,,8.250
+USA,CA,North Highlands,,7.750
+USA,CA,North Hills (Los Angeles),,8.250
+USA,CA,North Hollywood (Los Angeles),,8.250
+USA,CA,North Palm Springs,,7.750
+USA,CA,North San Juan,,7.375
+USA,CA,North Shore,,7.750
+USA,CA,Northridge (Los Angeles),,8.250
+USA,CA,Norton A.F.B. (San Bernardino),,8.000
+USA,CA,Norwalk,,8.250
+USA,CA,Novato,,7.750
+USA,CA,Nubieber,,7.250
+USA,CA,Nuevo,,7.750
+USA,CA,Nyeland Acres,,7.250
+USA,CA,Oak Park,,7.250
+USA,CA,Oak Run,,7.250
+USA,CA,Oak View,,7.250
+USA,CA,Oakdale,,7.375
+USA,CA,Oakhurst,,7.750
+USA,CA,Oakland,,8.750
+USA,CA,Oakley,,8.250
+USA,CA,Oakville,,7.750
+USA,CA,Oasis,,7.750
+USA,CA,Oban,,8.250
+USA,CA,O'Brien,,7.250
+USA,CA,Occidental,,7.750
+USA,CA,Oceano,,7.250
+USA,CA,Oceanside,,7.750
+USA,CA,Ocotillo,,7.750
+USA,CA,Ocotillo Wells,,7.750
+USA,CA,Oildale,,7.250
+USA,CA,Ojai,,7.250
+USA,CA,Olancha,,7.750
+USA,CA,Old Station,,7.250
+USA,CA,Olema,,7.750
+USA,CA,Olinda,,7.250
+USA,CA,Olive View (Los Angeles),,8.250
+USA,CA,Olivehurst,,7.250
+USA,CA,Olivenhain (Encinitas),,7.750
+USA,CA,Olympic Valley,,7.250
+USA,CA,Omo Ranch,,7.250
+USA,CA,O'Neals,,7.750
+USA,CA,Ono,,7.250
+USA,CA,Ontario,,7.750
+USA,CA,Onyx,,7.250
+USA,CA,Opal Cliffs,,8.000
+USA,CA,Orange Cove,,7.975
+USA,CA,Orange,,7.750
+USA,CA,Orangevale,,7.750
+USA,CA,Orcutt,,7.750
+USA,CA,Ordbend,,7.250
+USA,CA,Oregon House,,7.250
+USA,CA,Orick,,7.250
+USA,CA,Orinda,,8.250
+USA,CA,Orland,,7.250
+USA,CA,Orleans,,7.250
+USA,CA,Oro Grande,,7.750
+USA,CA,Orosi,,7.750
+USA,CA,Oroville,,7.250
+USA,CA,Otay (Chula Vista),,7.750
+USA,CA,Oxnard,,7.250
+USA,CA,Pacheco,,8.250
+USA,CA,Pacific Grove,,7.250
+USA,CA,Pacific House,,7.250
+USA,CA,Pacific Palisades (Los Angeles),,8.250
+USA,CA,Pacifica,,8.250
+USA,CA,Pacoima (Los Angeles),,8.250
+USA,CA,Paicines,,7.250
+USA,CA,Pajaro,,7.250
+USA,CA,Pala,,7.750
+USA,CA,Palermo,,7.250
+USA,CA,Pallett,,8.250
+USA,CA,Palm City,,7.750
+USA,CA,Palm City (San Diego),,7.750
+USA,CA,Palm Desert,,7.750
+USA,CA,Palm Springs,,7.750
+USA,CA,Palmdale,,8.250
+USA,CA,Palo (Vista),,8.250
+USA,CA,Palo Alto,,8.250
+USA,CA,Palo Cedro,,7.250
+USA,CA,Palo Verde,,7.750
+USA,CA,Palomar Mountain,,7.750
+USA,CA,Palos Verdes Estates,,8.250
+USA,CA,Palos Verdes/Peninsula,,8.250
+USA,CA,Panorama City (Los Angeles),,8.250
+USA,CA,Paradise,,7.250
+USA,CA,Paramount,,8.250
+USA,CA,Parker Dam,,7.750
+USA,CA,Parkfield,,7.250
+USA,CA,Parlier,,7.975
+USA,CA,Pasadena,,8.250
+USA,CA,Paskenta,,7.250
+USA,CA,Paso Robles,,7.250
+USA,CA,Patterson,,7.375
+USA,CA,Patton,,7.750
+USA,CA,Pauma Valley,,7.750
+USA,CA,Paynes Creek,,7.250
+USA,CA,Pearblossom,,8.250
+USA,CA,Pearland,,8.250
+USA,CA,Pebble Beach,,7.250
+USA,CA,Pedley,,7.750
+USA,CA,Peninsula Village,,7.250
+USA,CA,Penn Valley,,7.375
+USA,CA,Penngrove,,7.750
+USA,CA,Penryn,,7.250
+USA,CA,Pepperwood,,7.250
+USA,CA,Permanente,,8.250
+USA,CA,Perris,,7.750
+USA,CA,Perry (Whittier),,8.250
+USA,CA,Pescadero,,8.250
+USA,CA,Petaluma,,7.750
+USA,CA,Petrolia,,7.250
+USA,CA,Phelan,,7.750
+USA,CA,Phillipsville,,7.250
+USA,CA,Philo,,7.250
+USA,CA,Pico Rivera,,8.250
+USA,CA,Piedmont,,8.750
+USA,CA,Piedra,,7.975
+USA,CA,Piercy,,7.250
+USA,CA,Pilot Hill,,7.250
+USA,CA,Pine Grove,,7.250
+USA,CA,Pine Valley,,7.750
+USA,CA,Pinecrest,,7.250
+USA,CA,Pinedale (Fresno),,7.975
+USA,CA,Pinetree,,8.250
+USA,CA,Pinole,,8.750
+USA,CA,Pinon Hills,,7.750
+USA,CA,Pioneer,,7.250
+USA,CA,Pioneertown,,7.750
+USA,CA,Piru,,7.250
+USA,CA,Pismo Beach,,7.250
+USA,CA,Pittsburg,,8.250
+USA,CA,Pixley,,7.750
+USA,CA,Placentia,,7.750
+USA,CA,Placerville,,7.500
+USA,CA,Plainview,,7.750
+USA,CA,Planada,,7.250
+USA,CA,Plaster City,,7.750
+USA,CA,Platina,,7.250
+USA,CA,Playa Del Rey (Los Angeles),,8.250
+USA,CA,Pleasant Grove,,7.250
+USA,CA,Pleasant Hill,,8.250
+USA,CA,Pleasanton,,8.750
+USA,CA,Plymouth,,7.250
+USA,CA,Point Arena,,7.750
+USA,CA,Point Mugu,,7.250
+USA,CA,Point Pittsburg (Pittsburg),,8.250
+USA,CA,Point Reyes Station,,7.750
+USA,CA,Pollock Pines,,7.250
+USA,CA,Pomona,,8.250
+USA,CA,Pond,,7.250
+USA,CA,Pondosa,,7.250
+USA,CA,Pope Valley,,7.750
+USA,CA,Poplar,,7.750
+USA,CA,Port Costa,,8.250
+USA,CA,Port Hueneme,,7.250
+USA,CA,Porter Ranch (Los Angeles),,8.250
+USA,CA,Porterville,,8.250
+USA,CA,Portola Valley,,8.250
+USA,CA,Portola,,7.250
+USA,CA,Portuguese Bend (Rancho Palos Verdes),,8.250
+USA,CA,Posey,,7.750
+USA,CA,Potrero,,7.750
+USA,CA,Potter Valley,,7.250
+USA,CA,Poway,,7.750
+USA,CA,Prather,,7.975
+USA,CA,Presidio (San Francisco),,8.500
+USA,CA,Presidio of Monterey (Monterey),,7.250
+USA,CA,Priest Valley,,7.250
+USA,CA,Princeton,,7.250
+USA,CA,Proberta,,7.250
+USA,CA,Project City,,7.250
+USA,CA,Prunedale,,7.250
+USA,CA,Pt. Dume,,8.250
+USA,CA,Pulga,,7.250
+USA,CA,Pumpkin Center,,7.250
+USA,CA,Quail Valley,,7.750
+USA,CA,Quartz Hill,,8.250
+USA,CA,Quincy,,7.250
+USA,CA,Rackerby,,7.250
+USA,CA,Rail Road Flat,,7.250
+USA,CA,Rainbow,,7.750
+USA,CA,Raisin City,,7.975
+USA,CA,Ramona,,7.750
+USA,CA,Ranchita,,7.750
+USA,CA,Rancho Bernardo (San Diego),,7.750
+USA,CA,Rancho California,,7.750
+USA,CA,Rancho Cordova,,7.750
+USA,CA,Rancho Cucamonga,,7.750
+USA,CA,Rancho Dominguez,,8.250
+USA,CA,Rancho Mirage,,7.750
+USA,CA,Rancho Murieta,,7.750
+USA,CA,Rancho Palos Verdes,,8.250
+USA,CA,Rancho Park (Los Angeles),,8.250
+USA,CA,Rancho Santa Fe,,7.750
+USA,CA,Rancho Santa Margarita,,7.750
+USA,CA,Randsburg,,7.250
+USA,CA,Ravendale,,7.250
+USA,CA,Ravenna,,8.250
+USA,CA,Raymond,,7.750
+USA,CA,Red Bluff,,7.250
+USA,CA,Red Mountain,,7.750
+USA,CA,Red Top,,7.750
+USA,CA,Redcrest,,7.250
+USA,CA,Redding,,7.250
+USA,CA,Redlands,,7.750
+USA,CA,Redondo Beach,,8.250
+USA,CA,Redway,,7.250
+USA,CA,Redwood City,,8.250
+USA,CA,Redwood Estates,,8.250
+USA,CA,Redwood Valley,,7.250
+USA,CA,Reedley,,7.975
+USA,CA,Refugio Beach,,7.750
+USA,CA,Represa (Folsom Prison),,7.750
+USA,CA,Requa,,7.250
+USA,CA,Rescue,,7.250
+USA,CA,Reseda (Los Angeles),,8.250
+USA,CA,Rheem Valley (Moraga),,8.250
+USA,CA,Rialto,,7.750
+USA,CA,Richardson Grove,,7.250
+USA,CA,Richardson Springs,,7.250
+USA,CA,Richfield,,7.250
+USA,CA,Richgrove,,7.750
+USA,CA,Richmond,,8.750
+USA,CA,Richvale,,7.250
+USA,CA,Ridgecrest,,7.250
+USA,CA,Rimforest,,7.750
+USA,CA,Rimpau (Los Angeles),,8.250
+USA,CA,Rio Bravo (Bakersfield),,7.250
+USA,CA,Rio Dell,,7.250
+USA,CA,Rio Linda,,7.750
+USA,CA,Rio Nido,,7.750
+USA,CA,Rio Oso,,7.250
+USA,CA,Rio Vista,,7.375
+USA,CA,Ripley,,7.750
+USA,CA,Ripon,,7.750
+USA,CA,River Pines,,7.250
+USA,CA,Riverbank,,7.375
+USA,CA,Riverdale,,7.975
+USA,CA,Riverside,,7.750
+USA,CA,Robbins,,7.250
+USA,CA,Rocklin,,7.250
+USA,CA,Rodeo,,8.250
+USA,CA,Rohnert Park,,7.750
+USA,CA,Rohnerville,,7.250
+USA,CA,Rolling Hills Estates,,8.250
+USA,CA,Rolling Hills,,8.250
+USA,CA,Romoland,,7.750
+USA,CA,Rosamond,,7.250
+USA,CA,Rose Bowl (Pasadena),,8.250
+USA,CA,Roseland (Santa Rosa),,8.000
+USA,CA,Rosemead,,8.250
+USA,CA,Roseville,,7.250
+USA,CA,Ross,,7.750
+USA,CA,Rossmoor,,7.750
+USA,CA,Rough and Ready,,7.375
+USA,CA,Round Mountain,,7.250
+USA,CA,Rowland Heights,,8.250
+USA,CA,Royal Oaks,,7.250
+USA,CA,Rubidoux,,7.750
+USA,CA,Ruby Valley,,7.250
+USA,CA,Rumsey,,7.250
+USA,CA,Running Springs,,7.750
+USA,CA,Ruth,,7.250
+USA,CA,Rutherford,,7.750
+USA,CA,Ryde,,7.750
+USA,CA,Sacramento,,7.750
+USA,CA,Saint Helena,,7.750
+USA,CA,Salida,,7.375
+USA,CA,Salinas,,7.750
+USA,CA,Salton City,,7.750
+USA,CA,Salyer,,7.250
+USA,CA,Samoa,,7.250
+USA,CA,San Andreas,,7.250
+USA,CA,San Anselmo,,7.750
+USA,CA,San Ardo,,7.250
+USA,CA,San Benito,,7.250
+USA,CA,San Bernardino,,8.000
+USA,CA,San Bruno,,8.250
+USA,CA,San Carlos,,8.250
+USA,CA,San Clemente,,7.750
+USA,CA,San Diego,,7.750
+USA,CA,San Dimas,,8.250
+USA,CA,San Fernando,,8.250
+USA,CA,San Francisco,,8.500
+USA,CA,San Gabriel,,8.250
+USA,CA,San Geronimo,,7.750
+USA,CA,San Gregorio,,8.250
+USA,CA,San Jacinto,,7.750
+USA,CA,San Joaquin,,7.975
+USA,CA,San Jose,,8.250
+USA,CA,San Juan Bautista,,8.000
+USA,CA,San Juan Capistrano,,7.750
+USA,CA,San Juan Plaza (San Juan Capistrano),,7.750
+USA,CA,San Leandro,,8.750
+USA,CA,San Lorenzo,,8.750
+USA,CA,San Lucas,,7.250
+USA,CA,San Luis Obispo,,7.750
+USA,CA,San Luis Rey (Oceanside),,7.750
+USA,CA,San Marcos,,7.750
+USA,CA,San Marino,,8.250
+USA,CA,San Martin,,8.250
+USA,CA,San Mateo,,8.250
+USA,CA,San Miguel,,7.250
+USA,CA,San Pablo,,8.250
+USA,CA,San Pedro (Los Angeles),,8.250
+USA,CA,San Quentin,,7.750
+USA,CA,San Rafael,,8.250
+USA,CA,San Ramon,,8.250
+USA,CA,San Simeon,,7.250
+USA,CA,San Tomas,,8.250
+USA,CA,San Ysidro (San Diego),,7.750
+USA,CA,Sand City,,7.750
+USA,CA,Sanger,,7.975
+USA,CA,Santa Ana,,7.750
+USA,CA,Santa Barbara,,7.750
+USA,CA,Santa Clara,,8.250
+USA,CA,Santa Clarita,,8.250
+USA,CA,Santa Cruz,,8.500
+USA,CA,Santa Fe Springs,,8.250
+USA,CA,Santa Margarita,,7.250
+USA,CA,Santa Maria,,7.750
+USA,CA,Santa Monica,,8.250
+USA,CA,Santa Nella,,7.250
+USA,CA,Santa Paula,,7.250
+USA,CA,Santa Rita Park,,7.250
+USA,CA,Santa Rosa,,8.000
+USA,CA,Santa Ynez,,7.750
+USA,CA,Santa Ysabel,,7.750
+USA,CA,Santee,,7.750
+USA,CA,Saratoga,,8.250
+USA,CA,Saticoy,,7.250
+USA,CA,Sattley,,7.250
+USA,CA,Saugus (Santa Clarita),,8.250
+USA,CA,Sausalito,,7.750
+USA,CA,Sawtelle (Los Angeles),,8.250
+USA,CA,Sawyers Bar,,7.250
+USA,CA,Scotia,,7.250
+USA,CA,Scott Bar,,7.250
+USA,CA,Scotts Valley,,8.500
+USA,CA,Sea Ranch,,7.750
+USA,CA,Seabright,,8.000
+USA,CA,Seal Beach,,7.750
+USA,CA,Seaside,,7.250
+USA,CA,Sebastopol,,8.000
+USA,CA,Seeley,,7.750
+USA,CA,Seiad Valley,,7.250
+USA,CA,Selby,,8.250
+USA,CA,Selma,,8.475
+USA,CA,Seminole Hot Springs,,8.250
+USA,CA,Sepulveda (Los Angeles),,8.250
+USA,CA,Sequoia National Park,,7.750
+USA,CA,Shafter,,7.250
+USA,CA,Shandon,,7.250
+USA,CA,Sharpe Army Depot,,7.750
+USA,CA,Shasta,,7.250
+USA,CA,Shasta Lake,,7.250
+USA,CA,Shaver Lake,,7.975
+USA,CA,Sheepranch,,7.250
+USA,CA,Shell Beach (Pismo Beach),,7.250
+USA,CA,Sheridan,,7.250
+USA,CA,Sherman Island,,7.750
+USA,CA,Sherman Oaks (Los Angeles),,8.250
+USA,CA,Sherwin Plaza,,7.250
+USA,CA,Shingle Springs,,7.250
+USA,CA,Shingletown,,7.250
+USA,CA,Shively,,7.250
+USA,CA,Shore Acres,,8.250
+USA,CA,Shoshone,,7.750
+USA,CA,Sierra City,,7.250
+USA,CA,Sierra Madre,,8.250
+USA,CA,Sierraville,,7.250
+USA,CA,Signal Hill,,8.250
+USA,CA,Silver Lake,,7.250
+USA,CA,Silverado Canyon,,7.750
+USA,CA,Simi Valley,,7.250
+USA,CA,Sisquoc,,7.750
+USA,CA,Sites,,7.250
+USA,CA,Sky Valley,,7.750
+USA,CA,Skyforest,,7.750
+USA,CA,Sleepy Valley,,8.250
+USA,CA,Sloat,,7.250
+USA,CA,Sloughhouse,,7.750
+USA,CA,Smartville,,7.250
+USA,CA,Smith River,,7.250
+USA,CA,Smithflat,,7.250
+USA,CA,Smoke Tree (Palm Springs),,7.750
+USA,CA,Snelling,,7.250
+USA,CA,Soda Springs,,7.375
+USA,CA,Solana Beach,,7.750
+USA,CA,Soledad,,7.250
+USA,CA,Solemint,,8.250
+USA,CA,Solvang,,7.750
+USA,CA,Somerset,,7.250
+USA,CA,Somes Bar,,7.250
+USA,CA,Somis,,7.250
+USA,CA,Sonoma,,7.750
+USA,CA,Sonora,,7.750
+USA,CA,Soquel,,8.000
+USA,CA,Soulsbyville,,7.250
+USA,CA,South Dos Palos,,7.250
+USA,CA,South El Monte,,8.250
+USA,CA,South Fork,,7.250
+USA,CA,South Gate,,8.250
+USA,CA,South Laguna (Laguna Beach),,8.250
+USA,CA,South Lake Tahoe,,7.750
+USA,CA,South Pasadena,,8.250
+USA,CA,South San Francisco,,8.250
+USA,CA,South Shore (Alameda),,8.750
+USA,CA,South Whittier,,8.250
+USA,CA,Spanish Flat,,7.750
+USA,CA,Spreckels,,7.250
+USA,CA,Spring Garden,,7.250
+USA,CA,Spring Valley,,7.750
+USA,CA,Springville,,7.750
+USA,CA,Spyrock,,7.250
+USA,CA,Squaw Valley,,7.975
+USA,CA,St. Helena,,7.750
+USA,CA,Standard,,7.250
+USA,CA,Standish,,7.250
+USA,CA,Stanford,,8.250
+USA,CA,Stanislaus,,7.250
+USA,CA,Stanton,,7.750
+USA,CA,Steele Park,,7.750
+USA,CA,Stevinson,,7.250
+USA,CA,Stewarts Point,,7.750
+USA,CA,Stinson Beach,,7.750
+USA,CA,Stirling City,,7.250
+USA,CA,Stockton,,8.000
+USA,CA,Stonyford,,7.250
+USA,CA,Storrie,,7.250
+USA,CA,Stratford,,7.250
+USA,CA,Strathmore,,7.750
+USA,CA,Strawberry,,7.250
+USA,CA,Strawberry Valley,,7.250
+USA,CA,Studio City (Los Angeles),,8.250
+USA,CA,Sugarloaf,,7.750
+USA,CA,Suisun City,,7.375
+USA,CA,Sulphur Springs,,8.250
+USA,CA,Sultana,,7.750
+USA,CA,Summerland,,7.750
+USA,CA,Summit,,7.750
+USA,CA,Summit City,,7.250
+USA,CA,Sun City,,7.750
+USA,CA,Sun Valley (Los Angeles),,8.250
+USA,CA,Sunland (Los Angeles),,8.250
+USA,CA,Sunnymead (Moreno Valley),,7.750
+USA,CA,Sunnyside,,7.750
+USA,CA,Sunnyvale,,8.250
+USA,CA,Sunol,,8.750
+USA,CA,Sunset Beach,,7.750
+USA,CA,Sunset Whitney Ranch,,7.250
+USA,CA,Surfside (Seal Beach),,7.750
+USA,CA,Susanville,,7.250
+USA,CA,Sutter,,7.250
+USA,CA,Sutter Creek,,7.250
+USA,CA,Swall Meadows (Bishop),,7.750
+USA,CA,Sylmar (Los Angeles),,8.250
+USA,CA,Taft,,7.250
+USA,CA,Tagus Ranch,,7.750
+USA,CA,Tahoe City,,7.250
+USA,CA,Tahoe Paradise,,7.250
+USA,CA,Tahoe Valley,,7.250
+USA,CA,Tahoe Vista,,7.250
+USA,CA,Tahoma,,7.250
+USA,CA,Talmage,,7.250
+USA,CA,Tamal (San Quentin),,7.750
+USA,CA,Tarzana (Los Angeles),,8.250
+USA,CA,Taylorsville,,7.250
+USA,CA,Tecate,,7.750
+USA,CA,Tecopa,,7.750
+USA,CA,Tehachapi,,7.250
+USA,CA,Tehama,,7.250
+USA,CA,Temecula,,7.750
+USA,CA,Temple City,,8.250
+USA,CA,Templeton,,7.250
+USA,CA,Terminal Island (Los Angeles),,8.250
+USA,CA,Termo,,7.250
+USA,CA,Terra Bella,,7.750
+USA,CA,Thermal,,7.750
+USA,CA,Thornton,,7.750
+USA,CA,Thousand Oaks,,7.250
+USA,CA,Thousand Palms,,7.750
+USA,CA,Three Rivers,,7.750
+USA,CA,Tiburon,,7.750
+USA,CA,Tierra Del Sol,,7.750
+USA,CA,Tierrasanta (San Diego),,7.750
+USA,CA,Tipton,,7.750
+USA,CA,Tollhouse,,7.975
+USA,CA,Toluca Lake (Los Angeles),,8.250
+USA,CA,Tomales,,7.750
+USA,CA,Toms Place,,7.250
+USA,CA,Topanga (Los Angeles),,8.250
+USA,CA,Topanga Park (Los Angeles),,8.250
+USA,CA,Topaz,,7.250
+USA,CA,Torrance,,8.250
+USA,CA,Town Center,,7.750
+USA,CA,Trabuco Canyon,,7.750
+USA,CA,Tracy,,7.750
+USA,CA,Tranquillity,,7.975
+USA,CA,Traver,,7.750
+USA,CA,Travis A.F.B. (Fairfield),,7.375
+USA,CA,Tres Pinos,,7.250
+USA,CA,Trinidad,,8.250
+USA,CA,Trinity Center,,7.250
+USA,CA,Trona,,7.750
+USA,CA,Trowbridge,,7.250
+USA,CA,Truckee,,7.875
+USA,CA,Tujunga (Los Angeles),,8.250
+USA,CA,Tulare,,8.250
+USA,CA,Tulelake,,7.250
+USA,CA,Tuolumne,,7.250
+USA,CA,Tuolumne Meadows,,7.750
+USA,CA,Tupman,,7.250
+USA,CA,Turlock,,7.375
+USA,CA,Tustin,,7.750
+USA,CA,Twain,,7.250
+USA,CA,Twain Harte,,7.250
+USA,CA,Twentynine Palms,,7.750
+USA,CA,Twin Bridges,,7.250
+USA,CA,Twin Peaks,,7.750
+USA,CA,Two Rock Coast Guard Station,,7.750
+USA,CA,U.S.Naval Postgrad School (Monterey),,7.250
+USA,CA,Ukiah,,7.750
+USA,CA,Union City,,8.750
+USA,CA,Universal City,,8.250
+USA,CA,University,,7.750
+USA,CA,University Park (Irvine),,7.750
+USA,CA,Upland,,7.750
+USA,CA,Upper Lake/ Upper Lake Valley,,7.250
+USA,CA,Vacaville,,7.375
+USA,CA,Val Verde Park,,8.250
+USA,CA,Valencia (Santa Clarita),,8.250
+USA,CA,Valinda,,8.250
+USA,CA,Vallecito,,7.250
+USA,CA,Vallejo,,7.375
+USA,CA,Valley Center,,7.750
+USA,CA,Valley Fair,,8.250
+USA,CA,Valley Ford,,7.750
+USA,CA,Valley Home,,7.375
+USA,CA,Valley Springs,,7.250
+USA,CA,Valley Village,,8.250
+USA,CA,Valyermo,,8.250
+USA,CA,Van Nuys (Los Angeles),,8.250
+USA,CA,Vandenberg A.F.B,,7.750
+USA,CA,Vasquez Rocks,,8.250
+USA,CA,Venice (Los Angeles),,8.250
+USA,CA,Ventucopa,,7.750
+USA,CA,Ventura,,7.250
+USA,CA,Verdugo City (Glendale),,8.250
+USA,CA,Vernalis,,7.750
+USA,CA,Vernon,,8.250
+USA,CA,Veteran's Hospital (Los Angeles),,8.250
+USA,CA,Victor,,7.750
+USA,CA,Victorville,,7.750
+USA,CA,Vidal,,7.750
+USA,CA,View Park,,8.250
+USA,CA,Villa Grande,,7.750
+USA,CA,Villa Park,,7.750
+USA,CA,Vina,,7.250
+USA,CA,Vincent,,8.250
+USA,CA,Vineburg,,7.750
+USA,CA,Vinton,,7.250
+USA,CA,Virgilia,,7.250
+USA,CA,Visalia,,8.000
+USA,CA,Vista Park,,7.250
+USA,CA,Vista,,8.250
+USA,CA,Volcano,,7.250
+USA,CA,Volta,,7.250
+USA,CA,Wallace,,7.250
+USA,CA,Walnut Creek,,8.250
+USA,CA,Walnut Grove,,7.750
+USA,CA,Walnut Park,,8.250
+USA,CA,Walnut,,8.250
+USA,CA,Warm Spring (Fremont),,8.750
+USA,CA,Warner Springs,,7.750
+USA,CA,Wasco,,7.250
+USA,CA,Waterford,,7.375
+USA,CA,Watsonville,,8.250
+USA,CA,Watts,,8.250
+USA,CA,Waukena,,7.750
+USA,CA,Wawona,,7.750
+USA,CA,Weaverville,,7.250
+USA,CA,Weed,,7.250
+USA,CA,Weimar,,7.250
+USA,CA,Weldon,,7.250
+USA,CA,Wendel,,7.250
+USA,CA,Weott,,7.250
+USA,CA,West Covina,,8.250
+USA,CA,West Hills (Los Angeles),,8.250
+USA,CA,West Hollywood,,8.250
+USA,CA,West Los Angeles (Los Angeles),,8.250
+USA,CA,West Pittsburg,,8.250
+USA,CA,West Point,,7.250
+USA,CA,West Sacramento,,7.750
+USA,CA,Westchester (Los Angeles),,8.250
+USA,CA,Westend,,7.750
+USA,CA,Westhaven,,7.250
+USA,CA,Westlake (Los Angeles),,8.250
+USA,CA,Westlake Village (Thousand Oaks),,7.250
+USA,CA,Westlake Village,,8.250
+USA,CA,Westley,,7.375
+USA,CA,Westminster,,7.750
+USA,CA,Westmorland,,7.750
+USA,CA,Westport,,7.250
+USA,CA,Westside,,7.375
+USA,CA,Westwood,,7.250
+USA,CA,Westwood (Los Angeles),,8.250
+USA,CA,Wheatland,,7.250
+USA,CA,Wheeler Ridge,,7.250
+USA,CA,Whiskeytown,,7.250
+USA,CA,Whispering Pines,,7.250
+USA,CA,White Pines,,7.250
+USA,CA,Whitethorn,,7.250
+USA,CA,Whitewater,,7.750
+USA,CA,Whitlow,,7.250
+USA,CA,Whitmore,,7.250
+USA,CA,Whittier,,8.250
+USA,CA,Wildomar,,7.750
+USA,CA,Wildwood,,7.250
+USA,CA,Williams,,7.750
+USA,CA,Willits,,7.750
+USA,CA,Willow Creek,,7.250
+USA,CA,Willow Ranch,,7.250
+USA,CA,Willowbrook,,8.250
+USA,CA,Willows,,7.250
+USA,CA,Wilmington (Los Angeles),,8.250
+USA,CA,Wilseyville,,7.250
+USA,CA,Wilsona Gardens,,8.250
+USA,CA,Wilton,,7.750
+USA,CA,Winchester,,7.750
+USA,CA,Windsor Hills,,8.250
+USA,CA,Windsor,,7.750
+USA,CA,Winnetka (Los Angeles),,8.250
+USA,CA,Winterhaven,,7.750
+USA,CA,Winters,,7.250
+USA,CA,Winton,,7.250
+USA,CA,Wishon,,7.750
+USA,CA,Witter Springs,,7.250
+USA,CA,Wofford Heights,,7.250
+USA,CA,Woodacre,,7.750
+USA,CA,Woodbridge,,7.750
+USA,CA,Woodfords,,7.250
+USA,CA,Woodlake,,7.750
+USA,CA,Woodland,,7.750
+USA,CA,Woodland Hills (Los Angeles),,8.250
+USA,CA,Woodleaf,,7.250
+USA,CA,Woodside,,8.250
+USA,CA,Woodville,,7.750
+USA,CA,Woody,,7.250
+USA,CA,Wrightwood,,7.750
+USA,CA,Yankee Hill,,7.250
+USA,CA,Yermo,,7.750
+USA,CA,Yettem,,7.750
+USA,CA,Yolo,,7.250
+USA,CA,Yorba Linda,,7.750
+USA,CA,Yorkville,,7.250
+USA,CA,Yosemite Lodge,,7.750
+USA,CA,Yosemite National Park,,7.750
+USA,CA,Yountville,,7.750
+USA,CA,Yreka,,7.250
+USA,CA,Yuba City,,7.250
+USA,CA,Yucaipa,,7.750
+USA,CA,Yucca Valley,,7.750
+USA,CA,Zamora,,7.250
+USA,CA,Zenia,,7.250
diff --git a/t/supporting_collateral/taxTables/missingHeaders.csv b/t/supporting_collateral/taxTables/missingHeaders.csv
new file mode 100644
index 000000000..9bd954afd
--- /dev/null
+++ b/t/supporting_collateral/taxTables/missingHeaders.csv
@@ -0,0 +1,4 @@
+country,city,code,taxRate
+USA,,,,0.0
+USA,Wisconsin,,,5.0
+USA,Wisconsin,Madison,53701,0.5
diff --git a/t/supporting_collateral/taxTables/orderedTaxTable.csv b/t/supporting_collateral/taxTables/orderedTaxTable.csv
new file mode 100644
index 000000000..5d2e105f0
--- /dev/null
+++ b/t/supporting_collateral/taxTables/orderedTaxTable.csv
@@ -0,0 +1,4 @@
+taxRate,country,state,city,code
+0.0,USA,,,
+5.0,USA,Wisconsin,,
+0.5,USA,Wisconsin,Madison,53701
diff --git a/www/extras/macro/ViewCart/cart.gif b/www/extras/macro/ViewCart/cart.gif
new file mode 100644
index 000000000..b8d6556e5
Binary files /dev/null and b/www/extras/macro/ViewCart/cart.gif differ
diff --git a/www/extras/yui-webgui/build/string/string.js b/www/extras/yui-webgui/build/string/string.js
new file mode 100644
index 000000000..6c02ba2ae
--- /dev/null
+++ b/www/extras/yui-webgui/build/string/string.js
@@ -0,0 +1,213 @@
+// Initialize namespace
+if (typeof WebGUI == "undefined") {
+ var WebGUI = {};
+}
+if (typeof WebGUI.str == "undefined") {
+ WebGUI.str = {};
+}
+
+
+/**
+ * This object contains generic string manipulation functions
+ */
+
+WebGUI.str.sprintfWrapper = {
+ init : function () {
+
+ if (typeof arguments == "undefined") { return null; }
+ if (arguments.length < 1) { return null; }
+ if (typeof arguments[0] != "string") { return null; }
+ if (typeof RegExp == "undefined") { return null; }
+
+ var string = arguments[0];
+ var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
+ var matches = new Array();
+ var strings = new Array();
+ var convCount = 0;
+ var stringPosStart = 0;
+ var stringPosEnd = 0;
+ var matchPosEnd = 0;
+ var newString = '';
+ var match = null;
+
+ while (match = exp.exec(string)) {
+ if (match[9]) { convCount += 1; }
+
+ stringPosStart = matchPosEnd;
+ stringPosEnd = exp.lastIndex - match[0].length;
+ strings[strings.length] = string.substring(stringPosStart, stringPosEnd);
+
+ matchPosEnd = exp.lastIndex;
+ matches[matches.length] = {
+ match: match[0],
+ left: match[3] ? true : false,
+ sign: match[4] || '',
+ pad: match[5] || ' ',
+ min: match[6] || 0,
+ precision: match[8],
+ code: match[9] || '%',
+ negative: parseInt(arguments[convCount]) < 0 ? true : false,
+ argument: String(arguments[convCount])
+ };
+ }
+ strings[strings.length] = string.substring(matchPosEnd);
+
+ if (matches.length == 0) { return string; }
+ if ((arguments.length - 1) < convCount) { return null; }
+
+ var code = null;
+ var match = null;
+ var i = null;
+
+ for (i=0; i0){F=H-1;do{this._buttons[F].set("disabled",G);}while(F--);}},_onKeyDown:function(K){var G=B.getTarget(K),I=B.getCharCode(K),H=G.parentNode.parentNode.id,J=E[H],F=-1;if(I==37||I==38){F=(J.index===0)?(this._buttons.length-1):(J.index-1);}else{if(I==39||I==40){F=(J.index===(this._buttons.length-1))?0:(J.index+1);}}if(F>-1){this.check(F);this.getButton(F).focus();
-}},_onAppendTo:function(H){var I=this._buttons,G=I.length,F;for(F=0;F
+ * In the U.S, Jan 1st is normally used based on a Sunday start of week.
+ * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
+ * =((this.preMonthDays+j+this.monthDays)-7)){Q.addClass(f,this.Style.CSS_CELL_BOTTOM);}e[e.length]=K.innerHTML;AC++;}if(c){e=this.renderRowFooter(T,e);}e[e.length]="
+ * In the U.S, Jan 1st is normally used based on a Sunday start of week.
+ * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
+ * ";
html[html.length] = ' ';
html[html.length] = ' ';
-
+
if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }
-
- for (var d=0;d<7;d++){ // Render actual days
-
+
+ for (var d=0; d < 7; d++){ // Render actual days
+
cellRenderers = [];
-
+
this.clearElement(cell);
cell.className = this.Style.CSS_CELL;
cell.id = this.id + cellPrefix + i;
@@ -2854,64 +2917,59 @@ YAHOO.widget.Calendar.prototype = {
workingDate.getFullYear() == todayYear) {
cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
}
-
+
var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()];
this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates
-
+
// Local OOM check for performance, since we already have pagedate
if (workingDate.getMonth() != useDate.getMonth()) {
cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
} else {
- YAHOO.util.Dom.addClass(cell, workingDayPrefix + workingDate.getDay());
- YAHOO.util.Dom.addClass(cell, dayPrefix + workingDate.getDate());
-
+ D.addClass(cell, workingDayPrefix + workingDate.getDay());
+ D.addClass(cell, dayPrefix + workingDate.getDate());
+
for (var s=0;s ";E[E.length]=" ";if(this.cfg.getProperty(M.SHOW_WEEKDAYS.key)){E=this.buildWeekdays(E);}E[E.length]="";return E;},buildWeekdays:function(C){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;C[C.length]="";E[E.length]=" \n";if(this.cfg.getProperty(A.SHOW_WEEK_HEADER.key)){C[C.length]=" ";return C;},renderBody:function(c,a){var m=YAHOO.widget.Calendar._DEFAULT_CONFIG;var AB=this.cfg.getProperty(m.START_WEEKDAY.key);this.preMonthDays=c.getDay();if(AB>0){this.preMonthDays-=AB;}if(this.preMonthDays<0){this.preMonthDays+=7;}this.monthDays=YAHOO.widget.DateMath.findMonthEnd(c).getDate();this.postMonthDays=YAHOO.widget.Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays;c=YAHOO.widget.DateMath.subtract(c,YAHOO.widget.DateMath.DAY,this.preMonthDays);var Q,H;var G="w";var W="_cell";var U="wd";var k="d";var I;var h;var O=this.today.getFullYear();var j=this.today.getMonth();var D=this.today.getDate();var q=this.cfg.getProperty(m.PAGEDATE.key);var C=this.cfg.getProperty(m.HIDE_BLANK_WEEKS.key);var Z=this.cfg.getProperty(m.SHOW_WEEK_FOOTER.key);var T=this.cfg.getProperty(m.SHOW_WEEK_HEADER.key);var M=this.cfg.getProperty(m.MINDATE.key);var S=this.cfg.getProperty(m.MAXDATE.key);if(M){M=YAHOO.widget.DateMath.clearTime(M);}if(S){S=YAHOO.widget.DateMath.clearTime(S);}a[a.length]="";var z=0;var J=document.createElement("div");var b=document.createElement("td");J.appendChild(b);var o=this.parent||this;for(var u=0;u<6;u++){Q=YAHOO.widget.DateMath.getWeekNumber(c,q.getFullYear(),AB);H=G+Q;if(u!==0&&C===true&&c.getMonth()!=q.getMonth()){break;}else{a[a.length]=" ";}for(var B=0;B ";}C[C.length]="";if(T){a=this.renderRowHeader(Q,a);}for(var AA=0;AA<7;AA++){I=[];this.clearElement(b);b.className=this.Style.CSS_CELL;b.id=this.id+W+z;if(c.getDate()==D&&c.getMonth()==j&&c.getFullYear()==O){I[I.length]=o.renderCellStyleToday;}var R=[c.getFullYear(),c.getMonth()+1,c.getDate()];this.cellDates[this.cellDates.length]=R;if(c.getMonth()!=q.getMonth()){I[I.length]=o.renderCellNotThisMonth;}else{YAHOO.util.Dom.addClass(b,U+c.getDay());YAHOO.util.Dom.addClass(b,k+c.getDate());for(var t=0;t ";}}a[a.length]="";return a;},renderFooter:function(A){return A;},render:function(){this.beforeRenderEvent.fire();var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;var C=YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty(A.PAGEDATE.key));this.resetRenderers();this.cellDates.length=0;YAHOO.util.Event.purgeElement(this.oDomContainer,true);var B=[];B[B.length]="";B=this.renderHeader(B);B=this.renderBody(C,B);B=this.renderFooter(B);B[B.length]="
";this.oDomContainer.innerHTML=B.join("\n");this.applyListeners();this.cells=this.oDomContainer.getElementsByTagName("td");this.cfg.refireEvent(A.TITLE.key);this.cfg.refireEvent(A.CLOSE.key);this.cfg.refireEvent(A.IFRAME.key);this.renderEvent.fire();},applyListeners:function(){var K=this.oDomContainer;var B=this.parent||this;var G="a";var D="mousedown";var H=YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT,G,K);var C=YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT,G,K);if(H&&H.length>0){this.linkLeft=H[0];YAHOO.util.Event.addListener(this.linkLeft,D,B.previousMonth,B,true);}if(C&&C.length>0){this.linkRight=C[0];YAHOO.util.Event.addListener(this.linkRight,D,B.nextMonth,B,true);}if(B.cfg.getProperty("navigator")!==null){this.applyNavListeners();}if(this.domEventMap){var E,A;for(var M in this.domEventMap){if(YAHOO.lang.hasOwnProperty(this.domEventMap,M)){var I=this.domEventMap[M];if(!(I instanceof Array)){I=[I];}for(var F=0;F"+B+" ";return A;},renderCellDefault:function(B,A){A.innerHTML=""+this.buildDayLabel(B)+"";},styleCellDefault:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_SELECTABLE);},renderCellStyleHighlight1:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT1);},renderCellStyleHighlight2:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT2);},renderCellStyleHighlight3:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT3);},renderCellStyleHighlight4:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT4);},renderCellStyleToday:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_TODAY);},renderCellStyleSelected:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_SELECTED);},renderCellNotThisMonth:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_OOM);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;},renderBodyCellRestricted:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL);YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_RESTRICTED);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;},addMonths:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.add(this.cfg.getProperty(A),YAHOO.widget.DateMath.MONTH,B));this.resetRenderers();
-this.changePageEvent.fire();},subtractMonths:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.subtract(this.cfg.getProperty(A),YAHOO.widget.DateMath.MONTH,B));this.resetRenderers();this.changePageEvent.fire();},addYears:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.add(this.cfg.getProperty(A),YAHOO.widget.DateMath.YEAR,B));this.resetRenderers();this.changePageEvent.fire();},subtractYears:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.subtract(this.cfg.getProperty(A),YAHOO.widget.DateMath.YEAR,B));this.resetRenderers();this.changePageEvent.fire();},nextMonth:function(){this.addMonths(1);},previousMonth:function(){this.subtractMonths(1);},nextYear:function(){this.addYears(1);},previousYear:function(){this.subtractYears(1);},reset:function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.resetProperty(A.SELECTED.key);this.cfg.resetProperty(A.PAGEDATE.key);this.resetEvent.fire();},clear:function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.setProperty(A.SELECTED.key,[]);this.cfg.setProperty(A.PAGEDATE.key,new Date(this.today.getTime()));this.clearEvent.fire();},select:function(C){var F=this._toFieldArray(C);var B=[];var E=[];var G=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;for(var A=0;A";E[E.length]=' ";if(this.cfg.getProperty(M.SHOW_WEEKDAYS.key)){E=this.buildWeekdays(E);}E[E.length]="";return E;},buildWeekdays:function(C){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;C[C.length]='';E[E.length]=' \n';if(this.cfg.getProperty(A.SHOW_WEEK_HEADER.key)){C[C.length]=" ";return C;},renderBody:function(g,e){var AF=YAHOO.widget.DateMath,M=YAHOO.widget.Calendar,Q=YAHOO.util.Dom,q=M._DEFAULT_CONFIG;var AE=this.cfg.getProperty(q.START_WEEKDAY.key);this.preMonthDays=g.getDay();if(AE>0){this.preMonthDays-=AE;}if(this.preMonthDays<0){this.preMonthDays+=7;}this.monthDays=AF.findMonthEnd(g).getDate();this.postMonthDays=M.DISPLAY_DAYS-this.preMonthDays-this.monthDays;g=AF.subtract(g,AF.DAY,this.preMonthDays);var T,I,H="w",Z="_cell",X="wd",n="d",J,l,R=this.today.getFullYear(),m=this.today.getMonth(),E=this.today.getDate(),v=this.cfg.getProperty(q.PAGEDATE.key),C=this.cfg.getProperty(q.HIDE_BLANK_WEEKS.key),c=this.cfg.getProperty(q.SHOW_WEEK_FOOTER.key),W=this.cfg.getProperty(q.SHOW_WEEK_HEADER.key),O=this.cfg.getProperty(q.MINDATE.key),V=this.cfg.getProperty(q.MAXDATE.key);if(O){O=AF.clearTime(O);}if(V){V=AF.clearTime(V);}e[e.length]='';var AC=0,K=document.createElement("div"),f=document.createElement("td");K.appendChild(f);var u=this.parent||this;for(var y=0;y<6;y++){T=AF.getWeekNumber(g,AE);I=H+T;if(y!==0&&C===true&&g.getMonth()!=v.getMonth()){break;}else{e[e.length]=' ";}for(var B=0;B ";}C[C.length]="';if(W){e=this.renderRowHeader(T,e);}for(var AD=0;AD<7;AD++){J=[];this.clearElement(f);f.className=this.Style.CSS_CELL;f.id=this.id+Z+AC;if(g.getDate()==E&&g.getMonth()==m&&g.getFullYear()==R){J[J.length]=u.renderCellStyleToday;}var U=[g.getFullYear(),g.getMonth()+1,g.getDate()];this.cellDates[this.cellDates.length]=U;if(g.getMonth()!=v.getMonth()){J[J.length]=u.renderCellNotThisMonth;}else{Q.addClass(f,X+g.getDay());Q.addClass(f,n+g.getDate());for(var w=0;w ";}}e[e.length]="";return e;},renderFooter:function(A){return A;},render:function(){this.beforeRenderEvent.fire();var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;var C=YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty(A.PAGEDATE.key));this.resetRenderers();this.cellDates.length=0;YAHOO.util.Event.purgeElement(this.oDomContainer,true);var B=[];B[B.length]='';B=this.renderHeader(B);B=this.renderBody(C,B);B=this.renderFooter(B);B[B.length]="
";this.oDomContainer.innerHTML=B.join("\n");this.applyListeners();this.cells=this.oDomContainer.getElementsByTagName("td");this.cfg.refireEvent(A.TITLE.key);this.cfg.refireEvent(A.CLOSE.key);this.cfg.refireEvent(A.IFRAME.key);this.renderEvent.fire();},applyListeners:function(){var K=this.oDomContainer;var B=this.parent||this;var G="a";var D="mousedown";var H=YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT,G,K);var C=YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT,G,K);if(H&&H.length>0){this.linkLeft=H[0];YAHOO.util.Event.addListener(this.linkLeft,D,B.previousMonth,B,true);}if(C&&C.length>0){this.linkRight=C[0];YAHOO.util.Event.addListener(this.linkRight,D,B.nextMonth,B,true);}if(B.cfg.getProperty("navigator")!==null){this.applyNavListeners();}if(this.domEventMap){var E,A;for(var M in this.domEventMap){if(YAHOO.lang.hasOwnProperty(this.domEventMap,M)){var I=this.domEventMap[M];if(!(I instanceof Array)){I=[I];}for(var F=0;F'+B+" ";return A;},renderCellDefault:function(B,A){A.innerHTML=''+this.buildDayLabel(B)+"";},styleCellDefault:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_SELECTABLE);},renderCellStyleHighlight1:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT1);},renderCellStyleHighlight2:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT2);},renderCellStyleHighlight3:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT3);},renderCellStyleHighlight4:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT4);},renderCellStyleToday:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_TODAY);},renderCellStyleSelected:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_SELECTED);},renderCellNotThisMonth:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_OOM);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;},renderBodyCellRestricted:function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL);YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_RESTRICTED);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;},addMonths:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.add(this.cfg.getProperty(A),YAHOO.widget.DateMath.MONTH,B));this.resetRenderers();this.changePageEvent.fire();},subtractMonths:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.subtract(this.cfg.getProperty(A),YAHOO.widget.DateMath.MONTH,B));this.resetRenderers();this.changePageEvent.fire();},addYears:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.add(this.cfg.getProperty(A),YAHOO.widget.DateMath.YEAR,B));this.resetRenderers();this.changePageEvent.fire();
+},subtractYears:function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.subtract(this.cfg.getProperty(A),YAHOO.widget.DateMath.YEAR,B));this.resetRenderers();this.changePageEvent.fire();},nextMonth:function(){this.addMonths(1);},previousMonth:function(){this.subtractMonths(1);},nextYear:function(){this.addYears(1);},previousYear:function(){this.subtractYears(1);},reset:function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.resetProperty(A.SELECTED.key);this.cfg.resetProperty(A.PAGEDATE.key);this.resetEvent.fire();},clear:function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.setProperty(A.SELECTED.key,[]);this.cfg.setProperty(A.PAGEDATE.key,new Date(this.today.getTime()));this.clearEvent.fire();},select:function(C){var F=this._toFieldArray(C);var B=[];var E=[];var G=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;for(var A=0;A";
html[html.length] = ' ';
html[html.length] = ' ';
-
+
if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }
-
- for (var d=0;d<7;d++){ // Render actual days
-
+
+ for (var d=0; d < 7; d++){ // Render actual days
+
cellRenderers = [];
-
+
this.clearElement(cell);
cell.className = this.Style.CSS_CELL;
cell.id = this.id + cellPrefix + i;
@@ -2837,64 +2900,59 @@ YAHOO.widget.Calendar.prototype = {
workingDate.getFullYear() == todayYear) {
cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
}
-
+
var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()];
this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates
-
+
// Local OOM check for performance, since we already have pagedate
if (workingDate.getMonth() != useDate.getMonth()) {
cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
} else {
- YAHOO.util.Dom.addClass(cell, workingDayPrefix + workingDate.getDay());
- YAHOO.util.Dom.addClass(cell, dayPrefix + workingDate.getDate());
-
+ D.addClass(cell, workingDayPrefix + workingDate.getDay());
+ D.addClass(cell, dayPrefix + workingDate.getDate());
+
for (var s=0;s