Developers API Tutorials

From Zen Cart(tm) Wiki
Revision as of 06:41, 31 March 2006 by Milosh (talk | contribs) (Observe and Prosper)
Jump to: navigation, search



initSystem Introduction

Why initSystem ??

The term initSystem, apart from being a tag used to group certain php files together in the new documentation system, is meant to embrace all of those files that are automatically included/initialised before any 'command' scripts can be run.

Zen Cart uses a (non Object Oriented) page controller pattern to decide the scripts run, based on HTTP_GET parameters. The most important of these being the 'main_page' HTTP_GET parameter. Dependant on that parameter a command script is then run. Each commmand script resides in a directory in /includes/modules/pages.

For example if main_page=login the command script would be taken from the /inlcudes/modules/pages/login/ directory. However the first thing every command script always does, is require the /includes/application_top.php file. This is the heart of the initSystem.

It is application_top.php that is responsible for initialising basic subsystems (database abstraction/sessions/languages etc) and loading global configuration data. In the past this was done using a hard-coded script. From v1.3.0 however Zen Cart now uses a control array to decide which functions/classes/data files are to be included and initialised. This allows contribution authors and 3rd party developers to gain access to the initSystem without compromising upgradeability.

In the following sections we will work through exactly how the Zen Cart engine uses application_top.php to initialise core systems.

application_top.php - A little bit of history

In terms of its osCommerce roots, application_top.php was the file included on every page/script needed to invoke and handle basic core sub-systems. Any function/class that was neeeded globally by any page needed to be initialised here.

From a customisation perspective this was a bad thing. If 3rd party code(contributions) needed to access a new global function/class then application_top.php would need to be 'hacked'. This would obviously cause problems on upgrades, when application_top.php would be over written, and any customisations would be lost.

Zen Cart attempted to mitigate this by providing certain override directories where extra data files/functions files could be placed that would be automatically included when application_top.php was run.

The problem with this system is that it only provides for a very few places within the running order of application_top.php where new code can be introduced. It also did not provide at all for the addittion of new classes. What was required was an application_top.php that allowed for the placing of any new function/class/script that was completely under the developers control. Futhermore some method of loading and invoking classes was also required.

v1.3.0 achieves this by abstracting the code run by application_top.php into a control array. This array stores details of functions/classes/init scripts that need to be run, and the order in which they are run in a special php array. Given this it is now possible for 3rd party developers to 'hook' into application_top.php and be confident that any future code upgrades will not affect their own code.

application_top.php - Breakpoints

In Zen Cart v1.3.0, there is now almost no procedural code in application_top.php. The small amount that is there will be discussed later. The bulk of procedural code in application_top.php is now given over to handling breakpoints. Breakpoints can simply be described as points of importance. We currently have approximately 20 breakpoints in application_top.php. At each breakpoint something important happens, we may load a function or class, initialise a class, load a script fragment. The important point is to recognise that at each beakpoint 3rd party code, by adding to the control array, can also load functions, load classes, initialise classes, run a class method or load a (require a) script fragment.

The Control Array

Control arrays are automatically loaded from the directory /includes/auto_loaders. Every .php file within that directory is expected to have a certain structure. In 1.3.0 we use a file called config.core.php as the main file for governing application_top.php, 3rd party developers can add their own control arrays. The structure of each file should look like this.

$autoLoadConfig[0] = array(); The value after $autoLoadConfig. in this case [0] represents the order in which the actions happen (e.g. the Breakpoint). Such that $autoLoadConfig[0] will occur before $autoLoadConfig[1]. Note also that any 2 entries where the breakpoint is the same will occur in the order they appear within the file. The actual contents of the array() part depends upon what effect is needed. lets consider a number of different scenarios.

First I just want to require a file to be loaded. for this the control array entry would be

 $autoLoadConfig[0][] = array('autoType'=>'require', 'loadFile'=> DIR_WS_INCLUDES . 'somefile.php'); 

The autotype parameter tells us that we just want to require a file, the loadFile parameter tells us which file we want to load.

Loading function files can also obviously be done using the above. Similarly if we want to 'include' a file.

 $autoLoadConfig[0][] = array('autoType'=>'include', 'loadFile'=> DIR_WS_INCLUDES . 'somefile.php'); 

