VbzCart/archive/code/files/shop.php
About
- Purpose: classes needed when dealing with customer data (shopping cart etc.)
- History:
- 2011-12-18 Saving current code just before making some API changes
Code
<php> <?php /*
PURPOSE: vbz class library for handling dynamic data related to shopping (cart, mainly) HISTORY: 2010-10-28 kluged the blank-order-email problem 2010-12-24 Fixed calls to Update() so they always pass arrays 2011-03-31 created AddMoney() and IncMoney() KLUGES: RenderReceipt() and TemplateVars() both have to reload the current record, which shouldn't be necessary.
- /
// FILE NAMES: define('KWP_ICON_ALERT' ,'/tools/img/icons/button-red-X.20px.png');
// TABLE NAMES: define('KST_CART_DATA' ,'shop_cart_data');
// TABLE ACTION KEYS define('KS_URL_PAGE_SESSION', 'sess'); define('KS_URL_PAGE_ORDER', 'ord'); // must be consistent with events already logged define('KS_URL_PAGE_ORDERS', 'orders');
// FORM FIELD NAMES: // -- cart/shipping define('KSF_SHIP_ZONE' ,'ship-zone'); // -- shipping define('KSF_SHIP_TO_SELF' ,'ship-to-self'); // TRUE = use shipping info for recipient too define('KSF_SHIP_IS_CARD' ,'ship-is-billing'); // TRUE = shipping address same as billing/card define('KSF_SHIP_MESSAGE' ,'ship-message'); // address fields: prefix with "ship" or "card" as appropriate define('_KSF_ADDR_NAME' ,'addr-name'); define('_KSF_ADDR_STREET' ,'addr-street'); define('_KSF_ADDR_CITY' ,'addr-city'); define('_KSF_ADDR_STATE' ,'addr-state'); define('_KSF_ADDR_ZIP' ,'addr-zip'); define('_KSF_ADDR_COUNTRY' ,'addr-country'); define('_KSF_ADDR_EMAIL' ,'addr-email'); define('_KSF_ADDR_PHONE' ,'addr-phone');
// for retrieving field data: define('KSF_PFX_SHIP', 'ship-'); define('KSF_ADDR_SHIP_NAME' ,KSF_PFX_SHIP._KSF_ADDR_NAME); define('KSF_ADDR_SHIP_STREET' ,KSF_PFX_SHIP._KSF_ADDR_STREET); define('KSF_ADDR_SHIP_CITY' ,KSF_PFX_SHIP._KSF_ADDR_CITY); define('KSF_ADDR_SHIP_STATE' ,KSF_PFX_SHIP._KSF_ADDR_STATE); define('KSF_ADDR_SHIP_ZIP' ,KSF_PFX_SHIP._KSF_ADDR_ZIP); define('KSF_ADDR_SHIP_COUNTRY' ,KSF_PFX_SHIP._KSF_ADDR_COUNTRY); define('KSF_CUST_SHIP_EMAIL' ,KSF_PFX_SHIP._KSF_ADDR_EMAIL); define('KSF_CUST_SHIP_PHONE' ,KSF_PFX_SHIP._KSF_ADDR_PHONE);
// -- payment define('KSF_CUST_CARD_NUM' ,'cust-card-num'); define('KSF_CUST_CARD_EXP' ,'cust-card-exp'); define('KSF_CUST_CARD_NAME' ,'cust-card-name'); define('KSF_CUST_CARD_STREET' ,'cust-card-street'); define('KSF_CUST_CARD_CITY' ,'cust-card-city'); define('KSF_CUST_CARD_STATE' ,'cust-card-state'); define('KSF_CUST_CARD_ZIP' ,'cust-card-zip'); define('KSF_CUST_CARD_COUNTRY' ,'cust-card-country'); define('KSF_CUST_CHECK_NUM' ,'cust-check-num'); define('KSF_CUST_PAY_EMAIL' ,'cust-pay-email'); define('KSF_CUST_PAY_PHONE' ,'cust-pay-phone'); // DATA STORAGE KEYS: // -- shipping define('KSI_SHIP_ZONE' ,100);
define('KSI_ADDR_SHIP_NAME' ,101); define('KSI_ADDR_SHIP_STREET' ,102); define('KSI_ADDR_SHIP_CITY' ,103); define('KSI_ADDR_SHIP_STATE' ,104); define('KSI_ADDR_SHIP_ZIP' ,105); define('KSI_ADDR_SHIP_COUNTRY' ,106); define('KSI_SHIP_MESSAGE' ,107); define('KSI_SHIP_TO_SELF' ,110); define('KSI_SHIP_IS_CARD' ,113);
define('KSI_CUST_SHIP_EMAIL' ,111); define('KSI_CUST_SHIP_PHONE' ,112); //define('KSI_SHIP_MISSING' ,120); // -- payment define('KSI_CUST_CARD_NUM' ,202); define('KSI_CUST_CARD_EXP' ,203);
define('KSI_ADDR_CARD_NAME' ,204); define('KSI_CUST_CARD_NAME' ,KSI_ADDR_CARD_NAME); // alias define('KSI_ADDR_CARD_STREET' ,205); define('KSI_CUST_CARD_STREET' ,KSI_ADDR_CARD_STREET); // alias define('KSI_ADDR_CARD_CITY' ,206); define('KSI_CUST_CARD_CITY' ,KSI_ADDR_CARD_CITY); define('KSI_ADDR_CARD_STATE' ,207); define('KSI_CUST_CARD_STATE' ,KSI_ADDR_CARD_STATE); define('KSI_ADDR_CARD_ZIP' ,208); define('KSI_CUST_CARD_ZIP' ,KSI_ADDR_CARD_ZIP); define('KSI_ADDR_CARD_COUNTRY' ,209); define('KSI_CUST_CARD_COUNTRY' ,KSI_ADDR_CARD_COUNTRY);
define('KSI_CUST_PAY_EMAIL' ,211); define('KSI_CUST_PAY_PHONE' ,212); //define('KSI_CUST_MISSING' ,220); define('KSI_CUST_CHECK_NUM' ,230);
// calculated data define('KSI_ITEM_TOTAL' ,301); define('KSI_PER_ITEM_TOTAL' ,302); define('KSI_PER_PKG_TOTAL' ,303);
// ORDER MESSAGE TYPES // these reflect the values in the ord_msg_media table define('KSI_ORD_MSG_INSTRUC', 1); // Instructions in submitted order define('KSI_ORD_MSG_PKSLIP', 2); // Packing slip define('KSI_ORD_MSG_EMAIL', 3); // Email define('KSI_ORD_MSG_PHONE', 4); // Phone call define('KSI_ORD_MSG_MAIL', 5); // Snail mail define('KSI_ORD_MSG_FAX', 6); // Faxed message define('KSI_ORD_MSG_LABEL', 7); // Shipping label (for delivery instructions) define('KSI_ORD_MSG_INT', 8); // internal use - stored, not sent
global $vgaCartDataType; // documentation says this shouldn't be necessary $vgaCartDataType = array (
KSI_SHIP_ZONE => 'ship zone', KSI_ADDR_SHIP_NAME => 'ship-to name', KSI_ADDR_SHIP_STREET => 'ship-to street', KSI_ADDR_SHIP_CITY => 'ship-to city', KSI_ADDR_SHIP_STATE => 'ship-to state', KSI_ADDR_SHIP_ZIP => 'ship-to zipcode', KSI_ADDR_SHIP_COUNTRY => 'ship-to country', KSI_SHIP_MESSAGE => 'ship-to message', KSI_SHIP_TO_SELF => 'ship to self?', KSI_SHIP_IS_CARD => 'ship to = card?', KSI_CUST_SHIP_EMAIL => 'ship-to email', KSI_CUST_SHIP_PHONE => 'ship-to phone',
// KSI_SHIP_MISSING => 'ship-to missing info', // -- payment
KSI_CUST_CARD_NUM => 'card number', KSI_CUST_CARD_EXP => 'card expiry', KSI_ADDR_CARD_NAME => 'card owner', KSI_ADDR_CARD_STREET => 'card street address', KSI_ADDR_CARD_CITY => 'card address city', KSI_ADDR_CARD_STATE => 'card address state', KSI_ADDR_CARD_ZIP => 'card zipcode', KSI_ADDR_CARD_COUNTRY => 'card country', KSI_CUST_CHECK_NUM => 'check number', KSI_CUST_PAY_EMAIL => 'customer email', KSI_CUST_PAY_PHONE => 'customer phone',
KSI_ITEM_TOTAL => 'item total', KSI_PER_ITEM_TOTAL => 's/h per-item total', KSI_PER_PKG_TOTAL => 's/h package total', );
if (defined('LIBMGR')) {
clsLibMgr::Add('strings', KFP_LIB.'/strings.php',__FILE__,__LINE__); clsLibMgr::Add('string.tplt', KFP_LIB.'/StringTemplate.php',__FILE__,__LINE__); clsLibMgr::Add('tree', KFP_LIB.'/tree.php',__FILE__,__LINE__); clsLibMgr::Add('vbz.store', KFP_LIB_VBZ.'/store.php',__FILE__,__LINE__); clsLibMgr::Load('strings',__FILE__,__LINE__); clsLibMgr::Load('string.tplt',__FILE__,__LINE__); clsLibMgr::Load('tree',__FILE__,__LINE__); clsLibMgr::Load('vbz.store',__FILE__,__LINE__);
} else { // assume all libraries are on path
require_once(KFP_LIB.'/strings.php'); require_once(KFP_LIB.'/StringTemplate.php'); require_once('store.php'); require_once(KFP_LIB.'/tree.php');
}
define('KS_VBZCART_SESSION_KEY','vbzcart_key');
// http query argument names define('KSQ_ARG_PAGE_DATA','page'); define('KSQ_ARG_PAGE_DEST','goto');
// http query values define('KSQ_PAGE_CART','cart'); // shopping cart define('KSQ_PAGE_SHIP','ship'); // shipping page define('KSQ_PAGE_PAY','pay'); // payment page define('KSQ_PAGE_CONF','conf'); // customer confirmation of order define('KSQ_PAGE_RCPT','rcpt'); // order receipt // if no page specified, go to the shipping info page (first page after cart): define('KSQ_PAGE_DEFAULT',KSQ_PAGE_SHIP);
/*
database class with creators for shop classes
- /
abstract class clsPageShop extends clsPage {
public function Sessions() {
return $this->Make('clsShopSessions');
} public function Clients() {
return $this->Make('clsShopClients');
} public function Carts() {
return $this->Make('clsShopCarts');
} public function CartLines() {
return $this->Make('clsShopCartLines');
} public function CartLog() {
return $this->Make('clsShopCartLog');
} public function Orders() {
return $this->Make('clsOrders');
} public function OrdLines() {
return $this->Make('clsOrderLines');
}
/*
public function OrderLog() {
return $this->Make('clsOrderLog');
}
- /
public function OrdMsgs() {
return $this->Make('clsOrderMsgs');
}
/*
public function Custs() {
return $this->Make('clsCusts');
} public function CustNames() {
return $this->Make('clsCustNames');
} public function CustAddrs() {
return $this->Make('clsCustAddrs');
} public function CustEmails() {
return $this->Make('clsCustEmails');
} public function CustPhones() {
return $this->Make('clsCustPhones');
} public function CustCCards() {
return $this->Make('clsCustCards');
}
- /
}
class clsPageCart extends clsPageShop {
protected $objSess; protected $objCart;
// process functions
public function DoPreContent() {
$this->inCkout = FALSE; // (2011-04-01) not sure why this is sometimes not getting set parent::DoPreContent();
} /*---- INPUT: $iCaller is for debugging and is discarded; caller should pass __METHOD__ as the argument. */ public function GetObjects($iCaller) {
$objSessions = $this->Sessions(); $this->objSess = $objSessions->GetCurrent(); // get whatever session is currently applicable (existing or new) $this->objCart = $this->objSess->CartObj(); $this->objCart->objSess = $this->objSess; // used for logging
} public function Cart($iObj=NULL) {
if (!is_null($iObj)) { $this->objCart = $iObj; } return $this->objCart;
} protected function HandleInput() {
$this->GetObjects(__METHOD__); $this->objCart->CheckData(); // check for any form data (added items, recalculations, etc.)
$this->strSheet = 'cart'; // cart stylesheet has a few different things in it
$this->strWikiPg = ; $this->strTitle = 'Shopping Cart'; // Displayed title (page header) $this->strName = 'shopping cart'; // HTML title $this->strTitleContext = 'this is your'; // 'Tomb of the...'; $this->strHdrXtra = ;
$this->strSideXtra = ; //'
- /
- /
- /
- /
- /
'; throw new exception('Unexpected argument type.'); } $this->objCart = $iCart; $this->intIndex = $iIndex; $this->strCtrlName = $iCtrlName; } protected function SaveThis() { } // define later public function DataType() { return $this->intIndex; } public function Value($iVal=NULL) { // overrides default action by loading data from database if needed if (!is_null($iVal)) { die('writing to read-only clsTreeNode object'); // this should never happen, because we're not using this class to write these values } if (!$this->Loaded()) { if (is_null($this->intIndex)) { $this->vVal = NULL; } else { $this->vVal = $this->objCart->DataItem($this->intIndex); } } else { } return $this->vVal; } public function CtrlName() { return $this->strCtrlName; } } class clsCartAddr extends clsContactFolder { //class clsCartAddr extends clsTreeNode { public $doFixed; public $doFixedCountry; // Country is sometimes determined by Zone public $strStateLabel; // label for "State" field public $strStatePost; // text for after "State" field public $lenStateField; // approximate width of "State" field public $strZipLabel; // label for "Zipcode"/"Postal code" field // protected function SaveThis() { } // stubbed off for customer-side pages /*---- OVERRIDE: Name is saved normally, but other data is saved in aggregate by SaveThis() */ protected function SaveSubs() { return $this->Name()->Save(); } public function Name() { return $this->Node('name'); } public function Street() { return $this->Node('street'); } public function City() { return $this->Node('city'); } public function State() { return $this->Node('state'); } public function Zip() { return $this->Node('zip'); } public function Country() { return $this->Node('country'); } public function HasCountry() { return $this->Exists('country'); } public function Instruc() { return $this->Node('instruc'); } public function Render($iCart) { // copy calculated stuff over to variables to make it easier to insert in formatted output: $ksName = $this->Name()->CtrlName(); $ksStreet = $this->Street()->CtrlName(); $ksCity = $this->City()->CtrlName(); $ksState = $this->State()->CtrlName(); $ksZip = $this->Zip()->CtrlName(); $ksCountry = $this->Country()->CtrlName(); $strName = $this->Name()->Value(); $strStreet = $this->Street()->Value(); $strCity = $this->City()->Value(); $strState = $this->State()->Value(); $strZip = $this->Zip()->Value(); $strCountry = $this->Country()->Value(); $strStateLabel = $this->strStateLabel; $strZipLabel = $this->strZipLabel; $lenState = $this->lenStateField; $strStateAfter = $this->strStatePost; if ($this->doFixedCountry) { $htCountry = ''.$strCountry.''; $htZone = ; } else { $htCountry = '<input name="'.$ksCountry.'" value="'.$strCountry.'" size=20>'; $htShipCombo = $this->htShipCombo; $htBtnRefresh = '<input type=submit name="update" value="Update Form">'; $htZone = " - change shippping zone: $htShipCombo $htBtnRefresh"; } if ($this->doFixedName) { $out = <<<__END__ Name: $strName __END__; } else { $out = <<<__END__ Name: <input name="$ksName" value="$strName" size=50> __END__; } $out .= $this->htmlBeforeAddress; if ($this->doFixed) { $out .= <<<__END__ Street Address
or P.O. Box: $strStreet City:$strCity $strStateLabel:$strState $strZipLabel:$strZip Country:$htCountry$htZone
__END__; } else { $out .= <<<__END__
Street Addressor P.O. Box: <textarea name="$ksStreet" cols=50 rows=3>$strStreet</textarea> City: <input name="$ksCity" value="$strCity" size=20> $strStateLabel: <input name="$ksState" value="$strState" size=$lenState>$strStateAfter $strZipLabel: <input name="$ksZip" value="$strZip" size=11> Country: $htCountry - change shippping zone: $htShipCombo $htBtnRefresh
__END__; } return $out;
} /*---- RETURNS: Complete address as single string, in multiple lines HISTORY:
2010-09-13 Added a line for Instruc()
*/ public function AsText($iLineSep="\n") {
$xts = new xtString($this->Street()->Value(),TRUE); $xts-> ReplaceSequence(chr(8).' ',' ',0); // replace any blank sequences with single space $xts->ReplaceSequence(chr(10).chr(13),$iLineSep,0); // replace any sequences of newlines with line sep string
$xts->Value .= $iLineSep.$this->City()->Value(); if ($this->State()->Filled()) { $xts->Value .= ', '.$this->State()->Value(); } if ($this->Zip()->Filled()) { $xts->Value .= ' '.$this->Zip()->Value(); } if ($this->Country()->Filled()) { $xts->Value .= ' '.$this->Country()->Value(); } if ($this->Instruc()->Filled()) { $xts->Value .= $iLineSep.$this->Instruc()->Value(); } return $xts->Value;
} public function AsSingleLine() {
return $this->AsText(' / ');
} public function AsSearchable() {
$out = $this->Street()->Value(); $out .= $this->City()->Value(); $out .= $this->State()->Value(); $out .= $this->Zip()->Value(); /* including the country when creating the search string causes issues:
if the country is domestic, then we don't want to include it (because many people won't)... so we need to have some way of determining whether this is so -- but that's currently locked inside the ShipZone data, which isn't always available. Need to have a mapping of countries to zones, and more rigorous handling of country inputs.
For now, just leave it out (how likely is it that an address in another country will match, including postal code?)
- /
// $out .= $this->Country()->Value(); $out = clsCustAddrs::Searchable($out); return $out;
}
} // contact fields class clsCartContact extends clsContactFolder { // public $Addr;
public $doFixed;
/*---- HISTORY:
2011-10-05 Apparently, when addr is the same for payment and shipping, the default is to put it under payment rather than with the other contact info (phone, email).
If I can't figure out why this is, I'll probably switch it around later.
In any case, this looks first locally, and then under (parent)->payment
*/ public function Addr() {
if ($this->Exists('addr')) { return $this->Node('addr'); } elseif ($this->Parent()->Exists('payment')) { $objPay = $this->Parent()->Node('payment'); if ($objPay->Exists('addr')) { return $objPay->Node('addr'); } } return NULL;
} public function HasEmail() {
return $this->Exists('email');
} public function Email() {
return $this->Node('email');
} public function HasPhone() {
return $this->Exists('phone');
} public function Phone() {
return $this->Node('phone');
} protected function Person() {
return $this->Parent();
} public function Render() {
$hrefForSpam = '<a href="'.KWP_WIKI.'Anti-Spam_Policy">';
// copy any needed constants over to variables for parsing: $ksEmail = $this->Email()->CtrlName(); $ksPhone = $this->Phone()->CtrlName();
$strEmail = $this->Email()->Value(); $strPhone = $this->Phone()->Value();
if ($this->doFixed) { $out = <<<__END__
Email:$strEmail Phone:$strPhone__END__; } else { $out = <<<__END__
Email: <input name="$ksEmail" value="$strEmail" size=30> {$hrefForSpam}anti-spam policy</a> Phone: <input name="$ksPhone" value="$strPhone" size=20> (optional) __END__; } return $out; } } // a Payment has an Address, a Number (/CVV/Exp) and a Name class clsPayment extends clsContactFolder { //public $Name; // is this needed? Can be found at Addr->Name() // public $Addr; // protected function SaveThis() { } // stubbed for customer-side public function CustName() { return $this->Addr()->Node('name'); } public function Addr() { return $this->Node('addr'); } /*----- USED BY: admin functions */ public function MakeAddr($iVal) { $objNode = new clsTreeNode(); $objNode->Value($iVal); $this->Node('addr',$objNode); } public function Num() { return $this->Node('num'); } /*----- USED BY: admin functions */ public function MakeNum($iVal) { $objNode = new clsTreeNode(); $objNode->Value($iVal); $this->Node('num',$objNode); } public function Exp() { return $this->Node('exp'); } /*----- USED BY: admin functions */ public function MakeExp($iVal) { $objNode = new clsTreeNode(); $objNode->Value($iVal); $this->Node('exp',$objNode); } /*----- INPUT: iMaxFuture: if year is given as 2 digits, then this is the furthest in the future the year is allowed to be (# of years from now). NOTE: Should be tested with current dates after 2050 (or between 1950 and 1999) to make sure it doesn't allow a year too far in the past. OUTPUT: EXP as a DateTime object */ public function ExpDate($iMaxFuture=50) { $strExp = $this->Exp()->Value(); return clsCustCards::ExpDate($strExp,$iMaxFuture); } public function ExpDateSQL() { return clsCustCards::ExpDateSQL($this->Exp()->Value()); } public function CVV() { if ($this->Exists('cvv')) { return $this->Node('cvv')->Value(); } else { return NULL; } } /*---- ACTION: Return a description of the payment in a safe format (incomplete credit card number) TO DO: Allow for payment types other than credit card */ public function SafeDisplay() { $out = clsCustCards::SafeDescr_Long($this->Num()->Value(),$this->Exp()->Value()); $out .= ''.$this->Addr()->AsText("\n
"); return $out; } } /*---- RULES: * a Person must have two mailing addresses: one for shipping, one for payment * these addresses may be the same * a Person must have an email address * a Person may have a phone number * email and phone are not tied to mailing address, just to Person * a Person may (for now, must) have a card * a card has one mailing address
- /
- /
'.print_r($arXl,TRUE).''; echo 'ord scratch:
'.print_r($actOrdScratch->Values(),TRUE).'';
$acts->Add(new Script_Copy_Named($actOrdScratch,$iOrder,$arXl),'order.update.'.$strFormName);
//echo '#1:'.print_r($iOrder->Values(),TRUE).'';
// the name is just for readability/debugging; it is not used by code
- /
/* } }
// handle email address if (is_null($strEmail)) { $acts->Add(new Script_Status('No email address given.')); } else { // make new email record, unless an identical one exists: $acts->Add(new Script_Status('Adding email '.$strEmail)); $act = $tblEmails->Make_Script($idCust, $strEmail); $acts->Add($act); }
// handle phone number if (is_null($strPhone)) { $acts->Add(new Script_Status('No phone number given.')); } else { // make new phone record, unless an identical one exists: $acts->Add(new Script_Status('Adding phone '.$strPhone)); // THIS FUNCTION HAS NOT BEEN WRITTEN YET -- reuse code from email class $act = $tblPhones->Make_Script($idCust, $strPhone); $acts->Add($act); }
// handle credit card information if (is_null($objCCard)) { $acts->Add(new Script_Status('No credit card information given.')); // currently, we're not expecting this, as ccard is required } else { $acts->Add(new Script_Status('Adding ccard '.$objCCard->SafeDisplay())); $act = $tblCCards->Make_Script($idCust, $objCCard); $acts->Add($act);
$actIns = $act->Get_byName('ccard.make',TRUE); $actCopy = new Script_SQL_Use_ID($actIns,$iOrder,'ID_ChargeCard'); $acts->Add($actCopy); } } /*
By this time, $iOrder should have data for updating these fields: * ID_Buyer * ID_Recip * ID_NameBuyer * ID_NameRecip * ID_ContactAddrRecip * ID_ChargeCard
Add one more thing:
- /
/* $iOrder->Value('WhenPrepped','NOW()'); // mark the order as "prepped"
//echo '#2:'.print_r($iOrder->Values(),TRUE).'';
$acts->Add($iOrder,'ord.upd'); }
return $acts;
}
- /
/* 2011-11-29 this can't possibly work -- $strFormName is used but never defined
protected function ImportContact(Script_Script $acts, $type, $obj) {
$acts->Add(new Script_Status('CHECKING '.$type));
$strName = $obj->Name()->Value(); $strKey = strtolower($strName);
$acts->Add(new Script_Status('ADDING NAME: '.$strName));
$actOrdScratch = new Script_RowObj_scratch('scratch order'); // ^ this will be copied to order update action ($iOrder) after name & address are set
// only add names that are different //if (array_key_exists($strKey,$arNames)) { $objName = $tblNames->Find($strKey,$idCust); if ($objName->HasRows()) { $objName->FirstRow(); $id = $objName->KeyValue(); $acts->Add(new Script_Status('SAME as existing name ID='.$id)); $actOrdScratch->Value('name.'.$strFormName,$id); } else { $arAct = $tblNames->Create_SQL($idCust,$strName); $act = new Script_Tbl_Insert($tblNames,$arAct); $acts->Add(new Script_SQL_Use_ID($act,$actOrdScratch,'name')); }
$strKey = $obj->AsSearchable(); $acts->Add(new Script_Status('ADDING ADDRESS: '.$strKey));
$acts->Add($tblAddrs->Make_Script($obj,$idCust,$actOrdScratch));
switch ($type) { case 'card': $sqlOrdContField = 'ID_Buyer'; $sqlOrdNameField = 'ID_NameBuyer'; $sqlOrdAddrField = NULL; break; case 'ship': $sqlOrdContField = 'ID_Recip'; $sqlOrdNameField = 'ID_NameRecip'; $sqlOrdAddrField = 'ID_ContactAddrRecip'; break; } $arXl = array( $sqlOrdAddrField => 'addr', $sqlOrdNameField => 'name' ); $iOrder->Value($sqlOrdContField,$idCust);
echo 'xfer:'.print_r($arXl,TRUE).''; echo 'ord scratch:
'.print_r($actOrdScratch->Values(),TRUE).'';
$acts->Add(new Script_Copy_Named($actOrdScratch,$iOrder,$arXl),'order.update.'.$strFormName);
//echo '#1:'.print_r($iOrder->Values(),TRUE).'';
// the name is just for readability/debugging; it is not used by code
}
- /
// this is the OLD, non-scripted version (it didn't work very well either)
public function DoResolve($iDB,$iContactID) {
$objDB = $iDB; $objPerson = $this;
$strChoice = $iContactID;
if (!is_null($strChoice)) { $strStat = $objPerson->Descr().': ';
if ($strChoice == 'new') { $doNew = TRUE; $doAdd = FALSE; } else { $doNew = FALSE; if (is_numeric($strChoice)) { $doAdd = TRUE; } else { $doAdd = FALSE; } }
if ($doNew || $doAdd) { if ($doNew) { $objContact = $objPerson->Contact(); if (is_object($objContact)) { // make new customer record, (shipping) address record, name record: $objCust = $objDB->Custs()->Make_fromCartAddr($objContact->Addr); $idCust = $objCust->ID; $strStat .= 'created new contact ID='.$idCust; } else { throw new exception('Person object has no Contact object.'); } } if ($doAdd) { $idCust = (int)$strChoice; $strStat .= 'resolved as contact ID='.$idCust;
$objAddrs = $objPerson->Addrs(); foreach ($objAddrs as $type => $obj) { $strStat .= ' / '.$type.':'; $strName = $obj->Name()->Value(); $arAction[] = 'making name record'; $idName = $objDB->CustNames()->Create($idCust,$strName); $strStat .= ' ID_Name='.$idName; $arAction[] = 'making address record'; $idAddr = $objDB->CustAddrs()->Create($idCust,$obj); $strStat .= ' ID_Addr='.$idAddr; $arAction[] = 'type='.$type.' idName='.$idName.' idAddr='.$idAddr.''; switch ($type) { case 'card': $idNameBuyer = $idName; $arOrdUpd['ID_NameBuyer'] = $idName; break; case 'ship': $idNameRecip = $idName; $idAddrRecip = $idAddr; $arOrdUpd['ID_NameRecip'] = $idName; $arOrdUpd['ID_ContactAddrRecip'] = $idAddr; break; } } $strStat .= ' / '; } $this->SubValue('id',$idCust); // 2011-09-22 what does this do? Where is it defined? //$arOrdUpd['ID_Cust'] = $idCust;
$arAction = NULL; $arUpdEv = NULL;
// make new email record, unless an identical one exists: if ($objPerson->HasEmail()) { $strEmail = $objPerson->Contact()->Email()->Value(); $arAction[] = 'making email record for ['.$strEmail.']'; $idEmail = $objDB->CustEmails()->Create($idCust, $strEmail); /* $objDB->LogEvent( __METHOD__, '|idCust='.$idCust.'|strEmail='.SQLValue($strEmail), 'Made email record; SQL='.SQLValue($sql), '+EM',FALSE,FALSE);
- /
$arEv = array( 'where' => __METHOD__, 'params' => '|idCust='.$idCust.'|strEmail='.SQLValue($strEmail), 'descr' => 'Made email record; SQL='.SQLValue($sql), 'code' => '+EM' ); $arUpdEv[] = $arEv;
/* // 2009-11-28 at present, we don't attach specific email or phone IDs to orders or contacts $arOrdUpd['ID_Email'] = $idEmail; $strStat .= ' ID_Email='.$idEmail; */ } // make new phone record, unless an identical one exists: if ($objPerson->HasPhone()) { $strPhone = $objPerson->Contact->Phone()->Value(); $arAction[] = 'making phone record for ['.$strPhone.']'; $idPhone = $objDB->CustPhones()->Create($idCust, $strPhone); /* $objDB->LogEvent( __METHOD__, '|idCust='.$idCust.'|strPhone='.SQLValue($strPhone), 'Made phone record; SQL='.SQLValue($sql), '+PH',FALSE,FALSE); // ($iWhere,$iParams,$iDescr,$iCode,$iIsError,$iIsSevere
- /
$arEv = array( 'where' => __METHOD__, 'params' => '|idCust='.$idCust.'|strPhone='.SQLValue($strPhone), 'descr' => 'Made phone record; SQL='.SQLValue($sql), 'code' => '+PH' ); $arUpdEv[] = $arEv; /* // 2009-11-28 at present, we don't attach specific email or phone IDs to orders or contacts $arOrdUpd['ID_Phone'] = $idPhone; $strStat .= ' ID_Phone='.$idPhone; */ } // make new ccard record, unless an identical one exists: if ($objPerson->HasCCard()) { $arAction[] = 'making ccard record'; $idCard = $objDB->CustCards()->Create($idCust, $objPerson->Payment()); $objDB->LogEvent( __METHOD__, '|idCust='.$idCust, 'Made ccard record; SQL='.SQLValue($sql), '+CC',FALSE,FALSE); assert('!empty($idCard) /* type='.get_class($objDB->CustCards()).' */'); $arOrdUpd['ID_ChargeCard'] = $idCard; $strStat .= ' ID_ChargeCard='.$idCard; } // if customer is shipper, write ID_Name and ID_Addr back to customer record: if (isset($idNameRecip)) { $arAction[] = 'updating contact with name/addr'; $arUpd['ID_Name'] = $idNameRecip; $arUpd['ID_Addr'] = $idAddrRecip; $objDB->Custs()->Update($arUpd,'ID='.$idCust); } else { $arAction[] = 'recipient details not in this object'; } } else { // log invalid input $strStat = 'Could not resolve - invalid choice "'.$strChoice.'".'; } } else { $strStat = 'No contact specified for resolution (how did we even get here?).'; }
$arUpdOrd['upd-ord'] = $arOrdUpd; $arUpdOrd['stat'] = $strStat;
$arOut['status'] = $strStat; $arOut['action'] = $arAction; $arOut['upd.ord'] = $arUpdOrd;
return $arOut;
}
} /*==================
CLASS: clsShipZone PURPOSE: shipping zone functions
- /
class clsShipZone {
public function Abbr($iAbbr=NULL) {
if (!is_null($iAbbr)) {
$this->strAbbr = $iAbbr;
}
if (empty($this->strAbbr)) {
//echo '
RESETTING SHIPZONE; WAS '.$this->ShipZone;
$this->strAbbr = 'US'; // TO DO: set from configurable parameter
}
return $this->strAbbr;
} public function Text() {
global $listShipListDesc;
return $listShipListDesc[$this->Abbr()];
} public function hasState() {
switch ($this->Abbr()) { case 'US': return TRUE; break; case 'CA': return TRUE; break; default: return FALSE; break; }
} public function StateLabel() {
switch ($this->Abbr()) { case 'US': return 'State'; break; case 'CA': return 'Province'; break; default: return 'County/Province'; break; }
} public function PostalCodeName() {
switch ($this->Abbr()) { case 'US': return 'Zip Code™'; break; default: return 'Postal Code'; break; }
} public function Country() {
switch ($this->strAbbr) { case 'US': return 'United States'; break; case 'CA': return 'Canada'; break; default: return NULL; break; }
} public function isDomestic() {
return ($this->Abbr() == 'US');
} public function ComboBox() {
global $listShipListDesc;
$strZoneCode = $this->Abbr(); $out = '<select name="ship-zone">'; foreach ($listShipListDesc as $key => $descr) { //$dest (keys(%listShipListDesc)) { $strZoneDesc = $descr; if ($key == $strZoneCode) { $htSelect = " selected"; } else { $strZoneDesc .= " - recalculate"; $htSelect = ""; } $out .= '<option'.$htSelect.' value="'.$key.'">'.$strZoneDesc.'</option>'; } $out .= '</select>'; return $out;
}
}
// ShopCart
class clsShopCarts extends clsTable {
const TableName='shop_cart';
public function __construct($iDB) {
parent::__construct($iDB); $this->Name(self::TableName); $this->KeyName('ID'); $this->ClassSng('clsShopCart'); $this->ActionKey('cart');
}
} class clsShopCart extends clsDataSet {
public $objShipZone; private $arDataItem; protected $objOrder;
protected $hasDetails; // customer details have been loaded? protected $objDataTree; protected $objAddrShip; protected $objAddrCard; protected $objContDest; protected $objContCust; protected $objShip; protected $objCust;
public function __construct(clsDatabase $iDB=NULL, $iRes=NULL, array $iRow=NULL) {
parent::__construct($iDB,$iRes,$iRow); $this->objShipZone = new clsShipZone(); $this->hasDetails = FALSE;
} public function InitNew($iSess) {
$this->ID = 0; $this->WhenCreated = NULL; // not created until saved $this->WhenViewed = NULL; $this->WhenUpdated = NULL; $this->WhenOrdered = NULL; $this->ID_Sess = $iSess; $this->ID_Order = NULL; $this->ID_Cust = NULL;
} /*==== BLOCK: EVENT HANDLING HISTORY:
2011-03-27 copied from VbzAdminCustCard to VbzAdminCart Then moved from VbzAdminCart to clsShopCart
*/ protected function Log() {
if (!is_object($this->logger)) { $this->logger = new clsLogger_DataSet($this,$this->objDB->Events()); } return $this->logger;
} public function StartEvent(array $iArgs) {
return $this->Log()->StartEvent($iArgs);
} public function FinishEvent(array $iArgs=NULL) {
return $this->Log()->FinishEvent($iArgs);
} public function EventListing() {
return $this->Log()->EventListing();
} // specialized event logging (deprecated) public function LogEvent($iCode,$iDescr,$iUser=NULL) {
global $vgUserName;
$strUser = is_null($iUser)?$vgUserName:$iUser; $this->objDB->CartLog()->Add($this,$iCode,$iDescr,$strUser);
} //==== /*---- HISTORY:
2010-12-31 Created so placed orders do not get "stuck" in user's browser 2011-02-07 Doesn't work; same cart still comes up (though at least it generates a new order... but it pulls up all the same contact info) 2011-03-27 Changed flag from ID_Order to WhenOrdered OR WhenVoided, because we don't want to have to clear ID_Order anymore. Carts should retain their order ID.
*/ public function IsLocked() {
return $this->IsOrdered() || $this->IsVoided();
} /*---- RETURNS: TRUE if the cart has been converted to an order USED BY: $this->IsLocked() and (something)->IsUsable() HISTORY:
2011-03-27 written for improved handling of cart status at checkout
*/ public function IsOrdered() {
return !(is_null($this->WhenOrdered));
} /*---- RETURNS: TRUE if the cart has been discarded (voided) USED BY: $this->IsLocked() and (something)->IsUsable() HISTORY:
2011-03-27 written for improved handling of cart status at checkout
*/ public function IsVoided() {
return !(is_null($this->WhenVoided));
}
// == GENERAL DATA UTILITY FUNCTIONS
public function GetDataItem($iType) {
global $sql; // for debugging
if (isset($this->arDataItem[$iType])) { return $this->arDataItem[$iType]; } else { $sqlType = $this->objDB->SafeParam($iType); $sql = 'SELECT Val FROM '.KST_CART_DATA.' WHERE (ID_Cart='.$this->ID.') AND (Type="'.$sqlType.'");';
$objItem = $this->objDB->DataSet($sql); if ($objItem->HasRows()) { $objItem->FirstRow(); return $objItem->Val; } else { return NULL; } }
} public function PutDataItem($iType,$iVal,$iForce=FALSE) {
global $sql; // for debugging
$sqlType = $this->objDB->SafeParam($iType); $sqlFilt = '(ID_Cart='.$this->ID.') AND (Type="'.$iType.'")'; if ($iVal == ) { if ($iForce) { // delete the entry $sql = 'DELETE FROM '.KST_CART_DATA.' WHERE '.$sqlFilt; $ok = $this->objDB->Exec($sql); return $ok; } else { return FALSE; } } else { $sqlVal = $this->objDB->SafeParam($iVal); // check to see if item exists (UPDATE) or not (INSERT): $objRows = $this->objDB->DataSet('SELECT Val FROM '.KST_CART_DATA.' WHERE '.$sqlFilt); $qrows = $objRows->RowCount(); if ($qrows == 0) { $sql = 'INSERT INTO '.KST_CART_DATA.' (ID_Cart,Type,Val) VALUES('.$this->ID.','.$iType.',"'.$sqlVal.'");'; $ok = $this->objDB->Exec($sql); } elseif ($qrows == 1) { $sql = 'UPDATE '.KST_CART_DATA.' SET Val="'.$sqlVal.'" WHERE '.$sqlFilt.';'; $ok = $this->objDB->Exec($sql); } else { // the db should never let this happen since ID_Cart+Type is the primary key, but leaving in as a sanity check for now. $this->objDB->LogEvent('cart.data.write','type='.$iType.' val='.$sqlVal,$qrows.' rows match: too many','DUP',TRUE,TRUE); $ok = FALSE; } return $ok; }
} public function DataItem($iType,$iVal=NULL,$iForce=FALSE) {
if ($this->HasCart()) { if (is_null($iVal)) { return $this->GetDataItem($iType); } else { return $this->PutDataItem($iType,$iVal,$iForce); } } else { return NULL; }
}
// == STATUS
public function HasCart() {
return $this->IsCreated(); // may use different criteria later on
} public function HasSession() {
$ok = FALSE; if ($this->HasField('ID_Sess')) { if ($this->ID_Sess>0) { $ok = TRUE; } } return $ok;
} public function Session() {
if ($this->HasSession()) { $objSessions = $this->objDB->Sessions(); $objSess = $objSessions->GetItem($this->ID_Sess); return $objSess; } else { return NULL; }
} // DEPRECATED - use OrderObj() public function Order() {
return $this->OrderObj();
} /*---- RETURNS: Order object */ public function OrderObj() {
$doGet = TRUE; if (isset($this->objOrder)) { if ($this->objOrder->ID == $this->ID_Order) { $doGet = FALSE; } } if ($doGet) { $this->objOrder = $this->objDB->Orders()->GetItem($this->ID_Order); } return $this->objOrder;
} public function HasLines() {
$objLines = $this->GetLines(); if (is_null($objLines)) { return FALSE; } else { return $objLines->hasRows(); }
} public function LineCount() {
if ($this->HasLines()) { return $this->objLines->RowCount(); } else { return 0; }
} public function GetLines($iRefresh=TRUE) {
if ($iRefresh || (!isset($this->objLines))) { if ($this->IsCreated()) { //$this->objLines = $this->objDB->CartLines()->GetData('(ID_Cart='.$this->ID.') AND (Qty>0)','clsShopCartLine'); $this->objLines = $this->objDB->CartLines()->GetData('(ID_Cart='.$this->ID.') AND (Qty>0)'); } else { $this->objLines = NULL; } } return $this->objLines;
} public function IsCreated() {
return ($this->ID > 0); //return !is_null($this->ID); //return $this->hasField('ID') || isset($this->ID)
} /*---- RETURNS: TRUE iff customer has any known email addresses public function HasEmail() {
$isShipCard = $this->DataItem(KSI_SHIP_IS_CARD); $isShipSelf = $this->DataItem(KSI_SHIP_TO_SELF); // $objCart->ContCustObj()->Email()->Value() if ($isShipSelf) { return $this->ContCustObj()->HasEmail();
} public function EmailObj() { }
- /
// == FORM HANDLING STUFF
public function CheckData() {
// check for buttons $doCheckout = isset($_POST['finish']); $isCart = (isset($_POST['recalc']) || $doCheckout); $isZoneSet = FALSE; // check for specific actions if (isset($_GET['action'])) { $strDo = $_GET['action']; switch ($strDo) { case 'del': $intItem = 0+$_GET['item']; $this->GetLines(); $this->objLines->Update(array('Qty'=>0),'ID_Item='.$intItem); $this->LogEvent('del','deleting from cart: ID '.$intItem); break; case 'delcart'; $this->LogEvent('clr','voiding cart'); $this->ID = -1; $this->objSess->DropCart(); break; } } else { foreach ($_POST as $key => $val) {
// check for added items:
if (substr($key,0,4) == 'qty-') { if (($val != ) && ($val != 0)) { $sqlCatNum = $this->objDB->SafeParam(substr($key,4)); if ($isCart) { // zero out all items, so only items in visible cart will be retained: $this->ZeroAll(); } $this->AddItem($sqlCatNum,$val); } } elseif ($key == KSF_SHIP_ZONE) { // $custShipZone = $this->GetFormItem(KSF_SHIP_ZONE); $custShipZone = $val; $this->DataItem(KSI_SHIP_ZONE,$custShipZone); $this->objShipZone->Abbr($custShipZone); $isZoneSet = TRUE; } } } if (!$isZoneSet) { $this->objShipZone->Abbr($this->DataItem(KSI_SHIP_ZONE)); } if ($doCheckout) { $this->LogEvent('ck1','going to checkout'); $objSess = $this->Session(); //http_redirect(KWP_CHKOUT,array(KS_VBZCART_SESSION_KEY => $objSess->SessKey())); //http_redirect(KWP_CHKOUT.'?'.KS_VBZCART_SESSION_KEY.'='.$objSess->SessKey()); http_redirect(KWP_CHKOUT); // http_redirect('https://ssl.vbz.net/phpinfo.php'); $this->LogEvent('ck2','sent redirect to checkout'); }
} public function ZeroAll() {
$this->Update(array('Qty'=>0),'ID_Cart='.$this->ID);
} public function AddItem($iCatNum,$iQty) {
$this->Build(); // make sure there's a record for the cart, get ID $objCartLines = $this->objDB->CartLines(); $objCartLines->Add($this->ID,$iCatNum,$iQty); $this->LogEvent('add','adding to cart: cat# '.$iCatNum.' qty '.$iQty);
} /*----- ACTION:
* make sure there is a cart record * update the quantity, if there is one
*/ public function Build() {
$id = $this->ID; if (empty($id)) { $this->Create(); }
} public function Create() {
$sql = 'INSERT INTO `'.clsShopCarts::TableName.'` (WhenCreated,ID_Sess)'. 'VALUES(NOW(),'.$this->ID_Sess.');'; $this->objDB->Exec($sql); $this->ID = $this->objDB->NewID('carts.create'); $objSess = $this->objDB->Sessions()->GetCurrent(); if (!is_object($objSess->Table)) { throw new exception('Session object has no table for Cart ID='.$this->Value('ID')); } $objSess->SetCart($this->ID);
} public function RenderHdr() {
$out = "\n".;
$out .= "\n
|