VbzCart/archive/code/files/shop.php/2011-12-18

from HTYP, the free directory anyone can edit if they can prove to me that they're not a spambot
< VbzCart‎ | archive‎ | code‎ | files‎ | shop.php
Jump to navigation Jump to search

About

This was an attempt to redo the API so sub-nodes would have better access to the whole script. It made things hopelessly tangled, so I had to abandon it and hope that I could work out another (better) way.

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 = ; //'

Cat #: '.$this->strReq; $this->strSheet = KSQ_PAGE_CART; // default } protected function DoContent() { echo $this->objCart->Render(); } } /* ==================== *\ || -- HELPER CLASSES -- || \* ==================== */ /*==== PURPOSE: parent class for all cart contact nodes HISTORY: 2011-11-26 started experimentally
  • /
abstract class clsContactNode extends clsTreeNode { protected $actSave; // script created to record this data; NULL = not created yet public function __construct($iNodes=NULL) { parent::__construct($iNodes); $this->actSave = NULL; } public function ScriptMe() { return $this->actSave; } /* protected function ScriptUp() { if ($this->HasParent()) { return $this->Parent()->ScriptMe(); } else { return NULL; } }
  • /
public function ScriptRoot() { if (is_null($this->Parent())) { throw new exception('Internal Error: Expecting parent, but it is NULL. CLASS: '.get_class($this).' CTRL: '.$this->CtrlName()); } return $this->Parent()->ScriptRoot(); } /*---- RETURNS: a "starter" script object to use for generating this object's scripts There's probably a better way to do this. */ public function ScriptStart() { return new Script_Script(); } public function Engine() { if (is_null($this->Parent())) { throw new exception('Object (class '.get_class($this).') has no parent.'); } return $this->Parent()->Engine(); } /*---- INPUT: $iScript -- the script currently being built. Avoid adding directly to it; add a folder, then add to the folder. Pass the folder, not iScript, on to the next level. */ public function Save(Script_Script $iScript) { $acts = NULL; echo '
SAVING '.get_class($this); if ($this->AccessCount() == 0) { $acts = $this->ScriptStart(); $iScript->Add($acts,get_class($this).'.save'); $this->SaveThis($acts); $this->SaveSubs($acts); $this->SavePost($acts); } else { echo ' - SKIPPED'; } $this->IncCount(); $this->actSave = $acts; return $acts; } abstract protected function SaveThis(Script_Script $iScript); protected function SavePost(Script_Script $iScript) {} /*---- ACTION: Save sub-nodes */ protected function SaveSubs(Script_Script $iScript) { if ($this->HasNodes()) { $acts = $this->ScriptStart(); $iScript->Add($acts,get_class($this).'.subs'); $ar = $this->Nodes(); foreach ($ar as $name => $obj) { $obj->Save($acts); } } else { $acts = NULL; } return $acts; } } /*==== PURPOSE: a contact node that holds other nodes but has no data of its own HISTORY: 2011-11-29 created
  • /
class clsContactFolder extends clsContactNode { protected function SaveThis(Script_Script $iScript) {} // nothing to save here; only saves sub-nodes } class clsContactFolder_reference extends clsContactFolder { protected function SaveSubs(Script_Script $iScript) {} // do not save sub-nodes; they are for reference only /*---- OVERRIDE: does not set the nodes' parents, because they may be parented elsewhere Otherwise we might get recursion. */ /* not sure if this is actually necessary; the problem seems to be something else protected function NodeAdd($iName,clsTreeNode $iNode) { $this->arSubs[$iName] = $iNode; }
  • /
} class clsContactRoot extends clsContactFolder { protected $objDB; protected $actSave; public function __construct(clsPageShop $iDB) { $this->objDB = $iDB; $this->actSave = NULL; } public function Engine() { return $this->objDB; } public function ScriptRoot() { if (is_null($this->actSave)) { $this->actSave = $this->ScriptStart(); } return $this->actSave; } public function Save(Script_Script $iScript) { global $dbgIdx; $dbgIdx = 0; $this->ResetCount(); return parent::Save($iScript); } } /*==== PURPOSE: Generic cart data field, no special handling HISTORY: 2011-11-29 changed ancestry from clsTreeNode to clsContactNode
  • /
class clsCartField extends clsContactNode { protected $objCart; protected $intIndex; // index of field type protected $strCtrlName; // name of control (on form) public function __construct($iCart, $iIndex, $iCtrlName) { if (is_object($iIndex)) { echo 'Internal error: argument $iIndex is an object of class '.get_class($iIndex).', not a scalar.
'; throw new exception('Unexpected argument type.'); } $this->objCart = $iCart; $this->intIndex = $iIndex; $this->strCtrlName = $iCtrlName; } protected function SaveThis(Script_Script $iScript) { } // 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(Script_Script $iScript) { return $this->Name()->Save($iScript); } 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 Address
or 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
  • /
class clsPerson extends clsContactFolder { /* public $Contact; public $Payment;
  • /
protected $strName; // for forms private $strDescr; // for text messages public function __construct($iName,$iDescr) { $this->strName = $iName; $this->strDescr = $iDescr; } public function Descr($iDescr=NULL) { if (!is_null($iDescr)) { $this->strDescr = $iDescr; } return $this->strDescr; } public function CtrlName() { return $this->strName; } public function HasContact() { return $this->Exists('contact'); } public function Contact() { return $this->Node('contact'); } public function Payment() { return $this->Node('payment'); } //=== EMAIL functions /*---- HISTORY: 2011-09-23 This totally wasn't working. Is now. clsTreeNode could probably use some tidying. */ public function HasEmail() { $out = FALSE; if ($this->Exists('contact')) { $objCont = $this->Contact(); if ($objCont->Exists('email')) { $objEmail = $objCont->Node('email'); $out = $objEmail->Filled(); } } return $out; } /*---- HISTORY: 2011-09-23 written to simplify things */ public function GetEmail() { if ($this->HasEmail()) { $out = $this->Contact()->Email()->Value(); } else { $out = NULL; } return $out; } //=== PHONE functions public function HasPhone() { $out = FALSE; if ($this->Exists('contact')) { $objCont = $this->Contact(); if ($objCont->Exists('phone')) { $objPhone = $objCont->Node('phone'); $out = $objPhone->Filled(); } } return $out; } /*---- HISTORY: 2011-09-23 written to simplify things */ public function GetPhone() { if ($this->HasPhone()) { $out = $this->Contact()->Phone()->Value(); } else { $out = NULL; } return $out; } //=== CCARD functions public function HasCCard() { $out = $this->Exists('payment'); return $out; } /*---- HISTORY: 2011-09-23 written to simplify things NOTE: slightly different from email/phone; returns object, not string */ public function GetCCard() { if ($this->HasCCard()) { return $this->Payment(); } else { return NULL; } } //=== /*---- HISTORY: 2011-11-21 fixed a bug where $objAddr was being called as a function, so presumably this method was not working before now. */ public function Addrs() { $arOut = NULL; if ($this->HasCCard()) { $arOut['card'] = $this->Payment()->Addr(); } $objAddr = $this->Contact()->Addr(); if (!is_null($objAddr)) { $arOut['ship'] = $objAddr; } return $arOut; } /*---- PURPOSE: The scripted version of DoResolve. EXPERIMENTAL/DEBUGGING HISTORY: 2011-09-21 started 2011-10-08 mostly working 2011-11-21 Script_DataRow was being called as a function instead of new object; fixed. Completed other identifier name changes. INPUT: <form> data: depends on which contact data is being resolved; there can be 1 or 2 different sets $iOrder - changes to be made to the order record This function does not actually execute this, but only modifies it. It is up to the caller to execute it *after* calling this. $iContact - ID of existing contact record to update, or NULL if a new one should be created */ /* 2011-11-29 currently trying to obsolete this public function DoResolve_Script(Script_SQL_DataRow_Command $iOrder, $iShipSelf=FALSE) { global $wgRequest; $objPerson = $this; $strStat = NULL; $strFormName = $objPerson->FormName(); $strChoice = $wgRequest->GetText($strFormName); $doNew = FALSE; $doUpd = FALSE; if ($strChoice == 'new') { // creating completely new contact record $doNew = TRUE; $strStat = 'Creating new contact record(s)'; $idContact = NULL; } elseif (is_numeric($strChoice)) { // updating existing contact record (possibly creating new detail records) $doUpd = TRUE; $idContact = (int)$strChoice; $strStat = 'Using contact ID='.$idContact; } elseif (empty($strChoice)) { $strStat = 'Please choose which contact to use.'; } else { throw new exception('Unexpected value for contact ID: ['.$strChoice.']'); } $acts = new Script_Script(); $acts->Add(new Script_Status($strStat)); // display the status message if ($doNew || $doUpd) { // get some data for later use $strEmail = $objPerson->GetEmail(); $strPhone = $objPerson->GetPhone(); $objCCard = $objPerson->GetCCard(); // get some objects for later use $objDB = $iOrder->Engine(); $tblEmails = $objDB->CustEmails(); $tblPhones = $objDB->CustPhones(); $tblCCards = $objDB->CustCards(); if ($doNew) { $objContact = $objPerson->Contact(); if (is_object($objContact)) { // data integrity check successful $objAddr = $objContact->Addr(); if (is_null($objAddr)) { $acts->Add(new Script_Status('No address info')); } else { // we have some address data to record, so record it $actCust = $objDB->Custs()->Make_fromCartAddr_SQL($objAddr); $acts->Add(new Script_Status('ADDING CONTACT ADDRESS (check this!)')); $acts->Add($actCust,'cust'); // there needs to be a less script-structure-dependent way to do this: //$actCustIns = $actCust->Trial(); // get the insert action $actCustIns = $actCust->Get_byName('cust.ins',TRUE); $actCustXfer = $actCust->Get_byName('cust.id.xfer',TRUE); // make sure email information is saved if (!is_null($strEmail)) { // CHANGE TO Make_Script() $actCustXfer->Add($tblEmails->Script_forAdd($strEmail, $actCustIns),'cust.email'); } // make sure phone information is saved if (!is_null($strPhone)) { // CHANGE TO Make_Script() $actCustXfer->Add($tblPhones->Script_forAdd($strPhone, $actCustIns),'cust.phone'); } // make sure credit card information is saved if (!is_null($objCCard)) { $actAddr = $actCust->Get_byName('cust.addr',TRUE); $actCCard = $tblCCards->Make_Script($idCust, $objCCard); $actCustXfer->Add($actCCard,'cust.card'); //$actCustXfer->Add($tblCCards->Script_forAdd($objCCard, $actCustIns, $actAddr),'cust.card'); //$actFill_ID_toCard = new Script_SQL_Use_ID($actAddr,$arCardCreate,'ID_Cust'); } } } else { throw new exception('Person object has no Contact object.'); } } if ($doUpd) { $idCust = (int)$strChoice; $strStat .= 'resolved as contact ID='.$idCust; $objAddrs = $objPerson->Addrs(); // list of addresses from cart data echo $objPerson->DumpHTML(); die(); // create records for all names (will be either 1 or 2) associated with the order $tblNames = $objDB->CustNames(); // customer names table $tblAddrs = $objDB->CustAddrs(); // customer addresses table $out = NULL; if (!is_null($objAddrs)) { foreach ($objAddrs as $type => $obj) { if (!is_object($obj)) { throw new exception('value for '.$type.' is not an object.'); } $this->ImportContact($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

  • /

/* } }

// 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
";

$out .= "\n<form method=post action='./'>";

$out .= "\n\n"; return $out; } public function RenderFtr() { return KHT_CART_FTR; } /*---- ACTION: Render the receipt in HTML Shows the order as generated from *cart* data, not what's in the order record. ...except for the order number. HISTORY: 2011-03-27 adapting this from clsOrder::RenderReceipt() */ public function RenderReceipt() { $out = NULL; $objCart = $this; $objOrd = $this->OrderObj(); // load contact data assert('is_object($objOrd);'); if (($objOrd->ID == 0) || ($objCart->ID == 0)) { throw new exception('Receipt has missing object: Order ID='.$this->Value('ID_Order').', Cart ID='.$objCart->KeyValue()); } $objCart->GetDetailObjs(); $objPay = $objCart->PersonCustObj()->Payment(); $objAddrCard = $objCart->AddrCardObj(); // the next line is a kluge which only works as long as payment is always ccard // it's also not clear why GetDetailObjs() isn't loading it properly $objPay->Node('addr', $objAddrCard); $arVars = array( 'ord.num' => $objOrd->Number, 'timestamp' => date(KF_RCPT_TIMESTAMP), 'cart.id' => $objCart->ID, 'cart.detail' => $objCart->RenderConfirm(), 'ship.name' => $objCart->AddrShipObj()->Name()->Value(), 'ship.addr' => $objCart->AddrShipObj()->AsText("\n
"), 'pay.name' => $objPay->Addr()->Name()->Value(), 'pay.spec' => $objPay->SafeDisplay(), 'email.short' => 'orders-'.date('Y').'@vbz.net' ); $objStrTplt = new clsStringTemplate_array(NULL,NULL,$arVars); $objStrTplt->MarkedValue(KHT_RCPT_TPLT); $out = ; $out .= $objStrTplt->Replace(); return $out; } /*----- ACTION: Renders the order contents as plaintext, suitable for emailing */ public function RenderOrder_Text() { /* NOT USED HERE $isShipCard = $this->DataItem(KSI_SHIP_IS_CARD); $isShipSelf = $this->DataItem(KSI_SHIP_TO_SELF);
  • /
// copy any needed constants over to variables for parsing: $ksShipMsg = KSF_SHIP_MESSAGE; $ksfCustCardNum = KSF_CUST_CARD_NUM; $ksfCustCardExp = KSF_CUST_CARD_EXP; // get non-address field data: $strCardNum = $this->DataItem(KSI_CUST_CARD_NUM); $strCardExp = $this->DataItem(KSI_CUST_CARD_EXP); $strCustShipMsg = $this->DataItem(KSI_SHIP_MESSAGE); $ftCustShipMsg = wordwrap($strCustShipMsg); $out = ; $out .= "ITEMS ORDERED:\n"; $out .= $this->RenderCore_Text(); $this->doFixedCard = TRUE; $this->doFixedSelf = TRUE; $this->doFixedName = TRUE; $this->htmlBeforeAddress = ; $this->htmlBeforeContact = ; $out .= "\n\nSHIP TO:\n"; $out .= ' '.$this->AddrShipObj()->Name()->Value()."\n"; $out .= ' '.$this->AddrShipObj()->AsText("\n "); $out .= "\n"; $out .= "\n Email: ".$this->ContDestObj()->Email()->Value(); $out .= "\n Phone: ".$this->ContDestObj()->Phone()->Value(); $out .= "\n\n "; if (empty($strCustShipMsg)) { $out .= "(No special instructions)"; } else { $out .= "Special Instructions:\n$ftCustShipMsg"; } $out .= "\n\nPAYMENT:\n ".clsCustCards::SafeDescr_Long($strCardNum,$strCardExp)."\n"; $out .= ' '.$this->AddrCardObj()->Name()->Value()."\n"; $out .= ' '.$this->AddrCardObj()->AsText("\n "); $out .= "\n"; $out .= "\n Email: ".$this->ContCustObj()->Email()->Value(); $out .= "\n Phone: ".$this->ContCustObj()->Phone()->Value(); return $out; } public function RenderCore($iAsForm) { $strZone = $this->objShipZone->Abbr(); $shipMinCost = 0; $out = '' .'' .'' .'' .'' .'' .'' .'' .'' .'' .''; $rsLine = $this->objLines; while ($rsLine->NextRow()) { if ($iAsForm) { $out .= $rsLine->RenderForm($this); } else { $out .= $rsLine->RenderHtml($this); } if ($shipMinCost < $rsLine->ShipPkgDest) { $shipMinCost = $rsLine->ShipPkgDest; } $intQty = $rsLine->Qty; $this->CostTotalItem += $rsLine->CostItemQty; $this->CostTotalShip += $rsLine->CostShipQty; } // save official totals for order creation: // TO DO: are CostTotalItem and CostTotalShip referenced anywhere else? Make them local if not. // But if they are, then why isn't shipMinCost also a field? $this->DataItem(KSI_ITEM_TOTAL,$this->CostTotalItem); $this->DataItem(KSI_PER_ITEM_TOTAL,$this->CostTotalShip); $this->DataItem(KSI_PER_PKG_TOTAL,$shipMinCost); $strTotalMerch = FormatMoney($this->CostTotalItem); $strItemsShip = FormatMoney($this->CostTotalShip); $strTotalItems = FormatMoney($this->CostTotalItem + $this->CostTotalShip); $strShipZone = $this->objShipZone->Text(); $strShipDesc = $strShipZone.' s/h package cost:'; $strShipPkg = FormatMoney($shipMinCost); $strTotalDesc = 'order total if shipping to '.$strShipZone.':'; $strOrdTotal = FormatMoney($this->CostTotalItem + $this->CostTotalShip + $shipMinCost); $objSess = $this->Session(); if ($iAsForm) { $htDelAll = '[<a href="?action=delcart" title="remove all items from cart">remove all</a>]'; $htFirstTot = "";

$htZoneCombo = 'Shipping destination: '.$this->objShipZone->ComboBox(); } else {

$htFirstTot = '';

$htZoneCombo = 'Shipping costs shown assume shipment to '.$this->objShipZone->Text().' address.'; } $out .= <<<__END__

$htFirstTot

__END__; $this->LogEvent('disp','displaying cart, zone '.$this->objShipZone->Abbr().' total $'.$strOrdTotal); return $out;

   }
   /*-----
     RETURNS: The contents of the cart as text. Includes column headers and totals.
     USED BY: does anything actually use this, or was it intended for the email confirmation?
   */
   public function RenderCore_Text() {

$abbrShipZone = $this->DataItem(KSI_SHIP_ZONE); $this->objShipZone->Abbr($abbrShipZone);

$shipMinCost = 0;

$strLineFmt = '%-16s |%6.2f |%6.2f |%4d |%7.2f |%10.2f |%13.2f';

if ($this->HasRows()) { $hdr = "\n".sprintf('%-17s|%6s|%5s|%5s|%7s|%10s|%13s' ,'cat #' ,' $ ea. ' ,' $ s/h ' ,' qty ' ,' $ sale ' ,' $ s/h tot ' ,' $ LINE TOTAL'); $out = $hdr; $out .= "\n".str_repeat('-',strlen($hdr)); $objLines = $this->GetLines(); $dlrSaleTot = 0; // total sale before shipping $dlrPItmTot = 0; // per-item shipping total $dlrPPkgMax = 0; // per-pkg shipping total while ($objLines->NextRow()) { $objItem = $objLines->Item(); $dlrShipItm = $objItem->ShipPriceItem($abbrShipZone); $dlrShipPkg = $objItem->ShipPricePkg($abbrShipZone);

$out .= $objLines->RenderText($this,$strLineFmt); if ($dlrPPkgMax < $dlrShipPkg) { $dlrPPkgMax = $dlrShipPkg; } $intQty = $objLines->Qty; $dlrSaleTot += $objLines->PriceItem; $dlrPItmTot += $dlrShipItm*$intQty; } $out .= "\n".str_repeat('=',strlen($hdr));

$ftTotalMerch = sprintf('%6.2f',FormatMoney($dlrSaleTot)); $ftItemsShip = sprintf('%6.2f',FormatMoney($dlrPItmTot)); $ftTotalItems = sprintf('%6.2f',FormatMoney($dlrSaleTot + $dlrPItmTot)); $ftShipPkg = sprintf('%6.2f',FormatMoney($dlrPPkgMax)); //$ftTotalDesc = 'order total for shipping to '.$ftShipZone.':'; $ftOrdTotal = sprintf('%6.2f',FormatMoney($dlrSaleTot + $dlrPItmTot + $dlrPPkgMax));

// these items don't depend on cart contents, but there's no point in calculating them if the cart is empty: $ftShipZone = $this->objShipZone->Text(); $ftShipDesc = $ftShipZone.' s/h package cost:';

//$objSess = $this->Session();

$ftZone = $this->objShipZone->Text(); //$ftZone = $this->objShipZone->Abbr(); //$ftZone = $abbrShipZone;

$out .= "\n" ."\n * Sale: $ftTotalMerch" ."\n * S/H -" ."\n * per item sum: $ftItemsShip" ."\n * per package: $ftShipPkg" ."\n========================" ."\n==== FINAL TOTAL: $ftOrdTotal" ."\n\nShipping Zone: $ftZone"; } else { $out = "\nSorry, we seem to have goofed: this cart appears to have no items in it."; $out .= "\nThe webmaster is being alerted to the problem."; } return $out;

   }
   public function Render() {

// return rendering of current contents of cart $ok = FALSE; if ($this->ID) { if ($this->HasLines()) { $ok = TRUE; } }

if ($ok) {

  1. get information for that destination type:

$out = $this->RenderHdr(); $out .= $this->RenderCore(TRUE); $out .= $this->RenderFtr(); } else { if ($this->IsCreated()) { if (is_null($this->objLines)) { $out = "Internal error - cart data not available!"; $this->LogEvent('disp',"can't display cart - no data!"); $this->objDB->Events()->LogEvent('cart.render',,'cart data unavailable','cdna',TRUE,TRUE); } else { $out = "Your cart is empty."; $this->LogEvent('disp','displaying cart - empty; zone '.$this->objShipZone->Abbr()); } } else { $out = "You have not put anything in your cart yet."; $this->LogEvent('disp','displaying cart - nothing yet; zone '.$this->objShipZone->Abbr()); } } return $out;

   }
   /*----
   PURPOSE: Render cart for order confirmation page (read-only, no form controls)
   */
   public function RenderConfirm() {

if ($this->HasLines()) { $out = $this->RenderCore(FALSE); } else { // log error - you shouldn't be able to get to this point with an empty cart $txtParams = 'Cart ID='.$this->ID.' Order ID='.$this->ID_Order; $this->objDB->LogEvent('cart.renderconf',$txtParams,'cart empty at confirmation','cec',TRUE,TRUE); // also sends email alert $out = 'INTERNAL ERROR: cart data has become separated from browser. The webmaster has been notified.'; } return $out;

   }

/*==========

 CUSTOMER DATA AMALGAMATION
  • /
   public function AddrShipObj() {

if (empty($this->objAddrShip)) { $arFields = array( 'name' => $this->SpawnName(KSI_ADDR_SHIP_NAME ,KSF_ADDR_SHIP_NAME), 'street' => new clsCartField($this, KSI_ADDR_SHIP_STREET, KSF_ADDR_SHIP_STREET), 'city' => new clsCartField($this, KSI_ADDR_SHIP_CITY ,KSF_ADDR_SHIP_CITY), 'state' => new clsCartField($this, KSI_ADDR_SHIP_STATE ,KSF_ADDR_SHIP_STATE), 'zip' => new clsCartField($this, KSI_ADDR_SHIP_ZIP ,KSF_ADDR_SHIP_ZIP), 'country' => new clsCartField($this, KSI_ADDR_SHIP_COUNTRY,KSF_ADDR_SHIP_COUNTRY), 'instruc' => new clsCartField($this, KSI_SHIP_MESSAGE ,KSF_SHIP_MESSAGE) ); $this->objAddrShip = $this->SpawnAddress($arFields); } return $this->objAddrShip;

   }
   public function AddrCardObj() {

if (empty($this->objAddrCard)) { $arFields = array( 'name' => $this->SpawnName(KSI_ADDR_CARD_NAME ,KSF_CUST_CARD_NAME), 'street' => new clsCartField($this, KSI_ADDR_CARD_STREET ,KSF_CUST_CARD_STREET), 'city' => new clsCartField($this, KSI_ADDR_CARD_CITY ,KSF_CUST_CARD_CITY), 'state' => new clsCartField($this, KSI_ADDR_CARD_STATE ,KSF_CUST_CARD_STATE), 'zip' => new clsCartField($this, KSI_ADDR_CARD_ZIP ,KSF_CUST_CARD_ZIP), 'country' => new clsCartField($this, KSI_ADDR_CARD_COUNTRY,KSF_CUST_CARD_COUNTRY), 'instruc' => new clsCartField($this, NULL ,NULL) ); $this->objAddrCard = $this->SpawnAddress($arFields); } return $this->objAddrCard;

   }
   public function ContDestObj() {

if (empty($this->objContDest)) { $arFields = array( 'email' => $this->SpawnEmail(KSI_CUST_SHIP_EMAIL ,KSF_CUST_SHIP_EMAIL), 'phone' => $this->SpawnPhone(KSI_CUST_SHIP_PHONE ,KSF_CUST_SHIP_PHONE) ); $this->objContDest = $this->SpawnContact($arFields); // blank object for rendering } return $this->objContDest;

   }
   public function ContCustObj_raw() {

if (empty($this->objContCust)) { $arFields = array( 'email' => $this->SpawnEmail(KSI_CUST_PAY_EMAIL ,KSF_CUST_PAY_EMAIL), 'phone' => $this->SpawnPhone(KSI_CUST_PAY_PHONE ,KSF_CUST_PAY_PHONE) ); $this->objContCust = $this->SpawnContact($arFields); } return $this->objContCust;

   }
   /*----
     RETURNS: Contact information for the customer. If the "shipping to myself" box was checked,

then it returns the shipping contact information instead, since this is presumed to be the customer's contact information as well.

   */
   public function ContCustObj() {

//$isShipCard = $this->DataItem(KSI_SHIP_IS_CARD); $isShipSelf = $this->DataItem(KSI_SHIP_TO_SELF); if ($isShipSelf) { return $this->ContDestObj(); } else { return $this->ContCustObj_raw(); }

   }
   /*----
     METHOD: SpawnPerson()
     ACTION: creates clsPerson object or descendant

This lets us stub off admin functionality here and extend it later.

     HISTORY:

2011-11-29 created

   */
   public function SpawnPerson($iName,$iDescr) {

return new clsPerson($iName,$iDescr);

   }
   /*----
     METHOD: SpawnPayment()
     ACTION: creates clsPayment object or descendant

This lets us stub off admin functionality here and extend it later.

     HISTORY:

2011-11-29 created

   */
   public function SpawnPayment($iNodes=NULL) {

return new clsPayment($iNodes);

   }
   /*----
     METHOD: SpawnAddress()
     ACTION: creates clsCartAddr object or descendant

This lets us stub off admin functionality here and extend it later.

     HISTORY:

2011-11-29 created

   */
   public function SpawnAddress($iNodes=NULL) {

return new clsCartAddr($iNodes);

   }
   /*----
     METHOD: SpawnContact()
     ACTION: creates clsCartContact object or descendant

This lets us stub off admin functionality here and extend it later. The extra functionality may not actually be needed for this, but I went and created it anyway. (2011-12-15: yes, it was needed.)

     HISTORY:

2011-11-29 created

   */
   public function SpawnContact($iNodes=NULL) {

return new clsCartContact($iNodes);

   }
   /*----
     METHOD: SpawnEmail()
     ACTION: creates object for handling cart email address data

This lets us stub off admin functionality here and extend it later.

     HISTORY:

2011-11-29 created

   */
   public function SpawnEmail($iIndex, $iCtrlName) {

return new clsCartField($this, $iIndex, $iCtrlName);

   }
   /*----
     METHOD: SpawnPhone()
     ACTION: creates object for handling cart phone number data

This lets us stub off admin functionality here and extend it later.

     HISTORY:

2011-11-29 created

   */
   public function SpawnPhone($iIndex, $iCtrlName) {

return new clsCartField($this, $iIndex, $iCtrlName);

   }
   /*----
     METHOD: SpawnName()
     ACTION: creates object for handling person-name data

This lets us stub off admin functionality here and extend it later.

     HISTORY:

2011-11-30 created

   */
   public function SpawnName($iIndex, $iCtrlName) {

return new clsCartField($this, $iIndex, $iCtrlName);

   }
   /*----
     METHOD: clsShopCart->PersonCustObj()
     USED BY: clsShopCart->GetDetailObjs(), clsOrder->RenderReceipt()
     HISTORY:

2010-09-12 Extracted from clsShopCart->GetDetailObjs() so it can be used by clsOrder->RenderReceipt() 2011-11-29 Now using SpawnPayment() and SpawnPerson() instead of creating directly with "new"

   */
   public function PersonCustObj() {

if (empty($this->objCust)) { $arFields = array( 'num' => new clsCartField($this, KSI_CUST_CARD_NUM, KSF_CUST_CARD_NUM), 'exp' => new clsCartField($this, KSI_CUST_CARD_EXP, KSF_CUST_CARD_EXP) ); $objPayment = $this->SpawnPayment($arFields); $this->objCust = $this->SpawnPerson('cust','buyer'); $this->objCust->Node('payment', $objPayment); // the buyer always has the credit card } return $this->objCust;

   }
   /*-----
     METHOD: clsShopCart->GetDetailObjs()
     FUTURE: rename to GetDataTree()
     HISTORY:

2010-09-12 borrowed (to be moved?) from clsPageCkout 2011-11-27 the version in clsPageCkout has now been commented out 2011-11-29 using SpawnPerson() instead of new clsPerson()

   */
   public function GetDetailObjs() {

if (!$this->hasDetails) { $objAddrShip = $this->AddrShipObj(); $objAddrCard = $this->AddrCardObj(); $objContDest = $this->ContDestObj(); $objContCust = $this->ContCustObj();

$objDataTree = new clsContactRoot($this->Engine()); /* $objDataRef = new clsContactFolder_reference(); $objDataTree->Node('reference data',$objDataRef); $objDataRef->Node('addr.ship',$objAddrShip); $objDataRef->Node('addr.card',$objAddrCard); $objDataRef->Node('cont.dest',$objContDest); $objDataRef->Node('cont.cust',$objContCust);

  • /

$this->objShip = $this->SpawnPerson('ship','recipient'); $this->objCust = $this->PersonCustObj();

$objDataTree->Node('person.ship',$this->objShip); $objDataTree->Node('person.cust',$this->objCust);

$this->objDataTree = $objDataTree;

$objPayment = $this->objCust->Payment();

$this->objShip->Node('contact', $objContDest); // shipping information is always specified

$objContDest->Addr = $objAddrShip; // shipping address

$this->custShipIsCard = $this->DataItem(KSI_SHIP_IS_CARD); $this->custShipToSelf = $this->DataItem(KSI_SHIP_TO_SELF);

if ($this->custShipIsCard) { $objPayment->Node('addr', $objAddrShip); // use shipping address for card } else { $objPayment->Node('addr', $objAddrCard); // use separate address for card }

$this->objShip->Node('name', $objAddrShip->Name());

if ($this->custShipToSelf) { // don't use separate person data; re-use buyer contact info plus shipping address $this->objShip->Node('payment', $objPayment); // the only buyer field the recipient doesn't have $this->objCust = $this->objShip; //$objContDest->Node('addr', $objAddrShip); // shipping address $this->objShip->Node('contact', $objContDest); $objContDest->Node('addr', $objAddrShip); } else { $this->objShip->Node('contact', $objContDest); $this->objCust->Node('contact', $objContCust); $this->objCust->Node('name', $objAddrCard->Name()); } $this->hasDetails = TRUE; } return $this->objDataTree;

   }
   /*----
     ASSUMES: GetDetailObjs() has been called
   */
   public function WhoCust() {

return $this->objCust;

   }
   /*----
     ASSUMES: GetDetailObjs() has been called
   */
   public function WhoShip() {

return $this->objShip;

   }

} class clsShopCartLines extends clsTable {

   const TableName='shop_cart_line';
   public function __construct($iDB) {

parent::__construct($iDB); $this->Name(self::TableName); $this->KeyName('ID'); $this->ClassSng('clsShopCartLine'); $this->seqCart = 0;

   }
   public function Add($iCart, $iCatNum, $iQty) {

$objItems = $this->objDB->Items(); $objItem = $objItems->Get_byCatNum($iCatNum); if (is_null($objItem)) { // TO DO: log error echo 'ERROR: Could not find item for catalog #'.$iCatNum.'
'; } else { $sqlCart = $this->objDB->SafeParam($iCart); $sqlWhere = '(ID_Cart='.$sqlCart.') AND (ID_Item='.$objItem->ID.')'; $objLine = $this->GetData($sqlWhere,'clsShopCartLine'); $objLine->NextRow(); // load the only data row

if (!$objLine->hasRows()) {

		$objLine->ID_Cart=$iCart;

} $objLine->ID_Item = $objItem->ID; $objLine->Qty($iQty); $objLine->Build(); }

   }

} class clsShopCartLine extends clsDataSet {

   public function __construct(clsDatabase $iDB=NULL, $iRes=NULL, array $iRow=NULL) {

parent::__construct($iDB,$iRes,$iRow); // this is necessary so $this->Update() will work // ...but it should be done by whatever code creates the object //$this->Table = $this->objDB->CartLines(); $this->ID = -1;

   }
   public function IsLoaded() {

return ($this->hasRows());

   }
   public function Cart() {

return $this->objDB->Carts()->GetItem($this->ID_Cart);

   }
   public function Item() {

$doLoad = FALSE; if (empty($this->objItem)) { $doLoad = TRUE; } elseif ($this->objItem->ID != $this->ID_Item) { $doLoad = TRUE; } if ($doLoad) { $this->objItem = $this->objDB->Items()->GetItem($this->ID_Item); } return $this->objItem;

   }
   public function Build() {

if ($this->IsLoaded()) { // if ($this->HasRows()) { $sql = 'UPDATE `'.clsShopCartLines::TableName.'` SET' .' Qty='.$this->Qty .' WhenEdited=NOW()' .' WHERE ID='.$this->ID; $this->objDB->Exec($sql); } else { $this->Seq = $this->Cart()->LineCount()+1; $sql = 'INSERT INTO `'.clsShopCartLines::TableName .'` (Seq,ID_Cart,ID_Item,Qty,WhenAdded)' .' VALUES(' .$this->Seq.', ' .$this->ID_Cart.', ' .$this->ID_Item.', ' .$this->Qty.', NOW());'; $this->objDB->Exec($sql); $this->ID = $this->objDB->NewID('cartLine.make'); }

   }
   public function Qty($iQty=NULL) {

if (!is_null($iQty)) { if ($this->Qty != $iQty) { $qtyNew = 0+$iQty; // make sure it's an integer -- prevent injection attack $arrSet['Qty'] = SQLValue($qtyNew); $arrSet['WhenEdited'] = 'NOW()'; $this->Update($arrSet); $this->Qty = $qtyNew; } } return $this->Qty;

   }

/*

   protected function ItemSpecs(array $iSpecs=NULL) {

if (is_null($iSpecs)) { $this->Item(); // make sure $this->objItem is loaded $this->objTitle = $this->objItem->Title(); $this->objItTyp = $this->objItem->ItTyp(); $this->objItOpt = $this->objItem->ItOpt();

$out['tname'] = $this->objTitle->Name; $out['ittyp'] = $this->objItTyp->Name($this->Qty); $out['itopt'] = $this->objItOpt->Descr; return $out; } else { return $iSpecs; }

   }
   public function ItemDesc(array $iSpecs=NULL) {	// plaintext

$sp = $this->ItemSpecs($iSpecs);

$strItOpt = $sp['itopt'];

$out = '"'.$sp['tname'].'" ('.$sp['ittyp']; if (!is_null($strItOpt)) { $out .= ' - '.$strItOpt; } $out .= ')';

return $out;

   }
   public function ItemDesc_ht(array $iSpecs=NULL) {	// as HTML

$sp = $this->ItemSpecs($iSpecs);

$htTitleName = ''.$this->objTitle->LinkName().''; $strItOpt = $sp['itopt'];

$out = $htTitleName.' ('.$sp['ittyp']; if (!is_null($strItOpt)) { $out .= ' - '.$strItOpt; } $out .= ')';

return $out;

   }
   public function ItemDesc_wt(array $iSpecs=NULL) {	// as wikitext

$sp = $this->ItemSpecs($iSpecs);

$wtTitleName = "".$this->objTitle->LinkName_wt().""; $strItOpt = $sp['itopt'];

$out = $wtTitleName.' ('.$sp['ittyp']; if (!is_null($strItOpt)) { $out .= ' - '.$strItOpt; } $out .= ')';

return $out;

   }
  • /
   /*-----
     PURPOSE: Do calculations necessary for rendering the cart line
     USED BY:

* the shopping cart form * the final order display * the conversion from cart to order

   */
   public function RenderCalc(clsShipZone $iZone) {

$arItem = $this->Item()->DescSpecs(); $txtItemDesc = $this->Item()->DescLong($arItem); $htmItemDesc = $this->Item()->DescLong_ht($arItem);

$objItem = $this->Item(); $this->PriceItem = $objItem->PriceSell; $this->Value('CatNum',$objItem->CatNum);

// save for copying to order line object: $this->DescText = $txtItemDesc; $this->DescHtml = $htmItemDesc;

// save so cart can figure totals; $idsZone = $iZone->Abbr(); $this->ShipPkgDest = $objItem->ShipPricePkg($idsZone); $this->ShipItmDest = $objItem->ShipPriceItem($idsZone);

// calculate costs: $this->CostItemQty = $this->Qty * $this->PriceItem; $this->CostShipQty = $this->Qty * $this->ShipItmDest;

   }
   /*
     ACTION: Render the current cart line using static HTML (no form elements; read-only)
     HISTORY:

2011-04-01 adapting this to use clsOrdLine->RenderStatic()

   */
   public function RenderHtml(clsShopCart $iCart) {

$objOLine = $this->Engine()->OrdLines()->SpawnItem(); $this->RenderCalc($iCart->objShipZone); // calculate some needed fields $objOLine->Init_fromCartLine($this); return $objOLine->RenderStatic($iCart->objShipZone); /*/ // calculate display fields: if ($this->Qty) { $this->RenderCalc($iCart->objShipZone);

$strQty = $this->Qty; $htLineCtrl = $strQty;

$mnyPrice = $this->PriceItem; // item price $mnyPerItm = $this->ShipItmDest; // per-item shipping $mnyPerPkg = $this->ShipPkgDest; // per-pkg minimum shipping $mnyPriceQty = $this->CostItemQty; // line total sale $mnyPerItmQty = $this->CostShipQty; // line total per-item shipping $mnyLineTotal = $mnyPriceQty + $mnyPerItmQty; // line total overall (does not include per-pkg minimum)

$strCatNum = $this->CatNum; $strPrice = FormatMoney($mnyPrice); $strPerItm = FormatMoney($mnyPerItm); $strPriceQty = FormatMoney($mnyPriceQty); $strPerItmQty = FormatMoney($mnyPerItmQty); $strLineTotal = FormatMoney($mnyLineTotal);

$strShipPkg = FormatMoney($mnyPerPkg);

$htDesc = $this->DescHtml;

$htDelBtn = ;

$out = <<<__END__

__END__; return $out; } /**/ } /*---- ACTION: Render the current cart line as part of an interactive HTML form */ public function RenderForm(clsShopCart $iCart) { // calculate display fields: if ($this->Qty) { $this->RenderCalc($iCart->objShipZone); //$htLineName = 'cart-line-'.$this->Seq; $htLineName = 'qty-'.$this->CatNum; $strQty = $this->Qty; $htLineCtrl = '<input size=2 align=right name="'.$htLineName.'" value='.$strQty.'>'; $mnyPrice = $this->PriceItem; // item price $mnyPerItm = $this->ShipItmDest; // per-item shipping $mnyPerPkg = $this->ShipPkgDest; // per-pkg minimum shipping $mnyPriceQty = $this->CostItemQty; // line total sale $mnyPerItmQty = $this->CostShipQty; // line total per-item shipping $mnyLineTotal = $mnyPriceQty + $mnyPerItmQty; // line total overall (does not include per-pkg minimum) $strCatNum = $this->CatNum; $strPrice = FormatMoney($mnyPrice); $strPerItm = FormatMoney($mnyPerItm); $strPriceQty = FormatMoney($mnyPriceQty); $strPerItmQty = FormatMoney($mnyPerItmQty); $strLineTotal = FormatMoney($mnyLineTotal); $strShipPkg = FormatMoney($mnyPerPkg); $htDesc = $this->DescHtml; $htDelBtn = '[<a href="?item='.$this->ID_Item.'&action=del" title="remove '.$strCatNum.' from cart">remove</a>] '; $out = <<<__END__ __END__; return $out; /**/ } } public function RenderText(clsShopCart $iCart,$iFmt) { if ($this->Qty) { $this->RenderCalc($iCart->objShipZone); $dlrPrice = $this->PriceItem; // item price $dlrPerItm = $this->ShipItmDest; // per-item shipping $dlrPerPkg = $this->ShipPkgDest; // per-pkg minimum shipping $dlrPriceQty = $this->CostItemQty; // line total sale $dlrPerItmQty = $this->CostShipQty; // line total per-item shipping $dlrLineTotal = $dlrPriceQty + $dlrPerItmQty; // line total overall (does not include per-pkg minimum) $ftCatNum = $this->CatNum; $ftPrice = FormatMoney($dlrPrice); $ftPerItm = FormatMoney($dlrPerItm); $ftQty = $this->Qty; $ftPriceQty = FormatMoney($dlrPriceQty); // price x qty $ftPerItmQty = FormatMoney($dlrPerItmQty); // per-item shipping x qty $ftLineTotal = FormatMoney($dlrLineTotal); $ftShipPkg = FormatMoney($dlrPerPkg); $ftDesc = $this->DescText; $out = "\n".sprintf($iFmt,$ftCatNum,$ftPrice,$ftPerItm,$ftQty,$ftPriceQty,$ftPerItmQty,$ftLineTotal); $out .= "\n - $ftDesc"; return $out; } /* (2010-02-18) actually, this is probably superceded by RenderText() public function RenderEmail($iCart) { /* TO DO: to be written; the following is from Perl: $out .= "$intLine. $lineDescText\n"; $out .= "Catalog #: $lineCatNum\n"; $out .= "Price each : ".AlignDollars($linePrice)."\n"; $out .= "Shipping each : ".AlignDollars($lineShipItemDest)."\n"; $out .= "Min. base shipping: ".AlignDollars($lineShipPkgDest)."\n"; if ($lineQty != 1) { $textOrder .= "##### QTY $strQty #####\n"; } $out .= "\n";
  • /
} } // ShopCart Log class clsShopCartLog extends clsTable { const TableName='shop_cart_event'; public function __construct($iDB) { parent::__construct($iDB); $this->Name(self::TableName); $this->KeyName('ID'); } public function Add($iCart,$iCode,$iDescr,$iUser=NULL) { global $vgUserName; $strUser = is_null($iUser)?$vgUserName:$iUser; if ($iCart->hasField('ID_Sess')) { $idSess = $iCart->ID_Sess; } else { // this shouldn't happen, but we still need to log the event, and ID_Sess is NOT NULL: $idSess = 0; } $edit['ID_Cart'] = $iCart->ID; $edit['WhenDone'] = 'NOW()'; $edit['WhatCode'] = SQLValue($iCode); $edit['WhatDescr'] = SQLValue($iDescr); $edit['ID_Sess'] = $idSess; $edit['VbzUser'] = SQLValue($strUser); $edit['Machine'] = SQLValue($_SERVER["REMOTE_ADDR"]); $this->Insert($edit); } } /* =================== CLASS: clsShopSessions PURPOSE: Handles shopping sessions
  • /
class clsShopSessions extends clsTable { protected $SessKey; const TableName='shop_session'; public function __construct($iDB) { parent::__construct($iDB); $this->Name(self::TableName); $this->KeyName('ID'); $this->ClassSng('clsShopSession'); } private function Create() { //$objSess = new clsShopSession($this->objDB); $objSess = $this->SpawnItem(); $objSess->InitNew(); $objSess->Create(); return $objSess; } public function SetCookie($iSessKey=NULL) { if (!is_null($iSessKey)) { $this->SessKey = $iSessKey; } setcookie(KS_VBZCART_SESSION_KEY,$this->SessKey,0,'/','.'.KS_STORE_DOMAIN); } public function GetCurrent() { $okSession = FALSE; $objClient = NULL; $strSessKey = NULL; if (isset($_COOKIE[KS_VBZCART_SESSION_KEY])) { $strSessKey = $_COOKIE[KS_VBZCART_SESSION_KEY]; } if (!is_null($strSessKey)) { list($ID,$strSessRand) = explode('-',$strSessKey); $objSess = $this->GetItem($ID); $okSession = $objSess->IsValidNow($strSessRand); // do session's creds match browser's creds? } if (!$okSession) { // no current/valid session, so make a new one: // add new record... $objSess = $this->Create(); // generate new session key $strSessKey = $objSess->SessKey(); //setcookie(KS_VBZCART_SESSION_KEY,$strSessKey); $this->SetCookie($strSessKey); } return $objSess; } } /* =================== CLASS: clsShopSession PURPOSE: Represents a single shopping session
  • /
class clsShopSession extends clsDataSet { private $objCart; private $objClient; public function __construct(clsDatabase $iDB=NULL, $iRes=NULL, array $iRow=NULL) { parent::__construct($iDB,$iRes,$iRow); /* if (is_null($this->objDB)) { echo '
';
	    throw new exception('Database not set in clsShopSession.');
	}
*/
	//$this->Table = $this->Engine()->Sessions();
    }
    public function InitNew() {
	$this->Token = RandomString(31);
	$this->ID_Client = NULL;
	$this->ID_Cart = NULL;
	$this->WhenCreated = NULL;	// hasn't been created until written to db
	$this->Client();
    }
    public function Create() {
	$sql =
	  'INSERT INTO `'.clsShopSessions::TableName.'` (ID_Client,ID_Cart,Token,WhenCreated)'.
	  'VALUES('.SQLValue($this->ID_Client).', '.SQLValue($this->ID_Cart).', "'.$this->Token.'", NOW());';
	$this->objDB->Exec($sql);
	$this->ID = $this->objDB->NewID('session.create');
	if (!$this->Client()->isNew) {
	    $this->Client()->Stamp();
	}
    }
    /*-----
      RETURNS: TRUE if the stored session credentials match current reality (browser's credentials)
    */
    public function IsValidNow($iKey) {
	  $ok = ($this->Token == $iKey);
	  if ($ok) {
	      $idClientWas = $this->ID_Client;
	      $objClient = $this->Client();
	      if ($idClientWas != $this->ID_Client) {
		  // not an error, but could indicate a hacking attempt -- so log it, flagged as severe:
		  $this->objDB->LogEvent(
		    'session.valid',
		    'KEY='.$iKey,' OLD-CLIENT='.$idClientWas.' NEW-CLIENT='.$this->ID_Client,
		    'stored session client mismatch','XCRED',FALSE,TRUE);
		  $ok = FALSE;
	      }
	  }
	  return $ok;
    }
    public function SetCart($iID) {
	$this->ID_Cart = $iID;
	$this->Update(array('ID_Cart'=>$iID));
    }
    /*----
      ACTION: Drop the current cart, so that added items will create a new one
      HOW: Tell the cart to lock itself, but don't forget it.
	CartObj() checks the cart to see if it is locked, and gets a new one if so.
      USED BY: "delete cart" user button
    */
    public function DropCart() {
	//$this->ID_Cart = NULL;
	$this->CartObj()->Update(array('WhenVoided'=>'NOW()'));
    }
    public function SessKey() {
	return $this->ID.'-'.$this->Token;
    }
    /*----
      RETURNS: TRUE if the cart is usable within the current context
	If we're displaying checkout stuff, it's okay to use a cart
	  which has already been turned into an order.
	If we're still in the cart-editing phase, then we need to
	  fetch a new cart if the old one has been ordered (or, later,
	  give the user options -- add to existing order, edit existing order,
	  create completely new order...).
    */
    private function IsCartUsable($iCart) {
	if ($this->Engine()->inCkout) {
	    // for voided, still need a new cart; ordered is ok
	    return !$iCart->IsVoided();
	} else {
	    // ordered or voided means we need a new cart
	    return !$iCart->IsLocked();
	}
    }
    /*----
      ACTION: Loads the cart object.
	* If ID_Cart is set, looks up that cart.
	* If that cart has been locked (which currently happens when the cart
	  is converted to an order but might mean other things in the future),
	  discard it and get a new one.
      INPUT: $iCaller is for debugging and is discarded; caller should pass __METHOD__ as the argument.
    */
    public function Cart() {	// DEPRECATED FORM
	return $this->CartObj();
    }
    public function CartObj() {
// if there's a cart for this session, load it; otherwise create a new one but don't save it:
	if (!isset($this->objCart)) {
	    $objCarts = $this->objDB->Carts();
	    if (!is_null($this->ID_Cart)) {
		$objCart = $objCarts->GetItem($this->ID_Cart);
/*
		// KLUGE for testing - should not normally be necessary:
		if ($this->objCart->ID_Sess==0) {
		    $this->objCart->ID_Sess = $this->ID;
		    $this->objCart->Update(array('ID_Sess'=>$this->ID));
		}
*/
		if (!$this->IsCartUsable($objCart)) {
		    $this->ID_Cart = NULL;	// get a new cart if the order is locked
		}
	    }
	    if (is_null($this->ID_Cart)) {
		$objCart = $this->objDB->Carts()->SpawnItem();
		$objCart->InitNew($this->ID);
	    }
	}
	assert('is_object($objCart);');	// we should always have a cart at this point
	$this->objCart = $objCart;
	return $objCart;
    }
    public function Client() {
// if the session's client record matches, then load the client record; otherwise create a new one:
	if (!isset($this->objClient)) {
	    $this->objClient = NULL;
	    $objClients = $this->objDB->Clients();
	    if (!is_null($this->ID_Client)) {
		$this->objClient = $objClients->GetItem($this->ID_Client);
		if (!$this->objClient->IsValidNow()) {
		    $this->objClient = NULL;	// doesn't match current client; need a new one
// TO DO: this should invalidate the session and be logged somewhere.
// It means that a session has jumped to a new browser, which shouldn't happen and might indicate a hacking attempt.
		}
	    }
	    if (is_null($this->objClient)) {
		$this->objClient = $objClients->SpawnItem();
		$this->objClient->InitNew();
		$this->objClient->Build();
		$this->ID_Client = $this->objClient->ID;
	    }
	}
	return $this->objClient;
    }
}
class clsShopClients extends clsTable {
    const TableName='shop_client';

    public function __construct($iDB) {
	parent::__construct($iDB);
	  $this->Name(self::TableName);
	  $this->KeyName('ID');
	  $this->ClassSng('clsShopClient');
    }
}
class clsShopClient extends clsDataSet {
    public function __construct(clsDatabase $iDB=NULL, $iRes=NULL, array $iRow=NULL) {
	parent::__construct($iDB,$iRes,$iRow);
	//$this->Table = $this->objDB->Clients();
    }
    public function InitNew() {
	$this->ID = NULL;
	$this->Address = $_SERVER["REMOTE_ADDR"];
	$this->Browser = $_SERVER["HTTP_USER_AGENT"];
	$this->Domain = gethostbyaddr($this->Address);
	$this->CRC = crc32($this->Address.' '.$this->Browser);
	$this->isNew = TRUE;
    }
    public function IsValidNow() {
	return (($this->Address == $_SERVER["REMOTE_ADDR"]) && ($this->Browser == $_SERVER["HTTP_USER_AGENT"]));
    }
    public function Stamp() {
	$this->Update(array('WhenFinal'=>'NOW()'));
    }
    public function Build() {
    // update existing record, if any, or create new one
	$sql = 'SELECT * FROM '.clsShopClients::TableName.' WHERE CRC="'.$this->CRC.'";';
	$this->Query($sql);
	if ($this->hasRows()) {
	    $this->NextRow();	// get data
	    $this->isNew = FALSE;
	} else {
	    $strDomain = $this->objDB->SafeParam($this->Domain);
	    $strBrowser = $this->objDB->SafeParam($this->Browser);
	    $sql = 'INSERT INTO `'.clsShopClients::TableName.'` (CRC, Address, Domain, Browser, WhenFirst)'
	    .' VALUES("'.$this->CRC.'", "'.$this->Address.'", "'.$strDomain.'", "'.$strBrowser.'", NOW());';
	    $this->objDB->Exec($sql);
	    $this->ID = $this->objDB->NewID('client.make');
	}
    }
}

/* ======================
 ORDER MANAGEMENT CLASSES
 Should probably be in a separate file
 LATER: Split into 3 parts:
  (1) core pieces, like table name
  (2) pieces only used by cart
  (3) (already done - class VbzAdminOrders) pieces only used by admin system
*/
class clsOrders extends clsTable {
    const TableName='core_orders';

    public function __construct($iDB) {
	parent::__construct($iDB);
	  $this->Name(self::TableName);
	  $this->KeyName('ID');
	  $this->ClassSng('clsOrder');
	  $this->ActionKey(KS_URL_PAGE_ORDER);
    }
    /*-----
    | FUNCTION: Create()
    | ACTION: create the order record (fill in minimal fields)
    */
    public function Create() {
	$intSeq = $this->NextOrdSeq();
	$strSeq = sprintf(KS_ORD_NUM_FMT,$intSeq);
	$strNum = KC_ORD_NUM_PFX.$strSeq;
	$arIns = array(
	    'Number'	=> SQLValue($strNum),
	    'SortPfx'	=> SQLValue(KC_ORD_NUM_SORT),
	    'WhenStarted'	=> 'NOW()'
	    );
	$this->Insert($arIns);
	$id = $this->objDB->NewID();
	assert('$id > 0');
	return $id;
    }
    /*-----
    | FUNCTION: NextOrdSeq()
    | ACTION: get the next order sequence number
    */
    private function NextOrdSeq() {
	$objVars = $this->objDB->VarsGlobal();
	$intOrdLast = $objVars->Val('ord_seq_prev');
	$intOrdThis = $intOrdLast+1;
	$objVars->Val('ord_seq_prev',$intOrdThis);
	return $intOrdThis;
    }
    /*-----
    | FUNCTION: Populate()
    | ACTION: fill in the order record with data from the cart
    */
    public function CopyCart($iOrdID, clsShopCart $iCartObj) {
	assert('$iOrdID > 0');

// ** ITEMS IN CART (convert from cart lines to order items)
	//$objTbl = new clsShopCartLines($this->objDB);
	//$objRows = $objTbl->GetData('ID_Cart='.$this->ID);

	$objOrd = $this->GetItem($iOrdID);
	$objOrd->CopyCart($iCartObj);

// should this code be in $objOrd->CopyCart?
	// in session object, set Order ID and clear Cart ID
	// 2011-03-27 wrong. just set Order ID.
	$arUpd = array(
	  'ID_Order'	=> $iOrdID,
	  //'ID_Cart'	=> 'NULL'
	  );
	$iCartObj->Session()->Update($arUpd);
	// log the event
	$this->Engine()->LogEvent(
	  __METHOD__,
	  '|ord ID='.$iOrdID.'|cart ID='.$iCartObj->KeyValue(),
	  'Converted cart to order; SQL='.SQLValue($iCartObj->Session()->sqlExec),
	  'C>O',FALSE,FALSE);

	return $objOrd;	// this is used by the checkout process
    }
}
class clsOrder extends clsVbzRecs {
    /*----
      NOTE: "Code" is now an integer. It used to be a string.
      HISTORY:
	2010-10-08 Moved here from VbzAdminOrder
    */
/*
    public function StartEvent($iWhere,$iCode,$iDescr,$iNotes=NULL) {
	$arEvent = array(
	  //'type'	=> clsEvents::kTypeOrd,
	  'type'	=> $this->Table->ActionKey(),
	  'id'		=> $this->ID,
	  'where'	=> $iWhere,
	  'code'	=> $iCode,
	  'descr'	=> $iDescr
	  );
	if (!is_null($iNotes)) {
	    $arEvent['notes'] = $iNotes;
	}
	$this->idEvent = $this->objDB->Events()->StartEvent($arEvent);
    }
*/
    protected function StartEvent_Simple($iCode,$iDescr,$iWhere) {
	//$this->objDB->OrderLog()->Add($this->ID,$iCode,$iDescr);
	$arEv = array(
	  'descr'	=> $iDescr,
	  'type'	=> $this->Table->ActionKey(),
	  'id'		=> $this->KeyValue(),
	  'where'	=> $iWhere,
	  'code'	=> $iCode);
//	$this->objDB->Events()->StartEvent($arEv);
	$this->StartEvent($arEv);
    }
    /*====
      HISTORY:
	2010-12-05 boilerplate event logging added to VbzAdminSupplier
	2011-03-23 copied from VbzAdminSupplier to VbzAdminOrder
	2011-03-27 moved from VbzAdminOrder to clsOrder
    */
    public 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();
    }
    //====
/*
    protected function FinishEvent(array $iArgs=NULL) {
	$this->objDB->Events()->FinishEvent($iArgs);
    }
*/
    /*----
      HISTORY:
	2010-10-08 Moved here from VbzAdminOrder; supercedes existing protected function
	  which might not have been working properly anyway (no idEvent)
    */
/* 2011-03-27 this is now redundant
    public function FinishEvent(array $iArgs=NULL) {
	$this->objDB->Events()->FinishEvent($this->idEvent,$iArgs);
    }
*/
    /*-----
      TO DO: Explain why this object sometimes doesn't know what the cart is yet
	(presumably happens during the cart->order transfer process)
      USED BY:
	Created for VbzAdmin, where it is sometimes necessary to pass the cart ID (why?)
	Now used by this class for displaying order receipt.
    */
    protected function Cart($iID=NULL) {
	if (is_null($iID)) {
	    $idCart = $this->ID_Cart;
	} else {
	    $idCart = $iID;
	}

	$doLoad = TRUE;
	if (isset($this->objCart)) {
	    if ($this->objCart->ID == $idCart) {
		$doLoad = FALSE;
	    }
	}
	if ($doLoad) {
	    $objCart = $this->objDB->Carts()->GetItem($idCart);
	    $this->objDB->Cart($objCart);
	    $this->objCart = $objCart;
	}
	return $objCart;
    }

    public function Lines() {
	$objTbl = $this->objDB->OrdLines();
	$objRows = $objTbl->GetData('ID_Order='.$this->ID);
	return $objRows;
    }
    public function CopyCart(clsShopCart $iCartObj) {
	$this->CopyCartData($iCartObj);
	$this->CopyCartLines($iCartObj);
    }
    /* =====
      ACTION: Copy over basic cart information (totals, etc.)
      HISTORY:
	2010-10-06 added cart ID to update -- otherwise final order confirmation page can't find cart data
    */
    private function CopyCartData(clsShopCart $iCartObj) {
	$curItemTotal = $iCartObj->DataItem(KSI_ITEM_TOTAL);
	$curShipItem = $iCartObj->DataItem(KSI_PER_ITEM_TOTAL);
	$curShipPkg = $iCartObj->DataItem(KSI_PER_PKG_TOTAL);
	$idCart = $iCartObj->ID;
	$this->ID_Cart;

	$arUpd = array(
	  'ID_Cart'		=> $idCart,
	  'WebTotal_Merch'	=> SQLValue($curItemTotal),
	  'WebTotal_Ship' 	=> SQLValue($curShipItem+$curShipPkg),
	  'WebTotal_Final'	=> SQLValue($curItemTotal+$curShipItem+$curShipPkg)
	  );
	$this->StartEvent_Simple('CDC','Copying data from cart ID='.$idCart,__METHOD__);
	$this->Update($arUpd);	// we're assuming the order record exists at this point
	$this->FinishEvent();
    }
    /*-----
     ACTION: Create order lines from cart lines
    */
    private function CopyCartLines(clsShopCart $iCartObj) {
	$objRows = $iCartObj->GetLines();	// shopping cart lines to convert
	$objTbl = new clsOrderLines($this->objDB);
	$objTbl->Update(array('QtyOrd'=>0),'ID_Order='.$this->ID);	// zero out any existing order lines in case customer edits cart
	if ($objRows->HasRows()) {
	    $this->StartEvent_Simple('CLC','copying cart lines',__METHOD__);
	    $intNew = 0;
	    $intUpd = 0;
	    while ($objRows->NextRow()) {
		$intSeq = $objRows->Seq;
		$idItem = $objRows->ID_Item;
		$intQty = $objRows->Qty;
		$dtWhenAdded = $objRows->WhenAdded;
		$dtWhenEdited = $objRows->WhenEdited;

		$objRows->RenderCalc($iCartObj->objShipZone);

		$arUpd = array(
		  'CatNum'	=> SQLValue($objRows->CatNum),
		  'Descr'	=> SQLValue($objRows->Item()->DescLong()),
		  'QtyOrd'	=> $intQty,
		  'Price'	=> SQLValue($objRows->PriceItem),
		  'ShipPkg'	=> SQLValue($objRows->ShipPkgDest),
		  'ShipItm'	=> SQLValue($objRows->ShipItmDest),
		  'isShipEst'	=> 'FALSE'
		  );

		// has this item already been transcribed?
		$sqlFilt = '(ID_Order='.$this->ID.') AND (ID_Item='.$idItem.')';
		$objOrdItems = $objTbl->GetData($sqlFilt);
		if ($objOrdItems->RowCount() > 0)  {
		    // already transcribed -- update existing record
		    $objTbl->Update($arUpd,$sqlFilt);
		    $intUpd++;
		} else {
		    // not already transcribed -- insert new record
		    $arIns = $arUpd + array(
		      'ID_Order' 	=> $this->ID,
		      'Seq'		=> $intSeq,
		      'ID_Item'		=> $idItem
		      );
		    $objTbl->Insert($arIns);
		    $intNew++;
		}
	    }
	    $strLines = $intNew.' order line'.Pluralize($intNew).' created';
	    if ($intUpd > 0) {
		$strLines .= ' and '.$intUpd.' order line'.Pluralize($intUpd).' updated';
	    }
	    $strLines .= ' from cart lines';
	    $this->FinishEvent(array('descr'=>$strLines));
	} else {
	    $this->StartEvent_Simple('CL0','No cart lines found at order creation time',__METHOD__);
	    $out = '<b>There has been an error</b>: your cart contents seem to be missing.';
	    // TO DO: send email alerting webmaster; print more helpful message.
	}
    }
    /*----
      PURPOSE: nomenclatural purity. I could have just added the display code to RenderReceipt(),
	but it seemed like a good idea to have a function that just generates the output without
	displaying it. It also seemed like a good idea not to add the code to the calling function,
	because that's untidy, So here we are, with a function shorter than its comments.
      NOTE: admin functions will generally want to handle the display themselves, so it is good practice
	to be able to retrieve the output to be displayed without displaying it.
	It may be redundant to have ShowReceipt() at all, however.
    */
    public function ShowReceipt() {
	$out = $this->RenderReceipt();
	echo $out;
    }
    /*----
      ACTION: Render the receipt in HTML
	Shows the order as generated from *cart* data, not what's in the order record.
	...except for the order number.
    */
    public function RenderReceipt() {
	$out = NULL;

// 2010-10-17 This should have been loaded earlier, but I don't have time to track it down now.
//	The problem is that it forgets the cart ID otherwise.
	$this->Reload();

	// get core objects, for code clarity/portability
	$objOrd = $this;
	$objCart = $this->Cart();
	$idOrder = $this->KeyValue();
	$idCart = $this->Value('ID_Cart');

	if (!is_object($objCart)) {
	    throw new exception("Receipt could not get cart object: Cart ID=[$idCart]");
	}
	if (($objOrd->IsNew()) || ($objCart->IsNew())) {
	    throw new exception("Receipt has missing object: Order ID=[$idOrder] Cart ID=[$idCart]");
	}

	$objCart->GetDetailObjs();
	$objPay = $objCart->PersonCustObj()->Payment();
	$objAddrCard = $objCart->AddrCardObj();
	// the next line is a kluge which only works as long as payment is always ccard
	// it's also not clear why GetDetailObjs() isn't loading it properly
	$objPay->Node('addr', $objAddrCard);

	$arVars = array(
	  'ord.num'	=> $objOrd->Number,
	  'timestamp'	=> date(KF_RCPT_TIMESTAMP),
	  'cart.id'	=> $objCart->ID,
	  'cart.detail'	=> $objCart->RenderConfirm(),
	  'ship.name'	=> $objCart->AddrShipObj()->Name()->Value(),
	  'ship.addr'	=> $objCart->AddrShipObj()->AsText("\n<br>"),
	  'pay.name'	=> $objPay->Addr()->Name()->Value(),
	  'pay.spec'	=> $objPay->SafeDisplay(),
	  'email.short'	=> 'orders-'.date('Y').'@vbz.net'
	  );
	$objStrTplt = new clsStringTemplate_array(NULL,NULL,$arVars);
	$objStrTplt->MarkedValue(KHT_RCPT_TPLT);
	$out = "<!-- ORDER ID: [$idOrder] / CART ID from order: [$idCart] -->";
	$out .= $objStrTplt->Replace();
	return $out;

//	return $objCart->RenderReceipt();
    }
    /*----
      RETURNS: Email address to use for this order (includes order number in address, as a way of tracking
	spam and possibly later as a way of automatically filing order-related correspondence)
    */
    public function EmailAddr() {
	return 'order-'.$this->Number.'@vbz.net';
    }
    /*----
      RETURNS: Order receipt (from cart data) in text-only format, suitable for emailing
      HISTORY:
	2010-09-13 writing started
    */
    public function RenderReceipt_Text() {
	$out = $this->Cart()->RenderOrder_Text();
	return $out;
    }
    /*----
      RETURNS: array of values needed to plug into email templates
      USED BY:
	cart class, to generate confirmation email
	order admin class, to display/generate confirmation email
    */
    public function TemplateVars() {
//	global $ksTextEmail;
	$this->Reload(); // 2010-10-28 kluge attempt - see also RenderReceipt()

	$objCart = $this->Cart();
	$objCust = $objCart->PersonCustObj();
	$objPay = $objCust->Payment();

	assert('is_object($objCart)');
	assert('is_object($objCart)');
	assert('is_object($objPay)');

	$objCardAddr = $objCart->AddrCardObj();
	$objCustCont = $objCart->ContCustObj();

	assert('is_object($objCardAddr)');
	assert('is_object($objCustCont)');

	$arVars = array(
// THIS IS A KLUGE -- should be something like objCart->CustObj->Name
	  'cust-name' => $objCardAddr->Name()->Value(),
	  'cust-email' => $objCustCont->Email()->Value(),
	  'orders-email' => $this->EmailAddr(),
	  'ord-num' => $this->Number
	  );
	return $arVars;
    }
    /*----
      RETURNS: array of values needed for sending confirmation email
      USED BY:
	cart class, to generate confirmation email
	order admin class, to display/generate confirmation email
    */
    public function EmailParams(array $iarVars, $iSubj=NULL, $iMsgPre=NULL) {
	$arVars = $iarVars;
	$objStrTplt = new clsStringTemplate_array(KS_TPLT_OPEN,KS_TPLT_SHUT,$arVars);

	$txtSubj = $iSubj;
	if (empty($txtSubj)) {
	    //$objStrTplt->MarkedValue(KS_TEXT_EMAIL_SUBJECT);
	    $txtSubj = $objStrTplt->Replace(KS_TPLT_EMAIL_SUBJECT);
	}
	$arOut['subj'] = $txtSubj;

	$txtMsgPre = $iMsgPre;
	if (empty($txtMsgPre)) {
	    $txtMsgPre = $objStrTplt->Replace(KS_TPLT_EMAIL_MSG_TOP);
	}
	$arOut['msg.pre'] = $txtMsgPre;

	$arVars['subject'] = $txtSubj;
	$objStrTplt->List = $arVars;

	//$objStrTplt->MarkedValue(KS_TEXT_EMAIL_ADDR_SELF);
	$txtAddr_Self = $objStrTplt->Replace(KS_TPLT_EMAIL_ADDR_SELF);
	$arOut['addr.self'] = $txtAddr_Self;

	//$objStrTplt->MarkedValue(KS_TEXT_EMAIL_ADDR_CUST);
	$txtAddr_Cust = $objStrTplt->Replace(KS_TPLT_EMAIL_ADDR_CUST);
	$arOut['addr.cust'] = $txtAddr_Cust;

	// Calculate text of email to send:
	$txtEmailBody = $txtMsgPre."\n".$this->RenderReceipt_Text();
	$arOut['msg.body'] = $txtEmailBody;

	return $arOut;
    }
    /*----
      ACTION: Email an order confirmation (or show what would be sent)
      RETURNS: contents of the email
      HISTORY:
	2010-10-08 moved from VbzAdminOrder to clsOrder
      INPUT:
	iReally: if FALSE, does not actually send the email, but only renders (and returns) it
	iParams: array of specifications for how to send the email
	  iParams['to-self']: if TRUE, send a copy to the store admin's address
	  iParams['to-cust']: if TRUE, send a copy to the customer's address
	  iParams['addr-self']: email address for store admin
	  iParams['addr-cust']: email address for customer
	  iParams['subject']: subject of the email
	  iParams['message']: body of the email
    */
    //public function EmailConfirm($iReally, $iSendToSelf, $iSendToCust, $iAddrSelf, $iAddrCust, $iSubject, $iMessage) {
    public function EmailConfirm($iReally, array $iParams, clsLogger $iLog) {
	$iSendToSelf = $iParams['to-self'];
	$iSendToCust = $iParams['to-cust'];
	$iAddrSelf = $iParams['addr-self'];
	$iAddrCust = $iParams['addr-cust'];
	$iSubject = $iParams['subject'];
	$iMessage = $iParams['message'];
	$htEmailAddr_Self = htmlspecialchars($iAddrSelf);
	$htEmailAddr_Cust = htmlspecialchars($iAddrCust);

	$htEmailBody = htmlspecialchars($iMessage);
	$txtSubj = $iSubject;
	$htSubj = htmlspecialchars($txtSubj);
	$intCustCopy = $iSendToCust?'1':'0';
	$txtCustCopy = $iSendToCust?'YES':'no';
	$txtSelfCopy = $iSendToSelf?'YES':'no';
	$doSend = $iReally;

	$out = NULL;
	$out .= '<table>';
	$out .= "<tr><td align=right><b>Store's email address</b>:</td><td>$htEmailAddr_Self</td></tr>";
	$out .= "<tr><td align=right><b>Customer's email address</b>:</td><td>$htEmailAddr_Cust</td></tr>";
	$out .= "<tr><td align=right><b>Subject</b>:</td><td>$htSubj</td></tr>";
	$out .= "<tr><td align=right><b>Copying to customer?</td><td>$txtCustCopy</td></tr>";
	$out .= '</table>';
      
	$out .= '<pre>'.$htEmailBody.'
';

if (!$doSend) { $out .= '<form method=post>'; $out .= '<input type=hidden name=email-body value="'.$htEmailBody.'">'; $out .= '<input type=hidden name=send-to-cust value="'.$intCustCopy.'">'; $out .= '<input type=hidden name=addr-self value="'.$htEmailAddr_Self.'">'; $out .= '<input type=hidden name=addr-cust value="'.$htEmailAddr_Cust.'">'; $out .= '<input type=hidden name=subject value="'.$htSubj.'">'; $out .= '<input type=submit name=btnSend value="Send email">'; $out .= '</form>'; }

if ($iReally) { // if we're actually sending, then actually send the email and log it:

// log attempt to send email (EM: email/manual) $arEv = array( 'descr' => 'manual order email (self:'.$txtSelfCopy.' cust:'.$txtCustCopy.') Subject: '.$txtSubj, 'code' => 'OEM', 'where' => __METHOD__ ); $iLog->StartEvent($arEv);

if ($iSendToCust) { // if being sent to customer. record the email in the messages table } $okSelf = TRUE; if ($iSendToSelf) { // send our copy of the email $okSelf = mail($iAddrSelf,$txtSubj.' (store copy)',$iMessage,"From: $iAddrCust"); } $okCust = TRUE; if ($iSendToCust) { // log the message we're trying to send global $vgUserName; $this->LogEmail(NULL,$vgUserName,'customer',$txtSubj,$iMessage);

// send the message to the customer $okCust = mail($iAddrCust,$txtSubj,$iMessage,"From: $iAddrSelf"); }

// log event completion if ($okSelf && $okCust) { $arEv = NULL; } else { $arEv = array( 'descrfin' => "self ok:$okSelf | cust ok:$okCust", 'error' => TRUE, 'severe' => TRUE); } $this->FinishEvent($arEv); } return $out;

   }
   public function LogEmail($iPackage,$iTxtFrom,$iTxtTo,$iSubject,$iMessage) {

$this->LogMessage( $iPackage, KSI_ORD_MSG_EMAIL, $iTxtFrom, $iTxtTo, $iSubject, $iMessage);

   }
   public function LogMessage($iPackage,$iMethod,$iTxtFrom,$iTxtTo,$iSubject,$iMessage) {

$this->objDB->OrdMsgs()->Add( $this->ID, $iPackage, $iMethod, $iTxtFrom, $iTxtTo, $iSubject, $iMessage);

   }

} class clsOrderLines extends clsTable {

   const TableName='ord_lines';
   public function __construct($iDB) {

parent::__construct($iDB); $this->Name(self::TableName); $this->KeyName('ID'); $this->ClassSng('clsOrderLine');

   }

} class clsOrderLine extends clsDataSet {

   public function Init_fromCartLine(clsShopCartLine $iLine) {

// some fields get copied over directly $arNames = array( 'Seq' => 'Seq', 'ID_Item' => 'ID_Item', 'Qty' => 'QtyOrd', 'CatNum' => 'CatNum', 'PriceItem' => 'Price', 'ShipPkgDest' => 'ShipPkg', 'ShipItmDest' => 'ShipItm', 'DescText' => 'Descr', //'DescHtml' => 'DescrHTML' // we may eventually add this field ); foreach($arNames as $srce => $dest) { $val = $iLine->Value($srce); $this->Value($dest,$val); }

   }
   /*----
     HISTORY:

2011-03-23 created for AdminPage()

   */
   protected $objItem, $idItem;
   public function ItemObj() {

$doLoad = TRUE; $id = $this->Value('ID_Item'); if (isset($this->idItem)) { if ($this->idItem == $id) { $doLoad = FALSE; } } if ($doLoad) { $this->objItem = $this->Engine()->Items($id); $this->idItem = $id; } return $this->objItem;

   }
   /*----
     RETURNS: selling price

if order line has no price, falls back to catalog item

     HISTORY:

2011-03-23 created for "charge for package" process

   */
   public function PriceSell() {

$prc = $this->Value('Price'); if (is_null($prc)) { $prc = $this->ItemObj()->PriceSell(); } return $prc;

   }
   /*----
     RETURNS: shipping per-package price

if order line has no per-package price, falls back to catalog item

     HISTORY:

2011-03-23 created for "charge for package" process

   */
   public function ShPerPkg() {

$prc = $this->Value('ShipPkg'); if (is_null($prc)) { $prc = $this->ItemObj()->ShPerPkg(); } return $prc;

   }
   /*----
     RETURNS: shipping per-item price -- defaults to catalog item's data

unless specified in package line

     HISTORY:

2011-03-23 created for "charge for package" process

   */
   public function ShPerItm() {

$prc = $this->Value('ShipItm'); if (is_null($prc)) { $prc = $this->ItemObj()->ShPerItm(); } return $prc;

   }
   /*----
     RETURNS: array of calculated values for this order line

array[sh-pkg]: shipping charge per package array[sh-itm.qty]: shipping charge per item, adjusted for quantity ordered array[cost-sell.qty]: selling cost, adjusted for quantity ordered

     USED BY: so far, only admin functions (shopping functions use Cart objects, not Order)
   */
   public function FigureStats() {

$qty = $this->Value('QtyOrd'); if ($qty != 0) { $prcShPkg = $this->ShPerPkg(); } else { // none of this item in package, so don't require this minimum $prcShPkg = 0; } $arOut['sh-pkg'] = $prcShPkg; $arOut['sh-itm.qty'] = $this->ShPerItm() * $qty; $arOut['cost-sell.qty'] = $this->PriceSell() * $qty; return $arOut;

   }
   /*----
     ACTION: Figures totals for the current rowset
     USED BY: so far, only admin functions (shopping functions use Cart objects, not Order)
     RETURNS: array in same format as FigureStats(), except with ".qty" removed from index names
   */
   public function FigureTotals() {

$arSum = NULL; while ($this->NextRow()) { $ar = $this->FigureStats();

$prcShItmSum = nzArray($arSum,'sh-itm',0); $prcShPkgMax = nzArray($arSum,'sh-pkg',0); $prcSaleSum = nzArray($arSum,'cost-sell',0);

$prcShItmThis = $ar['sh-itm.qty']; $prcShPkgThis = $ar['sh-pkg']; $prcSaleThis = $ar['cost-sell.qty'];

$arSum['sh-itm'] = $prcShItmSum + $prcShItmThis; $arSum['cost-sell'] = $prcSaleSum + $prcSaleThis; if ($prcShPkgMax < $prcShPkgThis) { $prcShPkgMax = $prcShPkgThis; } $arSum['sh-pkg'] = $prcShPkgMax; } return $arSum;

   }
   /*----
     ACTION: Render the current order line using static HTML (no form elements; read-only)
     HISTORY:

2011-04-01 adapted from clsShopCartLine::RenderHtml() to clsOrderLine::RenderStatic()

   */
   public function RenderStatic(clsShipZone $iZone) {

// calculate display fields: $qty = $this->Value('QtyOrd'); if ($qty) { //$this->RenderCalc($iZone);

$htLineCtrl = $qty;

$mnyPrice = $this->Value('Price'); // item price $mnyPerItm = $this->Value('ShipItm'); // per-item shipping $mnyPerPkg = $this->Value('ShipPkg'); // per-pkg minimum shipping $intQty = $this->Value('QtyOrd'); $mnyPriceQty = $mnyPrice * $intQty; // line total sale $mnyPerItmQty = $mnyPerItm * $intQty; // line total per-item shipping $mnyLineTotal = $mnyPriceQty + $mnyPerItmQty; // line total overall (does not include per-pkg minimum)

$strCatNum = $this->Value('CatNum'); $strPrice = FormatMoney($mnyPrice); $strPerItm = FormatMoney($mnyPerItm); $strPriceQty = FormatMoney($mnyPriceQty); $strPerItmQty = FormatMoney($mnyPerItmQty); $strLineTotal = FormatMoney($mnyLineTotal);

$strShipPkg = FormatMoney($mnyPerPkg);

$htDesc = $this->Value('Descr'); // was 'DescHtml', but that field doesn't exist here

$htDelBtn = ;

$out = <<<__END__

__END__; return $out; } } } /*---------- CLASS PAIR: order messages (table ord_msg)
  • /
class clsOrderMsgs extends clsTable { public function __construct($iDB) { parent::__construct($iDB); $this->Name('ord_msg'); $this->KeyName('ID'); $this->ClassSng('clsOrderMsg'); } /*---- ACTION: Adds a message to the order */ public function Add($iOrder,$iPackage,$iMethod,$iTxtFrom,$iTxtTo,$iSubject,$iMessage) { global $vgUserName; $arIns = array( 'ID_Ord' => $iOrder, 'ID_Pkg' => SQLValue($iPackage), // might be NULL 'ID_Media' => SQLValue($iMethod), 'TxtFrom' => SQLValue($iTxtFrom), 'TxtTo' => SQLValue($iTxtTo), 'TxtRe' => SQLValue($iSubject), 'doRelay' => 'FALSE', // 2010-09-23 this field needs to be re-thought 'WhenCreated' => 'NOW()', // later: add this as an optional argument, if needed 'WhenEntered' => 'NOW()', 'WhenRelayed' => 'NULL', 'Message' => SQLValue($iMessage) ); $this->Insert($arIns); } } class clsOrderMsg extends clsDataSet { } /* ======= CREDIT CARD UTILITY CLASS
  • /
class clsCustCards extends clsTable { public function __construct($iDB) { parent::__construct($iDB); $this->Name('cust_cards'); $this->KeyName('ID'); } // STATIC section // public static function CardTypeChar($iNum) { $chDigit = substr($iNum,0,1); $arDigits = array( '3' => 'A', '4' => 'V', '5' => 'M', '6' => 'D' ); if (isset($arDigits[$chDigit])) { $chOut = $arDigits[$chDigit]; } else { $chOut = '?'.$chDigit; } return $chOut; } public static function CardTypeName($iNum) { $chDigit = substr($iNum,0,1); $arDigits = array( '3' => 'Amex', '4' => 'Visa', '5' => 'MasterCard', '6' => 'Discover' ); if (isset($arDigits[$chDigit])) { $out = $arDigits[$chDigit]; } else { $out = '?'.$chDigit; } return $out; } public static function SafeDescr_Short($iNum,$iExp) { //$dtExp = strtotime($this->CardExp); $dtExp = self::ExpDate($iExp); if (is_null($dtExp)) { $strDate = '?/?'; } else { $strDate = $dtExp->format('n/y'); } $out = self::CardTypeChar($iNum).'-'.substr($iNum,-4).'x'.$strDate; return $out; } public static function SafeDescr_Long($iNum,$iExp) { $dtExp = self::ExpDate($iExp); if (is_null($dtExp)) { $strDate = '?/?'; } else { $strDate = $dtExp->format('F').' of '.$dtExp->format('Y').' ('.$dtExp->format('n/y').')'; } $out = self::CardTypeName($iNum).' # ends with -'.substr($iNum,-4).' expires in '.$strDate; return $out; } public static function Searchable($iRaw) { $xts = new xtString(strtolower($iRaw),TRUE); $xts->KeepOnly('0-9'); // keep only numerics return $xts->Value; } /*----- 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 static function ExpDate($iRaw,$iMaxFuture=50) { $strExp = $iRaw; // -- split into month/year or month/day/year $arExp = preg_split('/[\/.\- ]/',$strExp); $intParts = count($arExp); switch ($intParts) { case 1: // for now, we're going to assume MMYY[YY] // TO DO: if people start typing in M with no leading zero, will have to check for even/odd # of chars $intMo = substr($strExp,0,2); $intYr = substr($strExp,2); break; case 2: // month/year $intMo = $arExp[0]; $intYr = $arExp[1]; break; case 3: // month/day/year or year/month/day if (strlen($arExp[0]) > 3) { $intYr = $arExp[0]; $intMo = $arExp[1]; $intDy = $arExp[2]; } else { $intMo = $arExp[0]; $intDy = $arExp[1]; $intYr = $arExp[2]; } break; default: // unknown format, can't do anything } // check for validity: $ok = FALSE; if (isset($intYr)) { if ($intYr > 0) { if (($intMo > 0) && ($intMo < 13)) { $ok = TRUE; } } } if ($ok) { if ($intYr < 100) { // if year has no century, give it one $intYrNowPart = strftime('%y'); $intCent = (int)substr(strftime('%Y'),0,2); if ($intYr < $intYrNowPart) { $intCent++; } $intYr += ($intCent*100); $intYrNowFull = (int)strftime('%Y'); if ($intYr - $intYrNowFull > $iMaxFuture) { $intYr -= 100; } } if (!isset($intDy)) { $intDy = cal_days_in_month(CAL_GREGORIAN, $intMo, $intYr); // set to last day of month } $dtOut = $datetime = new DateTime(); $dtOut->setDate($intYr, $intMo, $intDy); return $dtOut; } else { return NULL; // if no year, then could not parse format } } public static function ExpDateSQL($iRaw) { $dt = self::ExpDate($iRaw); if (is_object($dt)) { return '"'.$dt->format('Y-m-d').'"'; } else { return 'NULL'; } } } /* =============== UTILITY FUNCTIONS
  • /
function RandomString($iLen) { $out = ; for ($i = 0; $i<$iLen; $i++) { $n = mt_rand(0,61); $out .= CharHash($n); } return $out; } function CharHash($iIndex) { if ($iIndex<10) { return $iIndex; } elseif ($iIndex<36) { return chr($iIndex-10+ord('A')); } else { return chr($iIndex-36+ord('a')); } } // this can later be adapted to be currency-neutral // for now, it just does dollars function FormatMoney($iAmount,$iPrefix=,$iPlus=) { if ($iAmount < 0) { $str = '-'.$iPrefix.sprintf( '%0.2f',-$iAmount); } else { $str = $iPlus.$iPrefix.sprintf( '%0.2f',$iAmount); } return $str; } /* HISTORY: 2011-08-03 added round() function to prevent round-down error
  • /
function AddMoney($iMoney1,$iMoney2) { $intMoney1 = (int)round($iMoney1 * 100); $intMoney2 = (int)round($iMoney2 * 100); $intSum = $intMoney1 + $intMoney2; return $intSum/100; } /* HISTORY: 2011-08-03 added round() function to prevent round-down error
  • /
function IncMoney(&$iMoney,$iMoneyAdd) { $intBase = (int)round(($iMoney * 100)); $intAdd = (int)round(($iMoneyAdd * 100)); $intSum = $intBase + $intAdd; $iMoney = $intSum/100; }</php>
cat #descriptionprice
each
per-item
s/h ea.
qty.purchase
line total
per-item
s/h line total
totalspkg s/h
min.
$htDelAlltotals:totals:
$strTotalMerch $strItemsShip $strTotalItems
$strShipDesc $strShipPkg
$strTotalDesc $strOrdTotal
$htZoneCombo
$htDelBtn$strCatNum $htDesc $strPrice $strPerItm $htLineCtrl $strPriceQty $strPerItmQty $strLineTotal $strShipPkg
$htDelBtn$strCatNum $htDesc $strPrice $strPerItm $htLineCtrl $strPriceQty $strPerItmQty $strLineTotal $strShipPkg
$htDelBtn$strCatNum $htDesc $strPrice $strPerItm $htLineCtrl $strPriceQty $strPerItmQty $strLineTotal $strShipPkg