Zen Cart API 教程

1.1 InitSystem

1.1.1 initSystem 介绍

为什么是 initSystem??

initSystem 原来是指一个用在把一定 PHP 文件组合在一起的标签,在新的 Zen Cart 文献中,initSystem 这个短语,是指在任何 ‘命令’脚本运行之前被自动包括或初始化的全部文件。

Zen Cart 使用一个(非面向对象)页面控制器模式,以 HTTP_GET 参数为基础,决定需要运行的脚本。其中最重要的是 'main_page' 这个 HTTP_GET 参数。取决于该参数,一个命令脚本然后运行。每个命令脚本位于 /includes/modules/pages 目录中。

例如,如果 main_page=login 那么将会从 /includes/modules/pages/login/ 目录提取命令脚本。然而每一个命令脚本要做的第一件事是 require() /includes/application_top.php 文件。这个文件是 initSystem 的核心。

application_top.php 文件负责初始化基本的子系统(数据库抽象/sessions/语言等等)以及加载全局配置数据。在以前这些是通过一个硬编码(hard-coded)脚本 来实现的。从 v1.3.0 开始,Zen Cart 现在使用了一个控制数组来决定哪些函数/类/数据 文件被包括和初始化。这将允许开发者和贡献者访问和扩展 initSystem 而不受升级影响。

在下面的几个章节,我们将会探讨 Zen Cart 引擎是如何使用 application_top.php 来初始化系统的。

1.1.2 application_top.php - 一点历史

按照 osCommerce 的定义,application_top.php 是被每一个“唤起和处理基础核心子系统所必须的”页面或脚本所包括的文件。任何被页面所需要的全局函数或类必须在这里被初始化。

从一个定制角度而言这糟糕透了。如果第三方代码(贡献者)需要访问一个新的全局函数或类,那么 application_top.php 需要被破解。这显然会引发升级问题:当 application_top.php 被重写(在升级过程中),任何定制的代码将会丢失。

Zen Cart 试图减轻这个痛苦,办法是通过提供一定的重写目录,来放置额外的数据或函数文件,当 application_top.php 运行的时候可以自动包括进这些额外文件。

这个系统的问题是:在 application_top.php 运行顺序中,只提供了很少的空间来引入新的代码。它同时也没有提供引入新类的功能。需求是:一个 application_top.php 文件应该允许放置由开发者完全掌控的任意新函数、类或者脚本。更进一步,还应该允许放置一些加载和唤起类的方法。

自从 v1.3,Zen Cart 通过把由 application_top.php 运行的代码抽象进一个控制数组,来实现这个目标。这个数组存储了需要运行的函数、类、初始化脚本的细节,以及它们(函数、类、初始化脚本)的运行顺序。由 此,现在第三方开发者可以 hook (挂勾)到 application_top.php,同时可以确信将来任何的代码升级(系统升级)一般不会覆盖他们自己的代码。

1.1.3 application_top.php - 断点

在 Zen Cart,application_top.php 文件中现在几乎没有过程代码。很少的一部分过程代码将会稍后探讨。以前大量存在于 application_top.php 文件中的过程代码现在让位给处理断点。断点可以简单的描述为重要的节点。在 application_top.php 文件中我们现在有差不多20个断点。在每一个断点,一些重要的事情发生了。- 我们可以加载一个函数或者类,初始化一个类,加载一个脚本片断,诸如此类。重要的节点用来识别在每个断点,第三方代码(通过添加到控制数组的方式)也能加 载函数,加载类,初始化类,运行一个类的方法或者加载(require)一个脚本片断。

1.1.4 控制数组

控制数组会从 /includes/auto_loaders 目录中被自动加载。在那个目录中的每一个 *.php 文件预计拥有一定的结构。在 v1.3 我们使用一个名为 config.core.php 的文件作为控制 application_top.php 的主要文件。第三方开发者可添加他们自己的控制数组文件。每个文件的结构看起来应该是这样的:

 

  1. $autoLoadConfig [0] =  array ();  

在 $autoLoadConfig 后面的值(本例中是[0])代表动作发生的顺序(也就是断点),这样一来 $autoLoadConfig[0] 将会在 $autoLoadConfig[1] 之前发生。同时注意断点相同的任意两个条目将会以它们在文件中出现的先后顺序发生。array() 部分的内容取决于需要达到的效果。让我们设想一些不同的场景。

首先,我只想包括一个需要加载的文件。为此,控制数组条目将会是:

 

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

autoType 参数告诉我们,我们只想包括一个文件。

loadFile 参数告诉我们,我们要加载的是哪个文件。

加载函数文件显然可以通过以上方法来做到。

类似的,如果我们希望“include”一个文件:

 

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

我们于是有了一个特殊的“require”类型。initSystem 引入了一个名为 init_scripts 的特殊类的 .php 文件。这些文件保存在 includes/init_includes 目录中。每一个这样的文件包含有少量的可以作为 initSystem 进程的一部分运行的过程编码。把它们分离进一个特殊目录的原因是,可以允许重写那些 init_scripts,这将会在稍后进一步探讨。现在,为了加载一个 init_script 我们使用以下的控制数组结构:

 

  1. $autoLoadConfig [] =  array (  
  2.             array (  
  3.                 'autoType'  =>  'init_script' ,  
  4.                 'loadFile'  =>  'init_database.php'   
  5.             )  
  6.         );  

。。。。。。。通过一个类文件,我们想加载类文件定义,然后实例化这个类,最后可能运行这个类的一个方法(所有这样运行的,都在 application_top.php 文件范围内)。

