Zen Cart API 教程
2010-10-19 08:56
204 查看
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
的主要文件。第三方开发者可添加他们自己的控制数组文件。每个文件的结构看起来应该是这样的:
view plain
copy to clipboard
?
$autoLoadConfig
[0] =
array
();
$autoLoadConfig[0] = array();
在 $autoLoadConfig 后面的值(本例中是[0])代表动作发生的顺序(也就是断点),这样一来
$autoLoadConfig[0] 将会在 $autoLoadConfig[1]
之前发生。同时注意断点相同的任意两个条目将会以它们在文件中出现的先后顺序发生。array()
部分的内容取决于需要达到的效果。让我们设想一些不同的场景。
首先,我只想包括一个需要加载的文件。为此,控制数组条目将会是:
view plain
copy to clipboard
?
$autoLoadConfig
[0][] =
array
(
'autoType'
=>
'require'
,
'loadFile'
=> DIR_WS_INCLUDES .
'somefile.php'
);
$autoLoadConfig[0][] = array( 'autoType' => 'require', 'loadFile' => DIR_WS_INCLUDES . 'somefile.php' );
autoType 参数告诉我们,我们只想包括一个文件。
loadFile 参数告诉我们,我们要加载的是哪个文件。
加载函数文件显然可以通过以上方法来做到。
类似的,如果我们希望“include”一个文件:
view plain
copy to clipboard
?
$autoLoadConfig
[0][] =
array
(
'autoType'
=>
'include'
,
'loadFile'
=> DIR_WS_INCLUDES .
'somefile.php'
);
$autoLoadConfig[0][] = array( 'autoType' => 'include', 'loadFile' => DIR_WS_INCLUDES . 'somefile.php' );
我们于是有了一个特殊的“require”类型。initSystem 引入了一个名为 init_scripts 的特殊类的 .php
文件。这些文件保存在 includes/init_includes 目录中。每一个这样的文件包含有少量的可以作为 initSystem
进程的一部分运行的过程编码。把它们分离进一个特殊目录的原因是,可以允许重写那些
init_scripts,这将会在稍后进一步探讨。现在,为了加载一个 init_script 我们使用以下的控制数组结构:
view plain
copy to clipboard
?
$autoLoadConfig
[] =
array
(
array
(
'autoType'
=>
'init_script'
,
'loadFile'
=>
'init_database.php'
)
);
$autoLoadConfig[] = array( array( 'autoType' => 'init_script', 'loadFile' => 'init_database.php' ) );
。。。。。。。通过一个类文件,我们想加载类文件定义,然后实例化这个类,最后可能运行这个类的一个方法(所有这样运行的,都在 application_top.php 文件范围内)。
通过控制数组,我们有以下的条目来帮助我们。
view plain
copy to clipboard
?
$autoLoadConfig
[0][] =
array
(
'autoType'
=>
'class'
,
'loadFile'
=>
'shopping_cart.php'
);
$autoLoadConfig
[30][] =
array
(
'autoType'
=>
'classInstantiate'
,
'className'
=>
'cache'
,
'objectName'
=>
'zc_cache'
);
$autoLoadConfig
[80][] =
array
(
'autoType'
=>
'classInstantiate'
,
'className'
=>
'shoppingCart'
,
'objectName'
=>
'cart'
,
'checkInstantiated'
=>true,
'classSession'
=>true);
$autoLoadConfig
[120][] =
array
(
'autoType'
=>
'objectMethod'
,
'objectName'
=>
'navigation'
,
'methodName'
=>
'add_current_page'
);
$autoLoadConfig[0][] = array('autoType'=>'class', 'loadFile'=>'shopping_cart.php'); $autoLoadConfig[30][] = array('autoType'=>'classInstantiate', 'className'=>'cache', 'objectName'=>'zc_cache'); $autoLoadConfig[80][] = array('autoType'=>'classInstantiate', 'className'=>'shoppingCart', 'objectName'=>'cart', 'checkInstantiated'=>true, 'classSession'=>true); $autoLoadConfig[120][] = array('autoType'=>'objectMethod', 'objectName'=>'navigation', 'methodName' => 'add_current_page');
逐一分析以上选项。
autoType => 'class' 我们在这做的是包括进一个“loadFile”。然后,在本例中我们从 includes/classed (DIR_WS_CLASS)目录提取文件。
autoType => 'classInstantiate' 执行这样形式的代码:objectName = new className();。
根据以上的代码,得到的一个例子:
view plain
copy to clipboard
?
$zc_cache
=
new
cache();
$zc_cache = new cache();
由此推论,我们可能需要实例化一个与 session 相关的类。例如 shopping_cart 类。从上面的例子中,我们可以得到:
view plain
copy to clipboard
?
$_SESSION
[
'cart'
] =
new
shoppingCart();
$_SESSION['cart'] = new shoppingCart();
事实上我们更进一步,通常来说我们只想实例化一个 session 对象,如果它还不是一个 session 对象。这种情况下,我们使用了“checkInstantiated”属性,这将会生成以下代码:
view plain
copy to clipboard
?
if
(!
$_SESSION
[
'cart'
]) {
$_SESSION
[
'cart'
] =
new
shoppingCart();
}
if (!$_SESSION['cart']) { $_SESSION['cart'] = new shoppingCart(); }
最后的例子,autoType => 'objectMethod' 展示了如何在 application_top.php 文件中运行一个类的方法。当写下本教程的时候,还没有传递方法参数的规定。所以产生的代码将会是(基于以上例子):
view plain
copy to clipboard
?
$navigation
-> add_current_page();
$navigation -> add_current_page();
1.1.4.1 关于后台自动加载器的注解
v1.3.x 的目标是最终把所有的函数移动和重构进类。更进一步,这些类将会被后台和前台代码通用。然而这将带来一个问题:自动加载类文件。现在的自动加载器的代码,将会默认的加载来自前台 includes/classes 的类,而不是 admin/includes/classes。
为了从 admin/includes/classes 目录加载后台类文件,在现在这个过渡时期,我们为 'autoType' => class 提供了一个额外的选项。
设想这个:
view plain
copy to clipboard
?
$autoLoadConfig
[0][] =
array
(
'autoType'
=>
'class'
,
'loadFile'
=>
'class.base.php'
);
$autoLoadConfig[0][] = array('autoType' => 'class', 'loadFile' => 'class.base.php');
如果这用在一个后台自动加载器中,它会加载来自前台类目录的类。对于基类而言,这很好,因为代码可以在前台和后台分享。然而,split_page_results_class 在前台和后台是不同的。所以为了加载一个后台类,我们使用:
view plain
copy to clipboard
?
$autoLoadConfig
[0][] =
array
(
'autoType'
=>
'class'
,
'loadFile'
=>
'splite_page_results.php'
,
'classPath'
=> DIR_WS_CLASSES);
$autoLoadConfig[0][] = array('autoType' => 'class', 'loadFile' => 'splite_page_results.php', '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 文件中的代码。注意:我已经移除了文档标签以便浏览:
view plain
copy to clipboard
?
define(
'DEBUG_AUTOLOAD'
, false);
define('IS_ADMIN_FLAG'
, false);
define('PAGE_PARSE_START_TIME'
, microtime());
@ini_set
(
"arg_separator.output"
,
"&"
);
if
(
file_exists
(
'includes/local/configure.php'
)) {
include
(
'includes/local/configure.php'
);
}
if
(defined(
'STRICT_ERROR_REPORTING'
) && STRICT_ERROR_REPORTING == true) {
error_reporting
(E_ALL);
} else
{
error_reporting
(E_ALL & ~E_NOTICE);
}
if
(
file_exists
(
'includes/configure.php'
)) {
include
(
'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'
);
exit
;
}
require
(
'includes/autoload_func.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
;
}
define('DEBUG_AUTOLOAD', false); define('IS_ADMIN_FLAG', false); define('PAGE_PARSE_START_TIME', microtime()); @ini_set("arg_separator.output","&"); if (file_exists('includes/local/configure.php')) { include('includes/local/configure.php'); } if (defined('STRICT_ERROR_REPORTING') && STRICT_ERROR_REPORTING == true) { error_reporting(E_ALL); } else { error_reporting(E_ALL & ~E_NOTICE); } if (file_exists('includes/configure.php')) { include('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'); exit; } require('includes/autoload_func.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; }
1.2 观察者类
1.2.1 介绍
一直以来,Zen Cart 项目众多目标中的一个是,让第三方开发者通过一个简单而且优雅的方式来给核心代码添加功能。在过去,我们为了达到这个,依靠重写和自动包括系统。然后这些仍然不能给开发者一个简单的方式来与核心代码的许多领域挂勾,除非是破解核心文件。观察者/通知者系统被引入,让开发者空前的访问核心代码,而不需要过多的触动任何核心文件。虽然表面上是为一个面向对象代码为基础,我们会在稍后看到它是如何同时与过程代码同时使用。
1.2.2 扩展全部类
为了应用观察者/通知者系统,Zen Cart 的一些结构作了改变。首先引入了两个新的类:基类(class.base.php)和通知者类(class.notifier.php)。基类包括了用来应用观察者/通知者系统的代码。然而为了使它作用 Zen Cart 的所有其它类,现在不得不被声明为基类的子类。如果你看一下 Zen Cart 源代码的类文件,你会发现:
view plain
copy to clipboard
?
class
currencies
extends
base {
class currencies extends base {
通知者类将会在稍后讨论,当我们把扩展观察者/通知者系统(ONS)看成是过程编码的时候。
1.2.3 通知者:老大哥正在看着你
那么,这么小题大做是为了什么?ONS 的关键点是开发者可以写代码,等待一定的事件发生,然后当发生的时候,执行他们自己的代码。
那么,事件是如何定义,它们在哪被触发?
事件由添加到 v1.3 核心的代码触发。在任何一个我们想通知一个事件发生的类中,我们添加:
view plain
copy to clipboard
?
$this
->notify(
'EVENT_NAME'
);
$this->notify('EVENT_NAME');
下面的例子可能有用:
在购物车类,当一件商品被添加到购物车后,这个事件触发了:
view plain
copy to clipboard
?
$this
->notify(
'NOTIFIER_CART_ADD_CART_END'
);
$this->notify('NOTIFIER_CART_ADD_CART_END');
在 Zen Cart v1.3 版本中许多事件都拥有通知者,这个是清单
。
这些通知很好,那么如何帮助开发者呢?
1.2.4 观察和壮大
为了利用通知者,开发者需要写一些代码来观察它们。观察者需要被写成一个类。有一个好的目录,includes/classes/observers,开发者可以在这放置这些类。让我们看一个例子。使用上面提到的通知者(NOTIFIER_CART_ADD_CART_END),我如何写一个类来观察那个事件?
view plain
copy to clipboard
?
<?php
class
myObserver
extends
base {
function
myObserver() {
$this
->attach(
$this
,
array
(
'NOTIFIER_CART_ADD_CART_END'
));
}
..
}
<?php class myObserver extends base { function myObserver() { $this->attach($this, array('NOTIFIER_CART_ADD_CART_END')); } ... }
正如你看到的,我们已经定义了一个新的名为 myObserver 的类,在它的构造函数(function myObserver)已经把 myObserver 这个类附加到 NOTIFIER_CART_ADD_CART_END 这个事件中。
“不错,”我听到你说,“但是我怎么样才能做点有用的事情?”
OK,问得好。当一个事件发生的时候,基类会查看,是否有任意的其它观察者类在观察这个事件。如果有,那么基类会执行那个观察者类中的一个方法。还
记得上面提到的 $this->notify('EVENT_NAME')吗?好的,当那个事件发生,基类会调用所有观察者的 update
方法。让我们看更多的代码:
view plain
copy to clipboard
?
class
myObserver
extends
base {
function
myObserver() {
$this
->attach(
$this
,
array
(
'NOTIFIER_CART_ADD_CART_END'
));
}
function
update(&
$callingClass
,
$notifier
,
$paramsArray
) {
... do
some stuff
}
}
class myObserver extends base { function myObserver() { $this->attach($this, array('NOTIFIER_CART_ADD_CART_END')); } function update(&$callingClass, $notifier, $paramsArray) { ... do some stuff } }
现在,当 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 类中:
view plain
copy to clipboard
?
class
myObserver
extends
base {
function
myObserver() {
global
$zco_notifier
;
$zco_notifier
->attach(
$this
,
array
(
'NOTIFY_HEADER_END_CHECKOUT_CONFIRMATION'
));
}
class myObserver extends base { function myObserver() { global $zco_notifier; $zco_notifier->attach($this, array('NOTIFY_HEADER_END_CHECKOUT_CONFIRMATION')); }
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 的文件,包含以下内容:
view plain
copy to clipboard
?
<?php
$autoLoadConfig
[10][] =
array
(
'autoType'
=>
'class'
,
'loadFile'
=>
'observers/class.freeProduct.php'
);
$autoLoadConfig
[90][] =
array
(
'autoType'
=>
'classInstantiate'
,
'className'
=>
'freeProduct'
,
'objectName'
=>
'freeProduct'
);
?>
<?php $autoLoadConfig[10][] = array('autoType'=>'class', 'loadFile'=>'observers/class.freeProduct.php'); $autoLoadConfig[90][] = array('autoType'=>'classInstantiate', 'className'=>'freeProduct', 'objectName'=>'freeProduct'); ?>
注意:已经选择10来引发这个观察者类在 session 开始之前加载的。注意:已经选择90作为从观察者需要附加到
$_SESSION['cart'] 类的偏移量(见下面的 freeProduct 例子),而 $_SESSION['cart']
被实例化的位置是80。
把这些组合起来,让我们看一下真实的例子。
1.2.6 一个真实的例子
被要求众多的一个特性是:当客户消费超过一定金额的时候,商店可以自动添加一件免费赠品。这个代码应该是智能的:它不仅可以在客户消费达到一定金额的时候添加免费赠品,同时也可以在客户改变购物车内容,消费金额在标准之下的时候,移除免费赠品。
传统的,虽然这个代码不是特别困难,它却意味要在 shoppingCart 类中的许多地方需要破解。通过 ONS,这将通过一个十分小的定制类来完成,同时不会造成任何的破坏。
以下是代码:
view plain
copy to clipboard
?
<?php
/**
* 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 dont 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
,
$paramsArray
=
array
()) {
if
(
$eventID
==
'NOTIFIER_CART_REMOVE_END'
&& (isset(
$_SESSION
[
'freeProductInCart'
]) &&
$_SESSION
[
'freeProductInCart'
] == TRUE ))
{
if
(!
$_SESSION
[
'cart'
]->in_cart(
$this
->freeProductID))
{
$_SESSION
[
'userRemovedFreeProduct'
] = TRUE;
}
}
if
(!isset(
$_SESSION
[
'userRemovedFreeProduct'
]) ||
$_SESSION
[
'userRemovedFreeProduct'
] != TRUE)
{
if
(
$_SESSION
[
'cart'
]->show_total() >=
$this
->freeAmount && !
$_SESSION
[
'cart'
]->in_cart(
$this
->freeProductID) )
{
$_SESSION
[
'cart'
]->add_cart(
$this
->freeProductID);
$_SESSION
[
'freeProductInCart'
] = TRUE;
}
}
if
(
$_SESSION
[
'cart'
]->show_total() <
$this
->freeAmount &&
$_SESSION
[
'cart'
]->in_cart(
$this
->freeProductID) )
{
$_SESSION
[
'cart'
]->remove(
$this
->freeProductID);
}
if
(
$_SESSION
[
'cart'
]->in_cart(
$this
->freeProductID))
{
$_SESSION
[
'cart'
]->contents[
$this
->freeProductID][
'qty'
] = 1;
}
}
}
?>
<?php /** * 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 dont 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, $paramsArray = array()) { if ($eventID == 'NOTIFIER_CART_REMOVE_END' && (isset($_SESSION['freeProductInCart']) && $_SESSION['freeProductInCart'] == TRUE )) { if (!$_SESSION['cart']->in_cart($this->freeProductID)) { $_SESSION['userRemovedFreeProduct'] = TRUE; } } if (!isset($_SESSION['userRemovedFreeProduct']) || $_SESSION['userRemovedFreeProduct'] != TRUE) { if ($_SESSION['cart']->show_total() >= $this->freeAmount && !$_SESSION['cart']->in_cart($this->freeProductID) ) { $_SESSION['cart']->add_cart($this->freeProductID); $_SESSION['freeProductInCart'] = TRUE; } } if ($_SESSION['cart']->show_total() < $this->freeAmount && $_SESSION['cart']->in_cart($this->freeProductID) ) { $_SESSION['cart']->remove($this->freeProductID); } if ($_SESSION['cart']->in_cart($this->freeProductID)) { $_SESSION['cart']->contents[$this->freeProductID]['qty'] = 1; } } } ?>
需要注意的几点:
首先,在类本身,我已经为系统设置了选项。这显然是一个坏的主意,通过一个后台模块来设置这些选项无疑要好许多。
其次,注意我们事实是在一个类中观察两个事件。
view plain
copy to clipboard
?
$_SESSION
[
'cart'
]->attach(
$this
,
array
(
'NOTIFIER_CART_ADD_CART_END'
,
'NOTIFIER_CART_REMOVE_END'
));
$_SESSION['cart']->attach($this, array('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 促销,或者买一送一。要达到这个目标比之前的例子要稍微难一些,因为购物车总额需要执行一些操作。然而你将看到这还是轻而易举的。view plain
copy to clipboard
?
<?php
/**
* Observer class used apply a Buy One Get One Free(bogof) algorithm 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
*
* Watches for 1 notifier event, triggered from the shopping cart class.
*/
function
myBogof() {
$this
->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 through 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'
];
break
;
}
}
$cost_saving
.=
$final_price
*
$numBogofs
;
}
}
}
}
$_SESSION
[
'cart'
]->total -=
$cost_saving
;
}
}
?>
<?php /** * Observer class used apply a Buy One Get One Free(bogof) algorithm 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 * * Watches for 1 notifier event, triggered from the shopping cart class. */ function myBogof() { $this->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 through 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']; break; } } $cost_saving .= $final_price * $numBogofs; } } } } $_SESSION['cart']->total -= $cost_saving; } } ?>
注意:这仍然有一些漏洞。
首先,虽然现在总额的变动在购物车页面和边框被显示,line total 没有被调整。
其次,这可能会在结账的时候产生输出混乱。
第三,没有和税结合测试过(@TODO)。
Notifiers currently set in Zen Cart
Notifier points for Zen Cart 1.3.7 Shopping Cart class
NOTIFIER_CART_INSTANTIATE_STARTNOTIFIER_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_BEGINNOTIFY_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_DETERMINEDNOTIFY_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_TAGSNOTIFY_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_BEGINNOTIFY_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_ACCOUNTNOTIFY_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_STORENOTIFY_PAYMENT_PAYPAL_CANCELLED_DURING_CHECKOUT
NOTIFY_PAYMENT_PAYPAL_INSTALLED
NOTIFY_PAYMENT_PAYPAL_UNINSTALLED
Sideboxes:
NOTIFY_SIDEBOX_START_EZPAGES_SIDEBOXNOTIFY_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_RESULTSNOTIFY_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_FOOTERBARNOTIFY_END_EZPAGES_FOOTERBAR
NOTIFY_START_EZPAGES_HEADERBAR
NOTIFY_END_EZPAGES_HEADERBAR
相关文章推荐
- Zen Cart API 教程
- Zen Cart API 教程
- zencart Easy Populate CSV插件,zencart插件,zencart教程
- zen-cart优化插件及站内优化详细教程
- zen-cart开发教程 - 开发Sidebox插件
- zen-cart Sidebox zen-cart开发教程 – 开发Sidebox
- zen-cart开发教程 - 通知者/观察者模式
- ZEN CART API TUTORIALS
- zen-cart开发教程 - 概述
- zen-cart开发教程 – 开发Sidebox .
- zen-cart开发教程 - 概述
- zen-cart开发教程 - 通知者/观察者模式
- 快客原创 火车头数据采集视频教程——第1讲 ecshop zencart shopex lightinthebox 网店数据批量采集教程
- zen-cart开发教程 - 开发Sidebox插件
- zen-cart开发教程 - 开发Sidebox
- Django-Rest-Framework 教程: 5. 提高关联性和超链接API
- Android基础入门教程——8.3.3 Paint API之—— MaskFilter(面具)
- ffmpeg入门系列教程(新API)示例 02
- Quartz教程二--API、Job与Trigger
- ASP.Net Web-api 不可多得的零基础教程4