Code
<php><?php
/*
 FILE: store.php
 PURPOSE: VbzCart page-rendering classes
 HISTORY:
   2012-05-08 split off from store.php
if (!defined('LIBMGR')) {
   require(KFP_LIB.'/libmgr.php');
}
clsLibMgr::Add('vbz.db',	KFP_LIB_VBZ.'/store.php',__FILE__,__LINE__);
 clsLibMgr::AddClass('clsVbzTable','vbz.db');
clsLibMgr::Add('rtext.html',	KFP_LIB.'/rtext-html.php',__FILE__,__LINE__);
 clsLibMgr::AddClass('clsRTDoc_HTML','rtext.html');
/*====
 NOTES:
   2011-02-02 This could use some rewriting. A lot of it is kind of pointless,
     and other parts duplicate RichText objects.
class clsPageOutput_WHO_USES_THIS {
   public $out;
   private $isOdd;
   public $inTbl;
   private $fpTools;
   private $fsLogo;
   static public function SectionHeader($iTitle) {
	return '
'.$iTitle.'
';
   }
   function __construct($ifpTools,$iLogoRel) {
	$this->out = ;
	$this->isOdd = false;
	$this->inTbl = 0;
	$this->fpTools = $ifpTools;
	$this->fsLogo = $this->ImageSpec($iLogoRel);
   }
/*
   function Clear() {
     $this->out = ;
   }
   function AddText($iText) {
     $this->out .= $iText;
   }
   function SectionHdr($iTitle) {	// this is specifically duplicated (where?)
      $this->out .= '
'.$iTitle.'
';
     return $this->out;
   }
   // eventually there needs to be a non-MediaWiki clsRichText, and these can go there
/*
   public function TableOpen($iAttr=NULL) {
   }
   public function TableShut() {
   }
   public function TblRowOpen($iAttr=NULL,$iHdr=FALSE) {
   }
   public function TblRowShut() {
   }
   public function TblCell($iText,$iAttr=NULL) {
   }
/* this duplicates RichText
 function StartTable($iTitle) {
   if ($iTitle) {
     $this->SectionHdr($iTitle);
      $this->out .= '
';
      $this->inTbl++;
    }
  }
  function RowStart($iClass=
) {
    if ($iClass) {
      $this->out .= '';
    } else {
      $this->out .= '';
    }
  }
  function RowStop() {
    $this->out .= '';
    $this->isOdd = !$this->isOdd;
  }
  function ColAdd($iText) {
    if ($this->isOdd) {
      $cellOpen = '';
  }
  function EndTable() {
    if ($this->inTbl) {
      $this->out .= '
|
|
';
   } else {
      $cellOpen = ' | ';
   }
 
    $this->out .= $cellOpen.$iText.' | 
';
     $this->inTbl--;
   }
   return $this->out;
 }
 function ShowImgUnavail() {
    $this->out .= '
<tbody></tbody>
<tbody></tbody>No Images Available for this item :-( |  
  | 
';
   return $this->out;
 }
}
/* ===================
 CLASS: clsVbzSkin
 PURPOSE: Abstract skin class
   Specifies all the bits that we'll want to have, but doesn't define them
   Provides some basic support services, but no actual content (text or formatting)
abstract class clsVbzSkin {
   protected $objDoc;
   public function __construct() {
	$this->objDoc = new clsRTDoc_HTML();
   }
   public function Doc() {
	return $this->objDoc;
   }
   /*-----
     USAGE: Normal main entry point -- should be called from index.php
   */
   public function DoPage() {
	try {
	    $this->DoPreContent();
	    $this->DoContent();
	    $this->DoPostContent();
	} catch(exception $e) {
	    $this->DoEmailException($e);
	}
   }
   /*----
     HISTORY:
	2011-03-31 added Page and Cookie to list of reported variables
   */
   protected function DoEmailException(exception $e) {
	$msg = $e->getMessage();
	$arErr = array(
	  'descr'	=> $e->getMessage(),
	  'stack'	=> $e->getTraceAsString(),
	  'guest.addr'	=> $_SERVER['REMOTE_ADDR'],
	  'guest.agent'	=> $_SERVER['HTTP_USER_AGENT'],
	  'guest.ref'	=> NzArray($_SERVER,'HTTP_REFERER'),
	  'guest.page'	=> $_SERVER['REQUEST_URI'],
	  'guest.ckie'	=> NzArray($_SERVER,'HTTP_COOKIE'),
	  );
	$out = $this->Message_toEmail_forException($arErr);	// generate the message to email
	$subj = $this->Subject_toEmail_forException($arErr);
	$ok = mail(KS_TEXT_EMAIL_ADDR_ERROR,$subj,$out);	// email the message
	echo $this->Message_toShow_forException($msg);		// display something for the guest
	throw $e;
	// FUTURE: log the error and whether the email was successful
   }
   abstract protected function Message_toEmail_forException(array $arErr);
   abstract protected function Subject_toEmail_forException(array $arErr);
   abstract protected function Message_toShow_forException($iMsg);
   /*-----
     ACTION: Displays everything *before* the main page contents
   */
   protected function DoPreContent() {
	$this->ParseInput();
	$this->HandleInput();
	$this->RenderHdrBlocks();
   }
   /*-----
     ACTION: Displays everything *after* the main page contents
   */
   protected function DoPostContent() {
	$this->RenderFtrBlocks();
   }