通过控制数组,我们有以下的条目来帮助我们。

 

  1. $autoLoadConfig [0][] =  array ( 'autoType' => 'class' ,  
  2.                               'loadFile' => 'shopping_cart.php' );  
  3. $autoLoadConfig [30][] =  array ( 'autoType' => 'classInstantiate' ,  
  4.                               'className' => 'cache' ,  
  5.                               'objectName' => 'zc_cache' );  
  6. $autoLoadConfig [80][] =  array ( 'autoType' => 'classInstantiate' ,  
  7.                               'className' => 'shoppingCart' ,  
  8.                               'objectName' => 'cart' ,  
  9.                               'checkInstantiated' =>true,  
  10.                               'classSession' =>true);  
  11. $autoLoadConfig [120][] =  array ( 'autoType' => 'objectMethod' ,  
  12.                               'objectName' => 'navigation' ,  
  13.                               'methodName'  =>  'add_current_page' );  

逐一分析以上选项。

autoType => 'class' 我们在这做的是包括进一个“loadFile”。然后,在本例中我们从 includes/classed (DIR_WS_CLASS)目录提取文件。

autoType => 'classInstantiate' 执行这样形式的代码:objectName = new className();。

根据以上的代码,得到的一个例子:

 

  1. $zc_cache  =  new  cache();  

由此推论,我们可能需要实例化一个与 session 相关的类。例如 shopping_cart 类。从上面的例子中,我们可以得到:

 

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

事实上我们更进一步,通常来说我们只想实例化一个 session 对象,如果它还不是一个 session 对象。这种情况下,我们使用了“checkInstantiated”属性,这将会生成以下代码:

 

  1. if  (! $_SESSION [ 'cart' ]) {  
  2.     $_SESSION [ 'cart' ] =  new  shoppingCart();  
  3. }  

最后的例子,autoType => 'objectMethod' 展示了如何在 application_top.php 文件中运行一个类的方法。当写下本教程的时候,还没有传递方法参数的规定。所以产生的代码将会是(基于以上例子):

 

  1. $navigation  -> add_current_page();  

1.1.4.1 关于后台自动加载器的注解

v1.3.x 的目标是最终把所有的函数移动和重构进类。更进一步,这些类将会被后台和前台代码通用。

然而这将带来一个问题:自动加载类文件。现在的自动加载器的代码,将会默认的加载来自前台 includes/classes 的类,而不是 admin/includes/classes。

为了从 admin/includes/classes 目录加载后台类文件,在现在这个过渡时期,我们为 'autoType' => class 提供了一个额外的选项。

设想这个:

 

  1. $autoLoadConfig [0][] =  array ( 'autoType'  =>  'class' ,  
  2.             'loadFile'  =>  'class.base.php' );  

如果这用在一个后台自动加载器中,它会加载来自前台类目录的类。对于基类而言,这很好,因为代码可以在前台和后台分享。然而,split_page_results_class 在前台和后台是不同的。所以为了加载一个后台类,我们使用:

 

  1. $autoLoadConfig [0][] =  array ( 'autoType'  =>  'class' ,  
  2.             'loadFile'  =>  'splite_page_results.php' ,  
  3.             'classPath'  => DIR_WS_CLASSES);  

1.1.5 重写或者扩展系统的自动加载器

重写内建的 auto_loader 有两种方法,然后影响在加载 application_top.php 过程发生的事。

通常的方法是在 /includes/auto_loader/ 目录中添加新的文件。在这添加的文件应该以 config 开始和以一个 .php 后缀结尾(也就是:config.you_app_name.php ),并且文件内容应该包括一个或者多个控制数组定义。这是推荐使用的方法,来为在 application_top.php 内部执行添加新的代码,同时允许开发者在这定制代码,通常不会受到系统升级的影响。

除此以外,在 /includes/auto_loader/ 目录中还有一个名为 overrides 的目录。它可以用来重写任何位于 /includes/auto_loader/ 目录中的自动加载器文件。例如,在 Zen Cart 使用的主要自动加载器文件是 config.core.php。如果在 overrides 目录中放置名为 config.core.php 的文件,它会取代原先的文件。

1.1.6 init_scripts 和 application_top.php

1.1.6.1 介绍

initSystem 允许你自动 include 或者 require 文件,以及自动加载或者实例化类。然后我们仍然需要可以运行一些过程代码。我们也想允许第三方插件重写该过程代码。init_scripts 允许我们做到这些。

1.1.6.2 init_scripts

在 1.3.0 版本中有18个基本的 init_scripts。这些 init_scripts 位于 /includes/init_includes 目录中。

  • init_add_crumbs.php (负责初始化 Breadcrumb)
  • init_cart_handler.php (负责处理 Cart 动作)
  • init_category_path.php (负责初始化分类路径)
  • init_currencies.php (负责初始化货币子系统)
  • init_customer_auth.php (负责检查客户状态, 同时either thru Down for Maintenance or the Approval level)
  • init_database.php (负责初始化 DB 层)
  • init_db_config_read.php (负责从数据库中读取配置数据)
  • init_file_db_names.php (负责加载文件和数据库表名定义)
  • init_general_funcs.php (负责加载位于 includes/functions 和 extra_functions 目录中的通用函数)
  • init_gzip.php (负责加载 Gzip output-buffering 函数)
  • init_header.php (负责运行页面头部进程)
  • init_languages.php (负责加载多语言支持子系统)
  • init_sanitize.php (负责加载输入消毒代码)
  • init_sefu.php (负责加载提供搜索引擎友好 URL 的代码)
  • init_sessions.php (负责加载 Session 代码)
  • init_special_funcs.php (负责加载特殊但是必须的函数)
  • init_templates.php (负责初始化模板系统和激活模板相关的语言内容定义)
  • init_tlds.php (负责设置顶级域名变量)