We then have a special type of 'require'. The initSystem introduces a special class of .php files called init_scripts. These are stored in the includes/init_includes directory. Each of these contain a small amount of procedural code that can be run as part of the initSystem process. The reason for separating them out into a special directory is to allow for those init_scripts to be overridden, more of which later. For now, to load an init_script we use the following control array structure.

 $autoLoadConfig[] = array('autoType'=>'init_script', 'loadFile'=> 'init_database.php'); 

Where the auto_loader system comes into its own is in the handling of class files. With a class file we want to load the class file definition. Then instantiate the class, and finally possibly run a class method within application_top.php

In terms of the control array we have the following entries to help us.

 $autoLoadConfig[0][] = array('autoType'=>'class',
 $autoLoadConfig[30][] = array('autoType'=>'classInstantiate',
 $autoLoadConfig[80][] = array('autoType'=>'classInstantiate',
 $autoLoadConfig[120][] = array('autoType'=>'objectMethod',
                               'methodName' => 'add_current_page');

Taking these options one by one

Where autotype=>'class' all we are really doing here is 'including' the 'loadFile'. However we, in this case draw the file from the includes/classes (DIR_WS_CLASS) directory.

Where autotype=>'classInstantiate' executes code of the form objectName = new className();

An example based on the code above is

 $zc_cache = new cache();

One corollary to this is that we may need to instantiate a class that is bound to a session, like the shopping_cart class. In this case as from the example above we get

 $_SESSION['cart'] = new shoppingCart();

and in fact we take that one step further, Noramlly we only want to instantiate a session object if it is not already a session object. In this case we take advantage of the 'checkInstantiated' property, which would generate code

 if (!$_SESSION['cart']) {
   $_SESSION['cart'] = new shoppingCart();  

The final example, where autotype-'objectMethod' shows how to run a class method within application_top.php. At the time of writing there is no provision for passing method parameters, So the code generated would be(based on the example above).


Notes on admin autoloaders

The goal of v1.3.X is to eventually remove and refactor all function into classes. Further these classes will be common between admin and catalog code.

However this presents a problem with the autoloading of class files. Currently the autoloader code, will default to loading a class from the catalog includes/classes directory rather than admin/includes/classes.

To provide for that interim period where we still need to to load admin class files from admin/includes/classes, we provide an extra option to the 'autotype'=>class option.

consider this

 $autoLoadConfig[0][] = array('autoType'=>'class',

if this is used in an admin autoloader it will load the class from the catalog classes directory. For the base class this is fine as the code can be shared between catalog and admin. However, at the moment the split_page_results_class is different between admin and catalog. So in order to load a admin specific class we do

 $autoLoadConfig[0][] = array('autoType'=>'class',

Overriding/Extending the system autoloader

There are 2 ways of overriding/extending the inbuilt auto_loader and thus affecting what happens during the loading of application_top.php.

The usual method would be to simply add a new file to the /includes/auto_loader/ directory. The file you add here should have a .php extension and should contain 1 or more control array definitions.

This is the recommended method to use for adding code to be executed witin application_top.php, and allows contribution authors to customise the code here in a way that will be unaffected by system upgrades.

Within the /includes/auto_loader/ directory is another directory called overrides. This can be used to override any autoloader file in the /includes/auto_loader/ directory. For example, the main autoloader file used in Zen Cart is config.core.php. If a file called config.core.php is placed in the overrides directory, this will be used instead of the original.

init_scripts and application_top.php


The initSystem allows you to automate the including/requiring of files and to automate the loading/instantiating of classes. However we still also need to be able to run some procedural code. We also want to allow 3rd parties to override that procedural code. init_scripts allow us to do this.


There are currently 17 init_scripts in the base 1.3.0 release. These init_scripts are in the includes/init_includes direcrtory.

  • init_add_crumbs.php (Responsible for initialising the Breadcrumb)
  • init_cart_handler.php (Responsible for handling Cart actions)
  • init_category_path.php (Reponsible for initialising Category Paths)
  • init_currencies.php (Responsible for initialising the Currencies Sub-System)
  • init_customer.php (Responsible for checking customer status, either thru Down for Maintenance or the Approval level)
  • init_database.php (Responsible for initialising the DB layer)
  • init_db_config_read.php (Responsible for reading SB configuration data)
  • init_file_db_names.php (Responsible for loading File and Database tablename Defines)
  • init_general_funcs.php (Resposible for loading general functions from the includes/functions directory)
  • init_gzip.php (Responsible for loading Gzip functions)
  • init_languages.php (Responsible for loading language based defines)
  • init_sanitize.php (Responsible for loading inout sanitising code)
  • init_sefu.php (Responsible for loading code to provide Search engine friendly urls)
  • init_sessions.php (Responsible for loading Session code)
  • init_special_funcs.php (Responsible for loading specialized funcions)
  • init_templates.php (Responsible for initialising the template System)
  • init_tlds.php (Responsible for setting Top Level Domain Variables)

Procedural code in application_top.php

Despite the use of the autoloader system, there is still a little procedural code left in application_top.php, although most of this procedural code is given over to processing autoloaders themselves.

Below is the code from the catalog includes/application_top.php. Note: I have removed all documentation tags for clarity

define('DEBUG_AUTOLOAD', false);
define('IS_ADMIN_FLAG', false);
define('PAGE_PARSE_START_TIME', microtime());
if (file_exists('includes/local/configure.php')) {
} else {
  error_reporting(E_ALL & ~E_NOTICE);
if (file_exists('includes/configure.php')) {
} else {
  header('location: zc_install/index.php');
if (!is_dir(DIR_FS_CATALOG.'/includes/classes'))  header('location: zc_install/index.php');
if ($za_dir = @dir(DIR_WS_INCLUDES . 'extra_configures')) {
  while ($zv_file = $za_dir->read()) {
    if (preg_match('/\.php$/', $zv_file) > 0) {
      include(DIR_WS_INCLUDES . 'extra_configures/' . $zv_file);
$loader_file = 'config.core.php';
$base_dir = DIR_WS_INCLUDES . 'auto_loaders/';
if (file_exists(DIR_WS_INCLUDES . 'auto_loaders/overrides/' . $loader_file)) {
  $base_dir = DIR_WS_INCLUDES . 'auto_loaders/overrides/';
include($base_dir . $loader_file);
if ($loader_dir = dir(DIR_WS_INCLUDES . 'auto_loaders')) {
  while ($loader_file = $loader_dir->read()) {
    if ((preg_match('/^config\./', $loader_file) > 0) && (preg_match('/\.php$/', $loader_file) > 0)) {
      if ($loader_file != 'config.core.php') {
        $base_dir = DIR_WS_INCLUDES . 'auto_loaders/';
        if (file_exists(DIR_WS_INCLUDES . 'auto_loaders/overrides/' . $loader_file)) {
          $base_dir = DIR_WS_INCLUDES . 'auto_loaders/overrides/';
        include($base_dir . $loader_file);
if (( (!file_exists('includes/configure.php') && !file_exists('includes/local/configure.php')) ) || (DB_TYPE == ) ||    (!file_exists('includes/classes/db/' .DB_TYPE . '/query_factory.php'))) {
  header('location: zc_install/index.php');
require(DIR_WS_INCLUDES . 'counter.php');
$customers_ip_address = $_SERVER['REMOTE_ADDR'];
if (!isset($_SESSION['customers_ip_address'])) {
  $_SESSION['customers_ip_address'] = $customers_ip_address;

Overriding init_scripts

It is very simple to override a core init script. The directory includes/init_incudes contains a directory called overrides. If I wanted to override the incudes/init_includes/init_sessions.php script then I would simply create a file called init_sessions.php in the includes/init_includes/overrides directory.

Observer Class


One of the many goals of the Zen-Cart project has always been to make it easier for 3rd party developers to add functionality to the core code in an easy and unobtrusive manner. To do this we have in the past relied on the override and auto inclusion systems. However these still do not give developers an easy method of hooking into many areas of core code, without 'hacking' core files themselves.

The observer/notifier system was introduced to give developers unprecendented access to core code, without the need to touch any core files at all. Although ostensibly written for an object oriented code base, we will see later how it can also be used with general procedural code as well.

Extending all Classes

In order to implement the observer/notifier system, some structural changes have been made to Zen Cart. Firstly two new classes have been introduced, the base class (class.base.php) and the notifier class (class.notifier.php).

The base class contains the code that is used to implement the observer/notifier system. However to make it effective all other Zen Cart classes now have to be declared as children of the base class. You will see this if you look at the source of any of the Zen Cart classes.

  class currencies extends base {

The notifier class will be discussed later, when we look at extending the observer/notifier system (ONS) into procedural code.

Notifiers: Big Brother is watching

So, what is all the fuss about?

The point of the ONS is that developers can write code that wait for certain events to happen, and then when they do, have their own code executed.

So, how are events defined, where are they triggered?

Events are triggered by code added to the core for v1.3 (with more to be added over time). In any class that wants to notify of an event happening we have added:


An example would probably help here:

In the shopping cart class after an item has been added to the cart this event is triggered:


There are many other events that have notifiers in Zen Cart v1.3.0; for a list see here.

All of this notifying is all well and good, but how does this help developers?

Observe and Prosper

To take advantage of notifiers, developers need to write some code to watch for them. Observers need to be written as a class. There's even a nice directory, includes/classes/observers, where developers can put these classes.

Lets take an example. Using the notifier mentioned above (NOTIFIER_CART_ADD_CART_END), how would I write a class that watched for that event?

 class myObserver extends base {
   function myObserver() {

As you can see we have defined a new class called myObserver and in the constructor function for that class (function myObserver) have attached this myObserver class to the event NOTIFIER_CART_ADD_CART_END.

Fine, I here you saying, but how do I actually doing any thing useful?

Ok, good question. Whenever an event occurs, the base class looks to see if any other observer class is watching that event. If it is, the base class executes a method in that observer class. Remember the $this->notify('EVENT_NAME') from above? Well, when that event occurs, the base class calls the update method of all observers. Lets see some more code:

 class myObserver extends base {
   function myObserver() {
   function update(&$callingClass, $notifier, $paramsArray) {
     ... do some stuff

Now, whenever the NOTIFIER_CART_ADD_CART_END occurs, our myObserver::update method will be executed.

Some notes about the parameters... As you can see the update method is passed 3 parameters. These are:

  • &$callingClass - This is a reference to the class in which the event occurred, allowing you access to that class' variables
  • $notifier - The name of the notifier that triggered the update (It is quite possible to observe more than one notifier)
  • $paramsArray - Not Used (for future)

Including observers into your code

Please note that the includes/classes/observers directory is not an autoload directory, so you will need to arrange for application_top.php to autoload your observer class. Lets assume you are using the freeProduct class (see below), and you have saved this in includes/classes/observers/class.freeProduct.php

You now need to arrange for this class to be loaded and instantiated. To do this tou need to use the application_top.php autoload system.

In includes/autoloaders xreate a file called config.freeProduct.php containing


$autoLoadConfig[90][] = array('autoType'=>'class',
$autoLoadConfig[90][] = array('autoType'=>'classInstantiate',


Note: 90 has been chosen as the offset as the observer needs to attach to the $SESSION['cart'] class. This is instantiated at offset 80.

To tie this all together, lets look at a real world example.

A Real World Example

One of the most often requested features is the ability for the store to automatically add a free gift to the Shopping Cart if the customer spends over a certain amount.

The code has to be intelligent, it not only has to add the free gift when the shopper spends over a certain amount, but also remove the gift if the user changes the contents of the shopping cart, such that the total falls below the threshold.

Traditionally, although the code for this is not particularly difficult, it would have meant 'hacking' the core shoppingCart class in a number of places. With the ONS, this can be achieved with one very small custom class and absolutley no hacking whatsoever.

Here's the code.

 * Observer class used to add a free product to the cart if the user spends more than $x
class freeProduct extends base {
   * The threshold amount the customer needs to spend.
   * Note this is defined in the shops base currency, and so works with multi currency shops
   * @var decimal
  var $freeAmount = 50;
   * The id of the free product.
   * Note. This must be a true free product. e.g. price = 0 Also make sure that if you don't want the customer
   * to be charged shipping on this, that you have it set correctly.
   * @var integer
  var $freeProductID = 57;
   * constructor method
   * Attaches our class to the $_SESSION['cart'] class and watches for 2 notifier events.
  function freeProduct() {
    $_SESSION['cart']->attach($this, array('NOTIFIER_CART_ADD_CART_END', 'NOTIFIER_CART_REMOVE_END'));
   * Update Method
   * Called by observed class when any of our notifiable events occur
   * @param object $class
   * @param string $eventID
  function update(&$class, $eventID) {
    if ($_SESSION['cart']->show_total() >= $this->freeAmount && !$_SESSION['cart']->in_cart($this->freeProductID) ) {
    if ($_SESSION['cart']->show_total() < $this->freeAmount && $_SESSION['cart']->in_cart($this->freeProductID) ) {
    if ($_SESSION['cart']->in_cart($this->freeProductID)) {
      $_SESSION['cart']->contents[$this->freeProductID]['qty'] = 1;

First, I have set the options for the system in the class itself. This is obviously a bad idea, and it would be much better to have an admin module to set these options.

Second, notice that we are actually watching for 2 events in the one class.

   $_SESSION['cart']->attach($this, array('NOTIFIER_CART_ADD_CART_END', 'NOTIFIER_CART_REMOVE_END'));

so we are watching for the NOTIFIER_CART_ADD_CART_END and NOTIFIER_CART_REMOVE_END of the shopping cart class.

The update class is extremely simple but in its simplicity manages to do all the work we require of it. It first tests to see if the total in the cart is over the threshold and, if it hasn't already, adds the free product to the cart. It then tests to see if the cart total has dropped below the threshold and, if the free product in the cart, removes it.

Now that was cool, how about something a little more difficult.

Another Real World Example

Again we return to the Shopping Cart and promotions. Another oft requested feature is the BOGOF promotion, or Buy One Get One Free. This is a little more difficut to achieve than our previous example, as there is some manipulation needed of the cart totals. However as you will see it is still pretty much a breeze.

 * Observer class used apply a Buy One Get One Free(bogof) algorithim to the cart
class myBogof extends base {
   * an array of ids of products that can be BOGOF.
   * @var array
  var $bogofsArray = array(10,4); //Under Siege2-Dark Territory & The replacement Killers
   * Integer number of bogofs allowed per product
   * For example if I add 4 items of product 10, that would suggest that I pay for 2 and get the other 2 free.
   * however you may want to restrict the customer to only getting 1 free regardless of the actual quantity
   * @var integer
  var $bogofsAllowed = 1;
   * constructor method
   * Attaches our class to the $_SESSION['cart'] class and watches for 1 notifier event.
  function myBogof() {
    $_SESSION['cart']->attach($this, array('NOTIFIER_CART_SHOW_TOTAL_END'));
   * Update Method
   * Called by observed class when any of our notifiable events occur
   * This is a bit of a hack, but it works. 
   * First we loop thru each product in the bogof Array and see if that product is in the cart.
   * Then we calculate the number of free items. As it is buy one get one free, the number of free items
   * is equal to the total quantity of an item/2.
   * Then we have to hack a bit (would be nice if there was a single cart method to return a product's in-cart price)
   * We loop thru the cart until we find the bogof item, get its final price, calculate the saving
   * and adjust the cart total accordingly.
   * @param object $class
   * @param string $eventID
  function update(&$class, $eventID) {
    $cost_saving = 0;
    $products = $_SESSION['cart']->get_products();
    foreach ($this->bogofsArray as $bogofItem) {
      if ($_SESSION['cart']->in_cart($bogofItem)) {
        if (isset($_SESSION['cart']->contents[$bogofItem]['qty']) && $_SESSION['cart']->contents[$bogofItem]['qty'] > 1) {
          $numBogofs = floor($_SESSION['cart']->contents[$bogofItem]['qty'] / 2);
          if ($numBogofs > $this->bogofsAllowed) $numBogofs = $this->bogofsAllowed;
          if ($numBogofs > 0) {
            for ($i=0, $n=sizeof($products); $i<$n; $i++) {
              if ($products[$i]['id'] = $bogofItem) {
                $final_price = $products[$i]['final_price'];
            $cost_saving .= $final_price * $numBogofs;
    $_SESSION['cart']->total -= $cost_saving;

NB. There are still some weaknesses here. First although the adjust total is correctly showm on the shopping cart page and sidebox, the line total is not adjusted. Secondly this will probably produce a confusing output at checkout. Have not tested for tax compliance yet @TODO

Notifiers currently set in v1.3.0

Notifier points for Zen Cart 1.3.0

Shopping Cart class


Order Class:



  • NOTIFY_EMAIL_AFTER_SEND (individual email)



Individual Pages (Header scripts):