// ABSTRACT section //
   /*-----
     ACTION: Displays the main page contents
   */
   protected abstract function DoContent();
   /*-----
     ACTION: Grab any expected input and interpret it
   */
   protected abstract function ParseInput();
   /*-----
     ACTION: Take the parsed input and do any needed processing (e.g. looking up data)
   */
   protected abstract function HandleInput();
   /*-----
     ACTION: Render any output that appears *before* the main content
   */
   protected abstract function RenderHdrBlocks();
   /*-----
     ACTION: Render any output that appears *after* the main content
   */
   protected abstract function RenderFtrBlocks();
   /*-----
     ACTION: Start a new section
   */
   public abstract function NewSection($iName);
   /*-----
     ACTION: Open a table, with appropriate CSS class etc.
   */
   public abstract function NewTable($iClass='content');
}
/* ===================
 CLASS: clsVbzSkin_Standard
 PURPOSE: Standard skin class
   Will later be replaceable with other skins
abstract class clsVbzSkin_Standard extends clsVbzSkin {
   private $objApp;
   private $fpTools;
   private $fsLogo;
   public function __construct() {
	parent::__construct();
	$this->fpTools = KWP_TOOLS;
	$this->fsLogo = KWP_LOGO_HEADER;
   }
   public function App(clsVbzApp $iApp=NULL) {
	if (!is_null($iApp)) {
	    $this->objApp = $iApp;
	}
	return $this->objApp;
   }
   protected function Data() {
	return $this->App()->Data();
   }
   public function NewSection($iTitle) {
	$obj = $this->Doc()->NewSection($iTitle,'hdr-sub');
   }
   public function NewTable($iClass='content') {
	$objDoc = $this->Doc();
	$obj = $objDoc->NewTable();
	$obj->ClassName($iClass);
	return $obj;
   }
   protected function Message_toEmail_forException(array $arErr) {
	$guest_ip = $arErr['guest.addr'];
	$guest_br = $arErr['guest.agent'];
	$guest_pg = $arErr['guest.page'];
	$guest_rf = $arErr['guest.ref'];
	$guest_ck = $arErr['guest.ckie'];
	$out = 'Description: '.$arErr['descr'];
	$out .= "\nStack trace:\n".$arErr['stack'];
	$out .= <<<__END__
Client information:
- IP Addr : $guest_ip
- Browser : $guest_br
- Cur Page: $guest_pg
- Prv Page: $guest_rf
- Cookie  : $guest_ck
__END__;
	return $out;
   }
   protected function Subject_toEmail_forException(array $arErr) {
	return 'error in VBZ from IP '.$arErr['guest.addr'];
   }
   protected function Message_toShow_forException($iMsg) {
	$msg = $iMsg;
	$out = <<<__END__
Ack! We seem to have a small problem here. (If it was a large problem, you wouldn't be seeing this message.)
The webmaster is being alerted about this.
Meanwhile, you might try reloading the page -- a lot of errors are transient,
which makes them hard to fix, which is why there are more of them than the other kind.
We apologize for the nuisance.
Error Message: $msg
__END__;
	return $out;
    }
    protected function RenderHdrBlocks() {
	$this->RenderHtmlStart();
	$this->RenderContentHdr();
	$this->DoSidebar();
    }
    protected function RenderFtrBlocks() {
	$this->RenderContentFtr();
	$this->RenderHtmlStop();
    }
    protected function RenderContentFtr() {
	global $didPage,$fltStart;
	echo '';
	$this->DoSepBar();
	echo '
| ';
	$fltExecTime = microtime(true)-$fltStart;
	$dat = getrusage();
	$fltUserTime = $dat["ru_utime.tv_usec"]/1000000;
	$strServer = $_SERVER['SERVER_SOFTWARE'];
	echo $strServer.' .. ';
	echo 'PHP '.phpversion().' .. Generated in '.$fltUserTime.' seconds (script execution '.$fltExecTime.' sec.) .. ';
	$strWikiPg = $this->strWikiPg;
	if ($strWikiPg) {
	    echo 'wiki: <a href="'.KWP_WIKI.kEmbeddedPagePrefix.$this->strWikiPg.'">'.$strWikiPg.'</a> .. ';
	}
	echo date('Y-m-d H:i:s');
	echo ' | 
';
	echo '
 ';
	$didPage = true;
    }
    protected function RenderHtmlStop() {
	echo "\n</body>\n</html>";
    }
// NEW METHODS for this class //
    protected function DoSidebar() {
//	$objCache = $this->CacheMgr();
// TO DO: these should be pulled from the [stats] table
/*
if ($objCache->dtNewest) {
    $timeSidebarBuild=$objCache->dtNewest;
} else {
    $timeSidebarBuild = NULL;
}
*/
    $timeSidebarBuild = NULL;
