VbzCart/docs/archive/code/files/shop.php

From Woozle Writes Code
< VbzCart‎ | docs‎ | archive‎ | code‎ | files
Jump to navigation Jump to search

About

  • Purpose: classes needed when dealing with customer data (shopping cart etc.)
  • History:
    • 2011-12-18 Saving current code just before making some API changes
    • 2013-02-20 About to rework part of the checkout -- combining shipping and payment pages; mostly works as-is, but iffy.
  • Alternatives:
    • /2011-12-18 - got too ugly and complicated and unreliable

Code

<php> <?php /*

 PURPOSE: vbz 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 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');

if (!defined('LIBMGR')) {

   require(KFP_LIB.'/libmgr.php');

}

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::AddClass('clsVbzPage', 'vbz.store');

clsLibMgr::Add('vbz.cart', KFP_LIB_VBZ.'/cart.php',__FILE__,__LINE__);

 clsLibMgr::AddClass('clsPageCart', 'vbz.cart');
 clsLibMgr::AddClass('clsShopCarts','vbz.cart');

clsLibMgr::Add('vbz.order', KFP_LIB_VBZ.'/orders.php',__FILE__,__LINE__);

 clsLibMgr::AddClass('clsOrders', 'vbz.order');

clsLibMgr::Load('strings',__FILE__,__LINE__); clsLibMgr::Load('string.tplt',__FILE__,__LINE__); clsLibMgr::Load('tree',__FILE__,__LINE__); clsLibMgr::Load('vbz.store',__FILE__,__LINE__); // clsLibMgr::Load('vbz.cart',__FILE__,__LINE__);

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_CONT','cont'); // contact page -- shipping and payment 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
  • /

class clsVbzData_Shop extends clsVbzData {

   public function Sessions($id=NULL) {

return $this->Make('clsSessions_StoreUI',$id);

   }
   public function Clients($id=NULL) {

return $this->Make('clsShopClients',$id);

   }
   public function Carts($id=NULL) {

return $this->Make('clsShopCarts',$id);

   }
   public function CartLines($id=NULL) {

return $this->Make('clsShopCartLines',$id);

   }
   public function CartLog() {

return $this->Make('clsShopCartLog');

   }
   public function Orders($id=NULL) {

return $this->Make('clsOrders',$id);

   }
   public function OrdLines($id=NULL) {

return $this->Make('clsOrderLines',$id);

   }

/*

   public function OrderLog() {

return $this->Make('clsOrderLog');

   }
  • /
   public function OrdMsgs($id=NULL) {

return $this->Make('clsOrderMsgs',$id);

   }

/*

   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: clsShipZone
 PURPOSE: shipping zone functions
 USAGE: Customize the isDomestic() function if you're shipping from somewhere other than the US
 RULES:
   * If a country's code isn't found in arDesc, it defaults to International
     ...there's got to be a better way to do this...
  • /

class clsShipZone {

   static private $arDesc = array(
     'CA' => 'Canada',
     'US' => 'United States',
     'INT' => 'International',
     );
   // per-item adjustment factors
   static private $arItmFactors = array(

'US' => 1.0, 'CA' => 2.0, 'INT' => 4.0,

     );
   // per-package adjustment factors
   static private $arPkgFactors = array(	// there's got to be a better way to do this...

'US' => 1.0, 'CA' => 2.0, 'INT' => 4.0,

     );
   static private $arCountryCodes = array(

'united states' => 'US', 'canada' => 'CA', 'australia' => 'AU',

     );
   private $strAbbr;
   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 Set_fromName($iName) {

$strLC = strtolower($iName); if (array_key_exists($strLC,self::$arCountryCodes)) { $this->strAbbr = self::$arCountryCodes[$strLC]; } else { echo 'Country ['.$iName.'] not found in list.'; throw new exception('Internal error: unknown country requested.'); }

   }
   public function Text() {	// should be Name()

return self::$arDesc[$this->Abbr()];

   }
   
   public function hasState() {

switch ($this->Abbr()) { case 'AU': return TRUE; break; case 'CA': return TRUE; break; case 'US': return TRUE; break; default: return FALSE; break; }

   }
   public function StateLabel() {

switch ($this->Abbr()) { case 'AU': return 'State/Territory'; break; case 'CA': return 'Province'; break; case 'US': return 'State'; 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() {

$strZoneCode = $this->Abbr(); $out = '<select name="ship-zone">'; foreach (self::$arDesc 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;

   }
   /*----
     RETURNS: per-item price factor for the current shipping zone
   */
   protected function PerItemFactor() {

echo 'CODE=['.$this->Abbr().'] ITEM FACTOR=['.self::$arItmFactors[$this->Abbr()].']
'; return self::$arItmFactors[$this->Abbr()];

   }
   /*----
     RETURNS: per-package price factor for the current shipping zone
   */
   protected function PerPkgFactor() {

return self::$arPkgFactors[$this->Abbr()];

   }
   /*----
     INPUT: base per-item shipping price
     RETURNS: calculated price for the current shipping zone
   */
   public function CalcPerItem($iBase) {

return $iBase * $this->PerItemFactor();

   }
   /*----
     INPUT: base per-package shipping price
     RETURNS: calculated price for the current shipping zone
   */
   public function CalcPerPkg($iBase) {

return $iBase * $this->PerPkgFactor();

   }

}

// 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;
    }
    /*----
      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 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');
	}
    }
}



/* ===============
 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>