1.1.6.3 重写 init_scripts

重写一个 init 脚本是很简单的。includes/init_includes 目录包括了一个名为 overrides 的目录。如果我想要重写 includes/init_includes/init_sessions.php 脚本,那么我只是简单的在 includes/init_includes/overrides 目录中创建一个名为 init_sessions.php 的文件就行了。

1.1.7 在 application_top.php 文件中的过程代码

除了应用自动加载器系统,在 application_top.php 文件中仍然残留了一小撮过程代码(procedural code,和面向对象的代码相对);虽然大部分的过程代码已经让位给处理自动加载器本身。

以下是前台 includes/application_top.php 文件中的代码。注意:我已经移除了文档标签以便浏览:

 

  1. define( 'DEBUG_AUTOLOAD' , false);  
  2. define('IS_ADMIN_FLAG' , false);  
  3. define('PAGE_PARSE_START_TIME' , microtime());  
  4. @ini_set ( "arg_separator.output" , "&" );  
  5. if  ( file_exists ( 'includes/local/configure.php' )) {  
  6.   include ( 'includes/local/configure.php' );  
  7. }  
  8. if  (defined( 'STRICT_ERROR_REPORTING' ) && STRICT_ERROR_REPORTING == true) {  
  9.   error_reporting (E_ALL);  
  10. else  {  
  11.   error_reporting (E_ALL & ~E_NOTICE);  
  12. }  
  13. if  ( file_exists ( 'includes/configure.php' )) {  
  14.   include ( 'includes/configure.php' );  
  15. else  {  
  16.   header('location: zc_install/index.php' );  
  17. }  
  18. if  (! is_dir (DIR_FS_CATALOG. '/includes/classes' ))  header( 'location: zc_install/index.php' );  
  19. if  ( $za_dir  = @dir(DIR_WS_INCLUDES .  'extra_configures' )) {  
  20.   while  ( $zv_file  =  $za_dir ->read()) {  
  21.     if  (preg_match( '//.php$/'$zv_file ) > 0) {  
  22.       include (DIR_WS_INCLUDES .  'extra_configures/'  .  $zv_file );  
  23.     }  
  24.   }  
  25. }   
  26. $loader_file  =  'config.core.php' ;  
  27. $base_dir  = DIR_WS_INCLUDES .  'auto_loaders/' ;  
  28. if  ( file_exists (DIR_WS_INCLUDES .  'auto_loaders/overrides/'  .  $loader_file )) {  
  29.   $base_dir  = DIR_WS_INCLUDES .  'auto_loaders/overrides/' ;  
  30. }  
  31. include ( $base_dir  .  $loader_file );  
  32. if  ( $loader_dir  = dir(DIR_WS_INCLUDES .  'auto_loaders' )) {  
  33.   while  ( $loader_file  =  $loader_dir ->read()) {  
  34.     if  ((preg_match( '/^config/./'$loader_file ) > 0) && (preg_match( '//.php$/'$loader_file ) > 0)) {  
  35.       if  ( $loader_file  !=  'config.core.php' ) {  
  36.         $base_dir  = DIR_WS_INCLUDES .  'auto_loaders/' ;  
  37.         if  ( file_exists (DIR_WS_INCLUDES .  'auto_loaders/overrides/'  .  $loader_file )) {  
  38.           $base_dir  = DIR_WS_INCLUDES .  'auto_loaders/overrides/' ;  
  39.         }  
  40.         include ( $base_dir  .  $loader_file );  
  41.       }  
  42.     }  
  43.   }  
  44. }  
  45. if  (( (! file_exists ( 'includes/configure.php' ) && ! file_exists ( 'includes/local/configure.php' )) ) || (DB_TYPE == ) ||    (! file_exists ( 'includes/classes/db/'  .DB_TYPE .  '/query_factory.php' ))) {  
  46.   header('location: zc_install/index.php' );  
  47.   exit ;  
  48. }  
  49. require ( 'includes/autoload_func.php' );  
  50. require (DIR_WS_INCLUDES .  'counter.php' );  
  51. $customers_ip_address  =  $_SERVER [ 'REMOTE_ADDR' ];  
  52. if  (!isset( $_SESSION [ 'customers_ip_address' ])) {  
  53.   $_SESSION [ 'customers_ip_address' ] =  $customers_ip_address ;  
  54. }  

1.2 观察者类

1.2.1 介绍

一直以来,Zen Cart 项目众多目标中的一个是,让第三方开发者通过一个简单而且优雅的方式来给核心代码添加功能。在过去,我们为了达到这个,依靠重写和自动包括系统。然后这些仍然不能给开发者一个简单的方式来与核心代码的许多领域挂勾,除非是破解核心文件。

观察者/通知者系统被引入,让开发者空前的访问核心代码,而不需要过多的触动任何核心文件。虽然表面上是为一个面向对象代码为基础,我们会在稍后看到它是如何同时与过程代码同时使用。

1.2.2 扩展全部类

为了应用观察者/通知者系统,Zen Cart 的一些结构作了改变。首先引入了两个新的类:基类(class.base.php)和通知者类(class.notifier.php)。

基类包括了用来应用观察者/通知者系统的代码。然而为了使它作用 Zen Cart 的所有其它类,现在不得不被声明为基类的子类。如果你看一下 Zen Cart 源代码的类文件,你会发现:

 

  1. class  currencies  extends  base {  

通知者类将会在稍后讨论,当我们把扩展观察者/通知者系统(ONS)看成是过程编码的时候。

1.2.3 通知者:老大哥正在看着你

那么,这么小题大做是为了什么?

ONS 的关键点是开发者可以写代码,等待一定的事件发生,然后当发生的时候,执行他们自己的代码。

那么,事件是如何定义,它们在哪被触发?

事件由添加到 v1.3 核心的代码触发。在任何一个我们想通知一个事件发生的类中,我们添加:

 

  1. $this ->notify( 'EVENT_NAME' );  

下面的例子可能有用:

在购物车类,当一件商品被添加到购物车后,这个事件触发了:

 

  1. $this ->notify( 'NOTIFIER_CART_ADD_CART_END' );  

在 Zen Cart v1.3 版本中许多事件都拥有通知者,这个是清单

这些通知很好,那么如何帮助开发者呢?

1.2.4 观察和壮大

为了利用通知者,开发者需要写一些代码来观察它们。观察者需要被写成一个类。有一个好的目录,includes/classes/observers,开发者可以在这放置这些类。

让我们看一个例子。使用上面提到的通知者(NOTIFIER_CART_ADD_CART_END),我如何写一个类来观察那个事件?

 

  1. <?php  
  2. class  myObserver  extends  base {  
  3.   function  myObserver() {  
  4.     $this ->attach( $thisarray ( 'NOTIFIER_CART_ADD_CART_END' ));  
  5.   }  
  6. ..  
  7. }  

正如你看到的,我们已经定义了一个新的名为 myObserver 的类,在它的构造函数(function myObserver)已经把 myObserver 这个类附加到 NOTIFIER_CART_ADD_CART_END 这个事件中。

“不错,”我听到你说,“但是我怎么样才能做点有用的事情?”

OK,问得好。当一个事件发生的时候,基类会查看,是否有任意的其它观察者类在观察这个事件。如果有,那么基类会执行那个观察者类中的一个方法。还 记得上面提到的 $this->notify('EVENT_NAME')吗?好的,当那个事件发生,基类会调用所有观察者的 update 方法。让我们看更多的代码:

 

  1. class  myObserver  extends  base {  
  2.   function  myObserver() {  
  3.     $this ->attach( $thisarray ( 'NOTIFIER_CART_ADD_CART_END' ));  
  4.   }  
  5.   function  update(& $callingClass$notifier$paramsArray ) {  
  6.     ... do  some stuff  
  7.   }  
  8. }  

现在,当 NOTIFIER_CART_ADD_CART_END 发生的时候,我们的 myOberserv::update 方法将会被执行。注意 attach() 可能会被作任何你想监听的类的一个方法被调用($_SESSION['cart'],在本例中)或者被类的内部变量 $this 调用。两者都可行,因为它们都是基类的一部分,attach 方法存在的地方。

关于参数需要注意。attach 方法有两个参数:

  • &$observer - 观察者类的引用,用来为一个新的监听者产生一个独一无二的 ID
  • $eventIDArray - 一个通知者数组,这些通知者正在被这个观察者监听

update 方法可以传递三个参数,它们是:

  • &$callingClass - 这是对类的一个引用,事件在这些类中发生,允许你访问那个类的变量
  • $notifier - 触发 update 的通知者名字(观察多个通知者是明显可行的)
  • $paramsArray - 还未使用(将来或许会用到)

注意!观察者/通知者系统是为一个面向对象的应用程序而写的, 因为观察者被期待附加到一个在它的方法内拥有通知者的类中。然而,在 Zen Cart 中许多的代码仍然面向过程的,不包括在一个类中。

为了正常运转,我们添加了“stub”通知者类。所以如果你想为在过程代码中的一个通知者创建一个观察者(例如在页面头部),你应该这样把通知者添加到你的 myObserver 类中:

 

  1. class  myObserver  extends  base {  
  2.   function  myObserver() {  
  3.     global   $zco_notifier ;  
  4.     $zco_notifier ->attach( $thisarray ( 'NOTIFY_HEADER_END_CHECKOUT_CONFIRMATION' ));  
  5.   }  

1.2.5 在你的代码中包括进观察者

请注意 includes/classes/observers 目录不是一个自动加载目录,所以你将需要安排 application_top.php 来自动加载你的观察者类,如同上面所述(在 auto_loaders 目录中添加一个新的 config.xxxxx.php 文件,诸如此类)。让我们假设你正在使用 freeProduct 类(看下面的例子),你已经把它保存到 includes/classes/observers/class.freeProduct.php 文件。

你现在需要为这个类安排从而加载和实例化它。为了达到这个目的,你需要使用 appliction_top.php 自动加载系统。

在 includes/auto_loaders 创建一个名为 config.freeProduct.php 的文件,包含以下内容:

 

  1. <?php  
  2. $autoLoadConfig [10][] =  array ( 'autoType' => 'class' ,  
  3.                               'loadFile' => 'observers/class.freeProduct.php' );  
  4. $autoLoadConfig [90][] =  array ( 'autoType' => 'classInstantiate' ,  
  5.                               'className' => 'freeProduct' ,  
  6.                               'objectName' => 'freeProduct' );  
  7. ?>   

注意:已经选择10来引发这个观察者类在 session 开始之前加载的。注意:已经选择90作为从观察者需要附加到 $_SESSION['cart'] 类的偏移量(见下面的 freeProduct 例子),而 $_SESSION['cart'] 被实例化的位置是80。

把这些组合起来,让我们看一下真实的例子。

1.2.6 一个真实的例子

被要求众多的一个特性是:当客户消费超过一定金额的时候,商店可以自动添加一件免费赠品。

这个代码应该是智能的:它不仅可以在客户消费达到一定金额的时候添加免费赠品,同时也可以在客户改变购物车内容,消费金额在标准之下的时候,移除免费赠品。

传统的,虽然这个代码不是特别困难,它却意味要在 shoppingCart 类中的许多地方需要破解。通过 ONS,这将通过一个十分小的定制类来完成,同时不会造成任何的破坏。

以下是代码:

 

  1. <?php  
  2. /**  
  3.  * Observer class used to add a free product to the cart if the user spends more than $x  
  4.  *  
  5.  */   
  6. class  freeProduct  extends  base {  
  7.   /**  
  8.    * The threshold amount the customer needs to spend.  
  9.    *   
  10.    * Note this is defined in the shops base currency, and so works with multi currency shops  
  11.    *  
  12.    * @var decimal  
  13.    */   
  14.   var   $freeAmount  = 50;  
  15.   /**  
  16.    * The id of the free product.  
  17.    *   
  18.    * Note. This must be a true free product. e.g. price = 0 Also make sure that if you dont want the customer  
  19.    * to be charged shipping on this, that you have it set correctly.  
  20.    *  
  21.    * @var integer  
  22.    */   
  23.   var   $freeProductID  = 57;  
  24.   /**  
  25.    * constructor method  
  26.    *   
  27.    * Attaches our class to the $_SESSION['cart'] class and watches for 2 notifier events.  
  28.    */   
  29.   function  freeProduct() {  
  30.     $_SESSION [ 'cart' ]->attach( $thisarray ( 'NOTIFIER_CART_ADD_CART_END''NOTIFIER_CART_REMOVE_END' ));  
  31.   }  
  32.   /**  
  33.    * Update Method  
  34.    *   
  35.    * Called by observed class when any of our notifiable events occur  
  36.    *  
  37.    * @param object $class  
  38.    * @param string $eventID  
  39.    */   
  40.   function  update(& $class$eventID$paramsArray  =  array ()) {  
  41.   if  ( $eventID  ==  'NOTIFIER_CART_REMOVE_END'  && (isset( $_SESSION [ 'freeProductInCart' ]) &&  $_SESSION [ 'freeProductInCart' ] == TRUE ))  
  42.   {  
  43.     if  (! $_SESSION [ 'cart' ]->in_cart( $this ->freeProductID))  
  44.     {  
  45.       $_SESSION [ 'userRemovedFreeProduct' ] = TRUE;  
  46.     }  
  47.   }  
  48.   if  (!isset( $_SESSION [ 'userRemovedFreeProduct' ]) ||  $_SESSION [ 'userRemovedFreeProduct' ] != TRUE)   
  49.   {  
  50.     if  ( $_SESSION [ 'cart' ]->show_total() >=  $this ->freeAmount && ! $_SESSION [ 'cart' ]->in_cart( $this ->freeProductID) )     
  51.     {  
  52.       $_SESSION [ 'cart' ]->add_cart( $this ->freeProductID);  
  53.       $_SESSION [ 'freeProductInCart' ] = TRUE;    
  54.     }  
  55.   }  
  56.    
  57.   if  ( $_SESSION [ 'cart' ]->show_total() <  $this ->freeAmount &&  $_SESSION [ 'cart' ]->in_cart( $this ->freeProductID) )   
  58.   {  
  59.     $_SESSION [ 'cart' ]->remove( $this ->freeProductID);  
  60.   }  
  61.   if  ( $_SESSION [ 'cart' ]->in_cart( $this ->freeProductID))   
  62.   {  
  63.     $_SESSION [ 'cart' ]->contents[ $this ->freeProductID][ 'qty' ] = 1;  
  64.   }  
  65.      
  66.   }    
  67. }  
  68. ?>  

需要注意的几点:

首先,在类本身,我已经为系统设置了选项。这显然是一个坏的主意,通过一个后台模块来设置这些选项无疑要好许多。

其次,注意我们事实是在一个类中观察两个事件。

 

  1. $_SESSION [ 'cart' ]->attach( $thisarray ( 'NOTIFIER_CART_ADD_CART_END''NOTIFIER_CART_REMOVE_END' ));  

所以我们正在观察 shopping_cart 类的 NOTIFIER_CART_ADD_CART_END 和 NOTIFIER_CART_REMOVE_END。

update 类是十分简单的,但是在它简单的管理中,需要完成我们要求它的工作。它首先是检查看购物车的总金额是否已经达到标准,如果还没有,添加免费赠品到购物车中。

然后它检查购物车的总金额是否在标准之下,如果免费赠品在购物车内,移除它。

这很酷,再增加点难度如何?

1.2.7 另外一个真实的例子

我们再次回到购物车和促销。另外一个经常要求的特性是 BOGOF 促销,或者买一送一。要达到这个目标比之前的例子要稍微难一些,因为购物车总额需要执行一些操作。然而你将看到这还是轻而易举的。

 

  1. <?php  
  2. /**  
  3.  * Observer class used apply a Buy One Get One Free(bogof) algorithm to the cart  
  4.  *  
  5.  */   
  6. class  myBogof  extends  base {  
  7.   /**  
  8.    * an array of ids of products that can be BOGOF.  
  9.    *  
  10.    * @var array  
  11.    */   
  12.   var   $bogofsArray  =  array (10,4);  //Under Siege2-Dark Territory & The replacement Killers   
  13.   /**  
  14.    * Integer number of bogofs allowed per product  
  15.    *   
  16.    * For example if I add 4 items of product 10, that would suggest that I pay for 2 and get the other 2 free.  
  17.    * however you may want to restrict the customer to only getting 1 free regardless of the actual quantity  
  18.    *  
  19.    * @var integer  
  20.    */   
  21.   var   $bogofsAllowed  = 1;  
  22.   /**  
  23.    * constructor method  
  24.    *   
  25.    * Watches for 1 notifier event, triggered from the shopping cart class.  
  26.    */   
  27.   function  myBogof() {  
  28.     $this ->attach( $thisarray ( 'NOTIFIER_CART_SHOW_TOTAL_END' ));  
  29.   }  
  30.   /**  
  31.    * Update Method  
  32.    *   
  33.    * Called by observed class when any of our notifiable events occur  
  34.    *   
  35.    * This is a bit of a hack, but it works.   
  36.    * First we loop through each product in the bogof Array and see if that product is in the cart.  
  37.    * Then we calculate the number of free items. As it is buy one get one free, the number of free items  
  38.    * is equal to the total quantity of an item/2.  
  39.    * 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)  
  40.    * We loop thru the cart until we find the bogof item, get its final price, calculate the saving  
  41.    * and adjust the cart total accordingly.  
  42.    *  
  43.    * @param object $class  
  44.    * @param string $eventID  
  45.    */   
  46.   function  update(& $class$eventID ) {  
  47.     $cost_saving  = 0;  
  48.     $products  =  $_SESSION [ 'cart' ]->get_products();  
  49.     foreach  ( $this ->bogofsArray  as   $bogofItem ) {  
  50.       if  ( $_SESSION [ 'cart' ]->in_cart( $bogofItem )) {  
  51.         if  (isset( $_SESSION [ 'cart' ]->contents[ $bogofItem ][ 'qty' ]) &&  $_SESSION [ 'cart' ]->contents[ $bogofItem ][ 'qty' ] > 1) {  
  52.           $numBogofs  =  floor ( $_SESSION [ 'cart' ]->contents[ $bogofItem ][ 'qty' ] / 2);  
  53.           if  ( $numBogofs  >  $this ->bogofsAllowed)  $numBogofs  =  $this ->bogofsAllowed;  
  54.           if  ( $numBogofs  > 0) {  
  55.             for  ( $i =0,  $n =sizeof( $products );  $i < $n$i ++) {  
  56.               if  ( $products [ $i ][ 'id' ] ==  $bogofItem ) {  
  57.                 $final_price  =  $products [ $i ][ 'final_price' ];  
  58.                 break ;  
  59.               }  
  60.             }  
  61.             $cost_saving  .=  $final_price  *  $numBogofs ;  
  62.           }  
  63.         }  
  64.       }  
  65.     }  
  66.     $_SESSION [ 'cart' ]->total -=  $cost_saving ;  
  67.   }  
  68. }  
  69. ?>  

注意:这仍然有一些漏洞。

首先,虽然现在总额的变动在购物车页面和边框被显示,line total 没有被调整。

其次,这可能会在结账的时候产生输出混乱。

第三,没有和税结合测试过(@TODO)。

Notifiers currently set in Zen Cart

Notifier points for Zen Cart 1.3.7

Shopping Cart class
  • NOTIFIER_CART_INSTANTIATE_START
  • NOTIFIER_CART_INSTANTIATE_END
  • NOTIFIER_CART_RESTORE_CONTENTS_START
  • NOTIFIER_CART_RESTORE_CONTENTS_END
  • NOTIFIER_CART_RESET_START
  • NOTIFIER_CART_RESET_END
  • NOTIFIER_CART_ADD_CART_START
  • NOTIFIER_CART_ADD_CART_END
  • NOTIFIER_CART_UPDATE_QUANTITY_START
  • NOTIFIER_CART_UPDATE_QUANTITY_END
  • NOTIFIER_CART_CLEANUP_START
  • NOTIFIER_CART_CLEANUP_END
  • NOTIFIER_CART_COUNT_CONTENTS_START
  • NOTIFIER_CART_COUNT_CONTENTS_END
  • NOTIFIER_CART_GET_QUANTITY_START
  • NOTIFIER_CART_GET_QUANTITY_END_QTY
  • NOTIFIER_CART_GET_QUANTITY_END_FALSE
  • NOTIFIER_CART_IN_CART_START
  • NOTIFIER_CART_IN_CART_END_TRUE
  • NOTIFIER_CART_IN_CART_END_FALSE
  • NOTIFIER_CART_REMOVE_START
  • NOTIFIER_CART_REMOVE_END
  • NOTIFIER_CART_REMOVE_ALL_START
  • NOTIFIER_CART_REMOVE_ALL_END
  • NOTIFIER_CART_GET_PRODUCTS_START
  • NOTIFIER_CART_GET_PRODUCTS_END
  • NOTIFIER_CART_SHOW_TOTAL_START
  • NOTIFIER_CART_SHOW_TOTAL_END
  • NOTIFY_CART_USER_ACTION
  • NOTIFY_HEADER_START_SHOPPING_CART
  • NOTIFY_HEADER_END_SHOPPING_CART

Order Class:
  • NOTIFY_ORDER_PROCESSING_STOCK_DECREMENT_BEGIN
  • NOTIFY_ORDER_PROCESSING_STOCK_DECREMENT_END
  • NOTIFY_ORDER_PROCESSING_CREDIT_ACCOUNT_UPDATE_BEGIN
  • NOTIFY_ORDER_PROCESSING_ATTRIBUTES_BEGIN
  • NOTIFY_ORDER_PROCESSING_ONE_TIME_CHARGES_BEGIN

Email:
  • NOTIFY_EMAIL_AFTER_EMAIL_FORMAT_DETERMINED
  • NOTIFY_EMAIL_BEFORE_PROCESS_ATTACHMENTS
  • NOTIFY_EMAIL_AFTER_PROCESS_ATTACHMENTS
  • NOTIFY_EMAIL_AFTER_SEND (individual email)
  • NOTIFY_EMAIL_AFTER_SEND_ALL_SPECIFIED_ADDRESSES (full batch)

Modules:
  • NOTIFY_MODULE_START_META_TAGS
  • NOTIFY_MODULE_END_META_TAGS
  • NOTIFY_MODULE_START_CREATE_ACCOUNT
  • NOTIFY_FAILURE_DURING_CREATE_ACCOUNT
  • NOTIFY_LOGIN_SUCCESS_VIA_CREATE_ACCOUNT
  • NOTIFY_MODULE_END_CREATE_ACCOUNT

Checkout:
  • NOTIFY_CHECKOUT_PROCESS_BEGIN
  • NOTIFY_CHECKOUT_PROCESS_BEFORE_ORDER_TOTALS_PRE_CONFIRMATION_CHECK
  • NOTIFY_CHECKOUT_PROCESS_BEFORE_ORDER_TOTALS_PROCESS
  • NOTIFY_CHECKOUT_PROCESS_AFTER_ORDER_TOTALS_PROCESS
  • NOTIFY_CHECKOUT_PROCESS_AFTER_PAYMENT_MODULES_BEFOREPROCESS
  • NOTIFY_CHECKOUT_PROCESS_AFTER_ORDER_CREATE
  • NOTIFY_CHECKOUT_PROCESS_AFTER_PAYMENT_MODULES_AFTER_ORDER_CREATE
  • NOTIFY_CHECKOUT_PROCESS_AFTER_ORDER_CREATE_ADD_PRODUCTS
  • NOTIFY_CHECKOUT_PROCESS_AFTER_SEND_ORDER_EMAIL
  • NOTIFY_CHECKOUT_PROCESS_HANDLE_AFFILIATES
  • NOTIFY_HEADER_START_CHECKOUT_CONFIRMATION
  • NOTIFY_HEADER_END_CHECKOUT_CONFIRMATION
  • NOTIFY_HEADER_START_CHECKOUT_PAYMENT
  • NOTIFY_HEADER_END_CHECKOUT_PAYMENT
  • NOTIFY_HEADER_START_CHECKOUT_PAYMENT_ADDRESS
  • NOTIFY_HEADER_END_CHECKOUT_PAYMENT_ADDRESS
  • NOTIFY_HEADER_START_CHECKOUT_PROCESS
  • NOTIFY_HEADER_END_CHECKOUT_PROCESS
  • NOTIFY_HEADER_START_CHECKOUT_SHIPPING
  • NOTIFY_HEADER_END_CHECKOUT_SHIPPING
  • NOTIFY_HEADER_START_CHECKOUT_SHIPPING_ADDRESS
  • NOTIFY_HEADER_END_CHECKOUT_SHIPPING_ADDRESS
  • NOTIFY_HEADER_START_CHECKOUT_SUCCESS
  • NOTIFY_HEADER_END_CHECKOUT_SUCCESS
  • NOTIFY_MODULE_START_CHECKOUT_NEW_ADDRESS
  • NOTIFY_MODULE_END_CHECKOUT_NEW_ADDRESS

Individual Pages (Header scripts):
  • NOTIFY_HEADER_START_ACCOUNT
  • NOTIFY_HEADER_END_ACCOUNT
  • NOTIFY_HEADER_START_ACCOUNT_EDIT
  • NOTIFY_HEADER_ACCOUNT_EDIT_UPDATES_COMPLETE
  • NOTIFY_HEADER_END_ACCOUNT_EDIT
  • NOTIFY_HEADER_START_ACCOUNT_HISTORY
  • NOTIFY_HEADER_END_ACCOUNT_HISTORY
  • NOTIFY_HEADER_START_ACCOUNT_HISTORY_INFO
  • NOTIFY_HEADER_END_ACCOUNT_HISTORY_INFO
  • NOTIFY_HEADER_START_ACCOUNT_NOTIFICATION
  • NOTIFY_HEADER_END_ACCOUNT_NOTIFICATION
  • NOTIFY_HEADER_START_ACCOUNT_PASSWORD
  • NOTIFY_HEADER_END_ACCOUNT_PASSWORD
  • NOTIFY_HEADER_START_ADDRESS_BOOK
  • NOTIFY_HEADER_END_ADDRESS_BOOK
  • NOTIFY_HEADER_START_ADDRESS_BOOK_PROCESS
  • NOTIFY_HEADER_ADDRESS_BOOK_DELETION_DONE
  • NOTIFY_HEADER_ADDRESS_BOOK_ENTRY_UPDATE_DONE
  • NOTIFY_HEADER_ADDRESS_BOOK_ADD_ENTRY_DONE
  • NOTIFY_HEADER_END_ADDRESS_BOOK_PROCESS
  • NOTIFY_HEADER_START_CREATE_ACCOUNT
  • NOTIFY_FAILURE_DURING_CREATE_ACCOUNT
  • NOTIFY_LOGIN_SUCCESS_VIA_CREATE_ACCOUNT
  • NOTIFY_HEADER_END_CREATE_ACCOUNT
  • NOTIFY_HEADER_START_CREATE_ACCOUNT_SUCCESS
  • NOTIFY_HEADER_END_CREATE_ACCOUNT_SUCCESS
  • NOTIFY_MAIN_TEMPLATE_VARS_START_DOCUMENT_GENERAL_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_PRODUCT_TYPE_VARS_DOCUMENT_GENERAL_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_EXTRA_DOCUMENT_GENERAL_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_END_DOCUMENT_GENERAL_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_START_DOCUMENT_GENERAL_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_END_DOCUMENT_GENERAL_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_START_DOCUMENT_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_PRODUCT_TYPE_VARS_DOCUMENT_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_EXTRA_DOCUMENT_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_END_DOCUMENT_PRODUCT_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_START_DOCUMENT_PRODUCT_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_END_DOCUMENT_PRODUCT_INFO
  • NOTIFY_HEADER_START_DOWNLOAD
  • NOTIFY_DOWNLOAD_VIA_SYMLINK___BEGINS
  • NOTIFY_DOWNLOAD_WITHOUT_REDIRECT___COMPLETED
  • NOTIFY_DOWNLOAD_WITHOUT_REDIRECT_VIA_CHUNKS___COMPLETED
  • NOTIFY_HEADER_END_DOWNLOAD
  • NOTIFY_HEADER_START_GV_FAQ
  • NOTIFY_HEADER_END_GV_FAQ
  • NOTIFY_HEADER_START_GV_SEND
  • NOTIFY_HEADER_END_GV_SEND
  • NOTIFY_HEADER_START_INDEX
  • NOTIFY_HEADER_END_INDEX
  • NOTIFY_HEADER_START_INDEX_MAIN_TEMPLATE_VARS
  • NOTIFY_HEADER_INDEX_MAIN_TEMPLATE_VARS_RELEASE_PRODUCT_TYPE_VARS
  • NOTIFY_HEADER_END_INDEX_MAIN_TEMPLATE_VARS
  • NOTIFY_HEADER_START_LOGIN
  • NOTIFY_LOGIN_SUCCESS
  • NOTIFY_LOGIN_FAILURE
  • NOTIFY_HEADER_END_LOGIN
  • NOTIFY_HEADER_START_LOGOFF
  • NOTIFY_HEADER_END_LOGOFF
  • NOTIFY_HEADER_START_EZPAGE
  • NOTIFY_HEADER_END_EZPAGE
  • NOTIFY_HEADER_START_PAGE_NOT_FOUND
  • NOTIFY_HEADER_END_PAGE_NOT_FOUND
  • NOTIFY_HEADER_START_PASSWORD_FORGOTTEN
  • NOTIFY_HEADER_END_PASSWORD_FORGOTTEN
  • NOTIFY_MAIN_TEMPLATE_VARS_START_PRODUCT_FREE_SHIPPING_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_PRODUCT_TYPE_VARS_PRODUCT_FREE_SHIPPING_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_EXTRA_PRODUCT_FREE_SHIPPING_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_END_PRODUCT_FREE_SHIPPING_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_START_PRODUCT_FREE_SHIPPING_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_END_PRODUCT_FREE_SHIPPING_INFO
  • NOTIFY_HEADER_START_PRODUCT_INFO
  • NOTIFY_HEADER_END_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_START_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_PRODUCT_TYPE_VARS_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_EXTRA_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_END_PRODUCT_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_START_PRODUCT_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_END_PRODUCT_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_START_PRODUCT_MUSIC_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_PRODUCT_TYPE_VARS_PRODUCT_MUSIC_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_EXTRA_PRODUCT_MUSIC_INFO
  • NOTIFY_MAIN_TEMPLATE_VARS_END_PRODUCT_MUSIC_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_START_PRODUCT_MUSIC_INFO
  • NOTIFY_PRODUCT_TYPE_VARS_END_PRODUCT_MUSIC_INFO
  • NOTIFY_HEADER_START_SITE_MAP
  • NOTIFY_HEADER_END_SITE_MAP
  • NOTIFY_HEADER_START_UNSUBSCRIBE
  • NOTIFY_HEADER_END_UNSUBSCRIBE

PayPal?:
  • NOTIFY_PAYMENT_PAYPAL_RETURN_TO_STORE
  • NOTIFY_PAYMENT_PAYPAL_CANCELLED_DURING_CHECKOUT
  • NOTIFY_PAYMENT_PAYPAL_INSTALLED
  • NOTIFY_PAYMENT_PAYPAL_UNINSTALLED

Sideboxes:
  • NOTIFY_SIDEBOX_START_EZPAGES_SIDEBOX
  • NOTIFY_SIDEBOX_END_EZPAGES_SIDEBOX
  • NOTIFY_HEADER_START_EZPAGES_HEADER
  • NOTIFY_HEADER_END_EZPAGES_HEADER
  • NOTIFY_FOOTER_START_EZPAGES_FOOTER
  • NOTIFY_FOOTER_END_EZPAGES_FOOTER

Search:
  • NOTIFY_HEADER_START_ADVANCED_SEARCH_RESULTS
  • NOTIFY_SEARCH_COLUMNLIST_STRING
  • NOTIFY_SEARCH_SELECT_STRING
  • NOTIFY_SEARCH_FROM_STRING
  • NOTIFY_SEARCH_WHERE_STRING
  • NOTIFY_SEARCH_ORDERBY_STRING
  • NOTIFY_HEADER_END_ADVANCED_SEARCH_RESULTS

EZ-Pages:
  • NOTIFY_START_EZPAGES_FOOTERBAR
  • NOTIFY_END_EZPAGES_FOOTERBAR
  • NOTIFY_START_EZPAGES_HEADERBAR
  • NOTIFY_END_EZPAGES_HEADERBAR
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值