$statsQtyTitlesAvail = 2245;
$statsQtyStockPieces = 1395;
$statsQtyStockItems = 753;
$statsQtyArtists = 136;
$statsQtyTopics = 1048;
//---------
	echo '';
    }
    protected function DoSepBar() {
      echo $this->Render_HLine();
    }
    protected function ImageSpec($iFileName) {
	return $this->fpTools.'/img'.$iFileName;
    }
    public function Render_HLine($iHeight=NULL) {
      $htHt = is_null($iHeight)?
:('height='.$iHeight);
      return '<img src="'.$this->ImageSpec('/bg/hlines/').'"'.$htHt.' alt="-----" width="100%">';
    }
    private function ToolbarItem($iURL,$iIcon,$iTitle,$iAlt) {
	return '<a href="'.$iURL.'"><img border=0 src="'.$this->ImageSpec('/icons/'.$iIcon.'.050pxh.png').'" title="'.$iTitle.'" alt="'.$iAlt.'"></a>';
    }
    protected function DoToolbar() {
	global $fpPages,$fwpCart;
	echo $this->ToolbarItem($fpPages.'/','home',KS_STORE_NAME.' home page','home page');
	echo $this->ToolbarItem($fpPages.'/search/','search','search page','search page');
	echo $this->ToolbarItem($fwpCart,'cart','shopping cart','shopping cart');
	echo $this->ToolbarItem(KWP_HELP_HOME,'help','help!','help');
    }
// NEW METHODS for this class //
    /*----
      PURPOSE: Renders HTML inside <head></head> section
      HISTORY:
	2011-01-11 Created
    */
    protected function RenderHtmlHeaderSection() {
	$strTitle = KS_STORE_NAME.' - '.$this->strName;
	$out = "\n<title>$strTitle</title>";
	$arVars = array('sheet' => $this->strSheet);
	$objStrTplt = new clsStringTemplate_array(NULL,NULL,$arVars);
	$objStrTplt->MarkedValue(KHT_PAGE_STYLE);
	$out .= $objStrTplt->Replace();
	//$out .= KHT_PAGE_STYLE;
	if (!empty($this->strName)) {
	    $ftName = ': '.htmlspecialchars($this->strName);
	} else {
	    $ftName = 
;
	}
	$strContent = KS_STORE_NAME_META.$ftName;
	$out .= "\n<meta name=description content=\"$strContent\">";
	return $out;
    }
    /*----
      PURPOSE: Renders HTML up to beginning of BODY.
      HISTORY:
	2011-01-11 Extracted everything between <head> and </head> into RenderHtmlHeaderSection()
    */
    protected function RenderHtmlStart() {
	//$this->strCalcTitle = KS_STORE_NAME.' - '.$this->strName;
	$out = KHT_PAGE_DOCTYPE;
	$out .= "\n<html>\n<head>";
	$out .= $this->RenderHtmlHeaderSection();
	$out .= "\n</head>";
	$out .= KHT_PAGE_BODY_TAG;
	echo $out;
    }
    protected function RenderContentHdr() {
	//$strWikiPg = $this->strWikiPg;
      // begin content header
	echo '
';
	echo '';
      // === LEFT HEADER: Title ===
	echo '';
      // === END LEFT HEADER ===
      // === RIGHT HEADER: nav icons ===
	echo '';
      // === END RIGHT HEADER ===
	echo "\n';
	echo '<a href="'.KWP_HOME_ABS.'"><img align=left border=0 src="'.$this->fsLogo.'" title="'.KS_STORE_NAME.' home" alt="'.KS_SMALL_LOGO_ALT.'"></a>';
	if ($this->strTitleContext) {
	  echo '<a href="/">'.KS_STORE_NAME.'</a>: '.$this->strTitleContext.' ';
	}
	echo ''.$this->strTitle.' | ';
	$this->DoToolbar();
	echo ' |   \n | 
";
    }
}
/*%%%%
  CLASS: clsVbzApp
  PURPOSE: container for the chosen skin and database
*/
class clsVbzApp {
    private $objSkin;
    private $objData;
    public function __constructor(clsVbzSkin $iSkin, clsVbzData $iData) {
	$this->objSkin = $iSkin;
	$this->objData = $iData;
    }
    public function Skin() {
	return $this->objSkin;
    }
    public function Data() {
	return $this->objData;
    }
}
/* ===================
  CLASS: clsVbzPage_Cat
  PURPOSE: Handles display of catalog page types
  TO DO: These classes still need more tidying -- see clsPageCat --
    and still need a bit of work to allow user-choosable skins.
*/
abstract class clsVbzPage_Cat extends clsVbzSkin_Standard {
// helper objects
    protected $db;	// database - CHANGE TO PRIVATE
// query
    protected $strReq;	// requested page
// page definition
    protected $strName;	// short title: {item name} (goes into html title, prefixed with store name)
    protected $strTitle;	// longer, descriptive title: {"item name" by Supplier} (goes at top of page)
    protected $strSheet;	// name of style sheet to use (without the .css)
    protected $strWikiPg;	// name of wiki page to embed, if any (blank = suppress embedding)
    protected $strTitleContext;	// context of short title, in HTML: {Supplier: Department:} (goes above title, in small print)
    protected $strHdrXtra;	// any extra stuff (HTML) for the header
    protected $strSideXtra;	// any extra stuff for the sidebar
    protected $lstTop;	// stuff listed at the top of the sidebar
// calculated fields
//    protected $strCalcTitle;
    protected $strContText;
// flags set by wiki contents
    protected $hideImgs;
    public function __construct() {
	parent::__construct();
	$this->lstTop = new clsNavList();
    }
    protected function HandleInput() {
	$this->strSheet	= 'browse';	// default
    }
    /*-----
      IMPLEMENTATION: Retrieves request from URL and parses it
	URL data identifies page, keyed to cat_pages data
    */
    protected function ParseInput() {
	if (isset($_SERVER['PATH_INFO'])) {
	    $strReq = $_SERVER['PATH_INFO'];
	} else {
	    $strReq = 
;
	}
	$this->strReq = $strReq;
	if (strrpos($strReq,'/')+1 < strlen($strReq)) {
	    $strRedir = KWP_CAT_REL.substr($strReq,1).'/';
	    header('Location: '.$strRedir);
	    exit;	// retry with new URL
	}
    }
// DIFFERENT TYPES OF PAGES
    protected function DoNotFound() {
	$this->strWikiPg	= 
;
	$this->strTitle	= 'Unknown Page';
	$this->strName	= 'unknown title in catalog';
	$this->strTitleContext	= 'Tomb of the...';
	$this->strHdrXtra	= 
;
	$this->strSideXtra	= '
Cat #: '.$this->strReq;
    }
// UTILITY
    protected function AddText($iText) {
	$this->strContText .= $iText;
    }
    private function DoWikiContent() {
# WIKI CONTENTS
#	$txtPage = GetEmbedPage('cat');
	if (KF_USE_WIKI) {
	    $txtWiki = GetWikiPage($this->strWikiPg);
	    if ($txtWiki) {
		if (strpos($txtWiki,'__NOIMG__') != -1) {
		    $txtWiki = str_replace('__NOIMG__',,$txtWiki);
		    $this->hideImgs = true;
		}
	    }
	    if ($txtWiki) {
		echo '';
	    }
	}
    }
}
/*%%%%
  TODO:
    * figure out why we need to have this as a separate class from clsVbzPage_Cat
    * give it a better name
*/
class clsPageCat extends clsVbzPage_Cat {
    private $objCatPage;	// object for identifying page to display
    private function Suppliers($id=NULL) {
	$tbl = $this->Data()->Suppliers();
	$tbl->Page($this);
	if (is_null($id)) {
	    return $tbl;
	} else {
	    $rc = $tbl->GetItem($id);
	    return $rc;
	}
    }
    protected function RenderHdrBlocks() {
	if ($this->useSkin) {
	    parent::RenderHdrBlocks();
	}
    }
    protected function RenderFtrBlocks() {
	if ($this->useSkin) {
	    parent::RenderFtrBlocks();
	}
    }
    protected function HandleInput() {
	parent::HandleInput();
	$strReq = $this->strReq;
	$this->objCatPage = $this->Data()->Pages()->GetItem_byKey($strReq);
	$objPage = $this->objCatPage;
	$this->useSkin = TRUE;
	if ($this->strReq) {
	    if (is_object($objPage)) {
		switch ($objPage->Type) {
		case 'S':
		  $this->DoCatSupp();
		  break;
		case 'D':
		  $this->DoCatDept();
		  break;
		case 'T':
		  $this->DoCatTitle();
		  break;
		case 'I':
		  $this->useSkin = FALSE;
		  $this->DoCatImage();
		  break;
		}
	    } else {
		$this->DoNotFound();
	    }
	} else {
	    $this->DoCatHome();
	}
    }
    public function DoContent() {
	echo $this->Doc()->Render();
    }
// SIDEBAR INFO for different types of pages
  private function DoCatIndicia() {
    $this->lstTop->Add('Section','<a href="'.KWP_CAT_REL.'">by supplier</a>');
  }
  private function DoSuppIndicia($iSupp,$isFinal=true) {
    $this->DoCatIndicia();
    if ($isFinal) {
      $this->lstTop->Add('Supplier',$iSupp->Name);
      $this->lstTop->Add('<a href="'.KWP_WIKI.$iSupp->Name.'">more info</a>');
    } else {
      $this->lstTop->Add('Supplier',$iSupp->Link());
    }
  }
  private function DoDeptIndicia($iDept,$isFinal=true) {
    $this->DoSuppIndicia($iDept->Supplier(),false);
    if ($isFinal) {
      $this->lstTop->Add('Dept.',$iDept->Name);
    } else {
      $this->lstTop->Add('Dept.',$iDept->LinkName());
    }
  }
  private function DoTitleIndicia($iTitle) {
    $this->DoDeptIndicia($iTitle->Dept(),false);
    $this->lstTop->Add('Title',$iTitle->Name);
    $this->lstTop->Add(' - catalog #',$iTitle->CatNum());
  }
    private function DoCatHome() {
	$this->DoCatIndicia();
	$this->strWikiPg	= 'cat';
	$this->strTitle	= 'Catalog Home';
	$this->strName	= 'Catalog main page';
	$this->strTitleContext	= 'hello and welcome to the...';
	$this->Suppliers()->DoHomePage();
	$this->AddText($this->Doc()->Render());
    }
    private function DoCatSupp() {
	$idRow = $this->objCatPage->Value('ID_Row');
	$rcSupp = $this->Suppliers($idRow);
	assert('is_object($rcSupp)');
	$strSuppName = $rcSupp->Value('Name');
	$this->DoSuppIndicia($rcSupp);
	$this->strWikiPg	= 'supp:'.strtoupper($rcSupp->Value('CatKey'));
	$this->strTitle	= $strSuppName;
	$this->strName	= 'listing for '.$strSuppName;
	$this->strTitleContext	= '<a href="'.KWP_CAT_REL.'">Suppliers</a>: '.$strSuppName.':';
	$rcSupp->DoDeptsPage();
	$this->AddText($this->Doc()->Render());
    }
  private function DoCatDept() {
    CallEnter($this,__LINE__,'clsPage.DoCatDept()');
//    $objDeptTbl = VbzClasses::Depts();
    $objDeptTbl = $this->Depts();
    $objDept = $objDeptTbl->GetItem($this->objCatPage->ID_Row);
    assert('is_object($objDept)');
    $objSupp = $objDept->Supplier();
    assert('is_object($objSupp)');
    $strDeptName = $objDept->Name;
    $strSuppName = $objSupp->Name;
    $strDeptLink = $objDept->LinkName();
    $strSuppLink = $objSupp->Link();
    $this->DoDeptIndicia($objDept);
    $this->strWikiPg	= 'dept:'.strtoupper($objDept->PageKey);
    $this->strTitle	= $strSuppName;
    $this->strName	= $strDeptName.' dept. of '.$strSuppName;
    $this->strTitleContext	= 'items <a href="'.KWP_CAT_REL.'">supplied</a> by '.$strSuppLink.'\'s '.$strDeptName.' department:';
    $this->AddText($objDept->DoPage());
    CallExit('clsPage.DoCatDept()');
  }
  private function DoCatTitle() {
    CallEnter($this,__LINE__,'clsPage.DoCatTitle()');
    $strPageKey = $this->objCatPage->Path;
//    $objTitleTbl = VbzClasses::Titles();
    $objTitleTbl = $this->Titles();
    $idRow = $this->objCatPage->ID_Row;
    $objTitle = $objTitleTbl->GetItem($idRow);
    assert('is_object($objTitle)');
    $objDept = $objTitle->Dept();
    assert('is_object($objDept)');
    $objSupp = $objDept->Supplier();
    assert('is_object($objSupp)');
    $strTitleName = $objTitle->Name;
    $this->DoTitleIndicia($objTitle);
//    $this->strAbbr	= 'title:'.strtoupper($strCatNum);
    $this->strWikiPg	= 'title:'.$objTitle->CatNum();
//print 'ABBR='.$this->strAbbr;
    $this->strTitle	= $strTitleName;
    $this->strName	= $strPageKey.' "'.$strTitleName.'" from '.$objSupp->Name;
    $this->strTitleContext 	= 
      'items <a href="'.KWP_CAT_REL.
      '">supplied</a> by '.$objSupp->Link().'\'s '.
      $objDept->LinkName().' department:';
    $objTitle->hideImgs = $this->hideImgs;
    $this->AddText($objTitle->DoPage());
    CallExit('clsPage.DoCatTitle()');
  }
  private function DoCatImage() {
    CallEnter($this,__LINE__,'clsPage.DoCatImage()');
//    $objImageTbl = VbzClasses::Images();
    $objImageTbl = $this->Images();
    $objImage = $objImageTbl->GetItem($this->objCatPage->ID_Row);
    $objImage->DoPage();
    CallExit('clsPage.DoCatImage()');
  }
}</php>