Opencart主要是做外贸电商的,跨境电商之类的,使用的是PHP语言。
OpenCart基于PHP+MYSQL开发,支持无限分类、无限产品、多主题、多种币种、多语言、购买评论、产品点评、搜索引擎优化、在线下单支付、内置多种支付接口等完善的电子商务功能,是国外著名的B2C电子商务系统。
界面简洁、友好、符合欧美人使用习惯、是最适合国人建外贸网站的程序之一。(同类型的B2C开源程序国内有Ecshop、ZenCart,国外还有Magento)
安装配置
环境要求:
PHP 5.3+(开启cURL、GD、mCrypt扩展)
MySQL 5.1+
Nginx / Apache(开启mod_rewrite重写模块)
安装流程:
OpenCart最新版本为 2.0.3,可从官网下载,下载地址https://www.opencart.com/index.php?route=download/download,下载最新版本后解压缩到网站根目录,通过浏览器访问首页,会自动进入安装过程。按照提示输入数据库配置信息设置后台密码直接安装完成。安装成功后,安全起见需移除install目录,访问https://your-site/admin即可进入管理后台。
注意事项:
安装前除需开启所需的PHP扩展、Apache服务器扩展,还需将以下目录或文件设置为可写。
system/cache 系统缓存目录
system/logs 系统日志目录 debug::log()将会用到
system/download 下载文件存放位置
system/upload 系统默认上传目录
system/modification 系统程序缓存目录
image/cache 缩略图缓存目录
admin/config.php 管理员配置文件
config.php 前台配置文件
以下为comprame需设为可写的目录或文件
hbsitemaps 谷歌地图生成存放目录
journal-cache Journal2 静态资源缓存目录
vqmod/logs vqmod日志目录
vqmod/vqcache vqmod缓存目录
vqmod/checked.cache vqmod用于保存已缓存的文件名
vqmod/mods.cache vqmod用于保存缓存文件的序列化源码
merchenta-product-feeds.txt 产品feeds list生成文件
product-feeds.txt 产品feeds list 生成文件
设为可写的方法:
*inux系统可终端执行chmod–R 777 path/to命令完成此操作。
结构
表结构
概述对比:
OC官方安装后数据表会有123张,现comprame已有数据表190张。OC默认的数据是以MyISAM为存储引擎,不支持事物和回滚,时间字段的数据类型为datetime类型。从查询读写效率上对比,MyISAM比Innodb快很多倍,但MyISAM并不支持事物和回滚,所以今后创建新数据表都默认使用Innodb引擎。
主要表的前缀:
category_* 产品分类相关表
coupon_* 优惠券相关的表
customer_* 客户相关的表
emailtempate_* 邮件模板相关表
journal2_* journal2插件相关表
order_* 订单数据相关表
product_* 产品数据相关表
文件目录结构
/admin 后台MVC程序目录
/catalog 前台MVC程序目录
/hbsitemaps Google地图生存存放目录
/image 图片资源目录
/image/cache 图片缩略图缓存目录
/media 静态资源目录
/sql/sql.txt 每次发版本所需执行的SQL存放
/system 系统公用类库程序脚本
/system/journal2 journal2插件程序
/system/modification modification缓存目录
/index.php 前台入口文件
/.htaccess Apache 重写配置文件
/d_social_login.php 外部登录文件
/robots.txt 搜索引擎机器人文件
URL重写及路由
URL规则:
RewriteCond%{REQUEST_FILENAME} !-f
RewriteCond%{REQUEST_FILENAME} !-d
RewriteCond%{REQUEST_URI} !.*.(ico|gif|jpg|jpeg|png|js|css)
RewriteRule^([^?]*) index.php?route=$1 [L,QSA]
OC的产品详情页、分类页面都是重写伪静态过后的网址,产品的详情页的网址在后台添加修改产品表单的 General -> SEO Keyword中设置。
直接指定:
除重写外,OC可通过route参数来指定需要访问的MVC路由。例如访问注册动作,MVC动作为 account/register,用户可通过local.comprame.com/index.php?route=account/register直接访问。参数传递示例:local.comprame.com/index.php?route=account/register¶m1=val1&..
重写:
运行opencart的环境必须支持重写,opencart默认推荐Apache服务器,将Apache服务器的mod_rewrite模块开启后,将网站配置directory指令中的AllowOverride设置为All,即可。如果使用nginx服务器,可在location指令中添加
if(!-e request_filename){
rewrite^/(.*) /index.php?_route_=$1 last;
break;
}
重启nginx即可。
程序运行流程
请求URL重写到入口文件
通过重写,将请求地址转发到入口文件,一般为index.php,入口文件可从请求参数 route 获得当前请求网址。如果是直接访问入口文件,通过route参数可指定对应的请求路由。
入口文件加载所类库并实例化
执行入口文件,会先引入配置文件,再通过是否定义 !defined(‘DIR_APPLICATION’)判断是否已安装,如果未定义此常量,则转到安装。
之后,入口文件内会引入所需的类、扩展、并实例化所需的类,包括链接数据库、实例化registry、loader、config、request、response、session、customer、controller……
绑定Action动作
执行动作会预先绑定new Action(‘common/seo_url’)动作,用于解析请求的route参数。
开始SEO插件检查并且指派动作Action
实例化控制器后,会开始执行请求的动作 controller?>dispatch( action, new Action(‘error/not_found’));计算出真正的请求路由,并引入该控制器实例化执行动作,执行完毕后会将内注册加到$response对象。
执行结果返回输出
通过$response->output();格式化内容进行输出
vc">OC的MVC
控制器 Controller
存放位置:
OC的控制器程序文件统一存放在 DIR_APPLICATION . ‘controller/’ 目录下。默认的即前台控制器都是放在 /catalog/controller目录下,后台都放在 /admin/controller。
类名规则:
控制器类名以Controller开头,继承自Controller父类。类名的与route路由对应原则:
this?>class=′Controller′.pregreplace(′/[a?zA?Z0?9]/′,′′,
path);
所以
示例1:
请求URL:index.php?route= account/register_promotion
对应文件:/catalog/controller/account/register_promotion.php
执行类名:ControllerAccountRegisterPromotion
示例2:
请求URL:index.php?route=test/account/register_promotion
对应文件:/catalog/controller/test/account/register_promotion.php
执行类名:ControllerTestAccountRegisterPromotion
注意信息:
PHP中函数名、类名、方法名是不区分大小写,但为了阅读方便和规范,类名书写应采用驼峰命名方式。
动作Action
动作Action即控制器的成员方法,命名英文字母或下划线开头英文字母数字下划线组成。请求时动作名附带在route参数中,如果未指定,默认动作为 index,注意请求访问以双下划线开头的方法不会被调用。
动作的参数接受:
对于直接请求的动作,可通过网址传递参数,接受可在动作内通过如下方式,先判定是否设置再接收。
if(isset(this->request->get[‘product_id’])){product_id= (int)this->request->get[‘product_id’];
}else{product_id= 0;
}
控制器的动作可直接调用前台其他控制器的动作,参数的传递可通过如下方式进行
传递:
$this->load->controller(‘catalog/feeds/build’,1);
接收:
public function build($notSetSession= false){
}
模型Model
模型文件存放在DIR_APPLICATION . ‘model/’目录下,模型的寻找如下:
file=DIRAPPLICATION.′model/′.
model .’.php’;
class=′Model′.pregreplace(′/[a?zA?Z0?9]/′,′′,
model);
在Action中引入和调用示例:
this?>load?>model(‘catalog/product′);
this->model_catalog_product->getProducts();
视图View
视图文件可任意扩展名结尾,统一存放在DIR_TEMPLATE目录下,默认为.tpl。
在Action的末尾,可通过如下代码实现视图的渲染,并注册到response对象以用于入口文件输出。
this?>response?>setOutput(
this->load->view(‘catalog/product_list.tpl’, $data));
插件
VQMOD
vQmod全称是 Virtual File Modification System 又称快速虚拟MOD)是一个虚拟覆盖系统的设计,以避免原有系统核心文件被修改。这个概念是很简单,它通过创建XML搜索/替换脚本文件,而不是直接更改核心文件。这些脚本文件是在页面加载解析为每个“源”核心文件“包括”或“规定”的php函数加载和资源,然后打补丁的脚本文件的变化,并保存到一个临时文件,然后在执行临时文件,在过程中取代了原来的文件。原来的源文件是永远不会改变。这将实现一个“虚拟”的变化中,在没有任何实际修改的核心文件中执行想要的过程和结果。
执行原理:PHP搜索替换。(通过XML读取vqmod/xml下的xml文件,逐个配置内指定的文件进行替换对应的代码,缓存到vqmod/vqcache下生成对应的缓存文件)
相关文件:
缺点:如果改动了要替换的文件的源码,可导致搜索不到对应文字而不替换。
刷新:可通过清空vqmod/checked.cache及vqmod/mods.cache内容强制vqmod重新生成缓存。
SEO插件
用途作用:后台管理操作管理oc_url_alias的数据。
后台位置:Extension -> Modules ->Complete SEO Package
邮件模板Emailtemplate
用途作用:可后台配置的邮件模板,用于格式化发送的邮件内容。
用法示例:
template=newEmailTemplate(
this->request,
this?>registry);
template->addData(
this?>request?>post);
template->data['password'] =
password;
template->data['account_login'] = this->url->link(‘account/login’,‘&email=’.this->request->post[‘email’], ‘SSL’);
template?>data[′accountlogintracking′]=
template->getTracking($template->data[‘account_login’]);
mail=newMail(); mail->useExternal= this?>config?>get(‘configmailexternal′); mail->setRegistry( this?>registry); mail->protocol = this?>config?>get(‘configmailprotocol′); mail->parameter = this?>config?>get(‘configmailparameter′); mail->smtp_hostname= this?>config?>get(‘configmailsmtphostname′); mail->smtp_username= this?>config?>get(‘configmailsmtpusername′); mail->smtp_password= html_entity_decode( this?>config?>get(‘configmailsmtppassword′),ENTQUOTES,‘UTF?8′); mail->smtp_port= this?>config?>get(‘configmailsmtpport′); mail->smtp_timeout= $this->config->get(‘config_mail_smtp_timeout’);
mail?>setTo(
this->request->post[‘email’]);
mail?>setFrom(
this->config->get(‘config_email’));
mail?>setSender(htmlentitydecode(
this->config->get(‘config_name’), ENT_QUOTES, ‘UTF-8’));
mail?>setSubject(
subject);
mail?>setText(
message);
template?>load(‘customer.forgotten′);
mail =
template?>hook(
mail);
mail?>send();
template->sent();
Journal2 插件
OC的一款主题模板主题,可最大化的在后台配置前台的模板显示。Journal2 可通过PHP判断设备尺寸,再渲染不同的HTML以自动适配。Journal2插件会在journal-cache下生成JS CSS的静态资源,因此此目录需要可写。
交易及支付流程(TBD)
交易流程 PayULatam支付 PayPal 快速付款 PayPal 标准支付 线下付款及流程专题
参数接受
在OC中, POST、 _GET、 FILES、 _REQUEST、 SERVER、 _COOKIE都已过滤后封装在 $this->request对象中,如需要再控制器动作中接受,可通先判断是否isset再传值。
过滤方式:
data=htmlspecialchars(
data, ENT_COMPAT, ‘UTF-8’);
参考代码:
if(isset(this->request->get[‘order’])){url.='&order=' . $this->request->get[‘order’];
}
多语言支持
语言的设置:
comprame前台默认的是西班牙语,后台使用的是英语。如需更改,可在增加语言文件后再在后台进行设置。后台位置 System -> Settings -> Store -> Local,Language、Administrator Language,在控制器动作中可通过
config?>get(‘configlanguage′)获得前台语言文件ID,
config->get(‘config_admin_language’)
获得后台语言文件ID。
翻译的使用:
先载入对应的语言文件,再通过
this?>language?>get对象即可,示例如下:
this->load->language(‘catalog/product’);
data[′success′]=
this->language->get(‘text_success’);
注意的事项:
1) 由于comprame已经使用了journal2 ,开启多语言支持会导致journal2主题不会引入对应的设置。所以不能开启多语言支持。
原因源码:
1
2
3
|
<code
class
=
"hljs tex"
>#D:\website\latin\src\catalog\view\theme\journal2\template\common\language.tpl
#common/header
$data[
'language'
] = $
this
->load->controller(
'common/language'
);</code>
|
动作Action调用
在Action动作中,可相互调用同端的动作Action(前端的控制器中只能调用前端的,后端调用后端的),实现方式如下:
data[′currency′]=
this->load->controller(‘common/currency’);
调用时参数的传递:
this?>load?>controller(′payment/ppexpress′,
varArray);
调用时参数的接受:
public function index() {
$varArray= func_get_args(); //获取传递的参数
}
Event事件触发
示例代码:
this?>event?>trigger(′pre.admin.attribute.add′,
data);
$data为传递进入的参数,默认为空。涉及表oc_event
1
2
3
4
5
6
7
8
9
|
<code
class
=
"hljs php"
>#event执行原理
public
function trigger($key, &$arg= array()) {
if
(isset($
this
->data[$key])) {
foreach($
this
->data[$key] as $event) {
$action =
new
Action($event, $arg);
$action->execute($
this
->registry);
}
}
}</code>
|
SQL过滤
SQL的过滤,主要是转换接受参数为int类型或string类型,使用显性类型强制转换。
示例int转换:
this?>db?>query("DELETEFROM".DBPREFIX."attributeWHEREattributeid=′".(int)
attribute_id.”’”);
示例string过滤:
sql="ANDnameLIKE′
this->db->escape($data[‘filter_name’]).”%’”;
SQL宽字符注入:
PHP中因为字符集问题可引起SQL宽字符chr(0x5c) chr(0x27) chr(0xbf)
注入,通过字符集强制转换为UTF-8,可防止。
在OC中, this?>request对象clean方法中已经过滤并有字符集转换。 data= htmlspecialchars($data, ENT_COMPAT, ‘UTF-8’);
所以针对传入需进行SQL拼接参数,必须从$this->request中获取!
货币转换
comprame开启支持3种货币,分别是美元USD、比索COP、墨西哥元MXN,前台默认的货币显示为COP(哥伦比亚币),后台员添加和修改产品时,所用的单位都是USD(美元)。
所以在前台显示商品价格,涉及货币转换,转换代码可参考如下:
this?>currency?>convert( value, $this->config->get(‘config_currency’), ‘USD’);
config_currency为后台设置的默认货币。
货币格式化输出:
this?>currency?>format(
money, ‘COP’);
注意事项:
COP哥伦比亚比索,其小数点使用的是逗号,而千分是使用的点号。
汇率的更新:
汇率是后台人员手动设置,位置System ->Localisation -> Currencies,涉及表oc_currency。
文件上传
后台通过common/filemanager/upload 进行上传,上传后保存在catalog目录下,默认为image。
// Make sure we have the correct directory
if (isset(this->request->get[‘directory’])){directory = rtrim(DIR_IMAGE . 'catalog/' . str_replace(array('../', '..\\', '..'), '', this->request->get[‘directory’]),‘/’);
}else{directory = DIR_IMAGE . ‘catalog’;
}
对于上传的文件,限制在如下4中mime类型的文件
// Allowed file mime types
$allowed = array(
‘image/jpeg’,
‘image/pjpeg’,
‘image/png’,
‘image/x-png’,
‘image/gif’
);
SESSION操作
在入口文件index.php,SESSION对象已经初始化后注册到了
registry对象中。
session= new Session();
registry?>set(′session′,
session);
在动作中,可通过如下方式对SESSION进行操作:
//检测
isset($this->session->data[‘success’])
//赋值
this?>session?>data[′success′]=
this->language->get(‘text_success’);
//销毁
unset($this->session->data[‘success’]);
COOKIE操作
//读取
if(isset(this->request->cookie[‘tracking’])){order_data['tracking'] = $this->request->cookie[‘tracking’];
}
//COOKIE的设置还是通过默认的setcookie进行操作
setcookie(‘language’,
code,time()+86400?30,′/′,
request->server[‘HTTP_HOST’]);
跳转及重定向
在Action动作中通过response对象进行重定向
this?>response?>redirect(
this->url->link(‘catalog/attribute’, ‘token=’ .
this?>session?>data[′token′].
url, ‘SSL’));
后台通信前台方法
原理:通过CURL请求前台api/login接口,使用api表的用户密码数据录到前台,再CURL请求前台要执行的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
<code
class
=
"hljs php"
>例子:common/dashboardapiRequest
public
function apiRequest($url, $data = array())
{
$json= array();
$
this
->load->language(
'sale/order'
);
$
this
->load->model(
'sale/order'
);
$
this
->load->model(
'user/api'
);
unset($
this
->session->data[
'cookie'
]);
$api_info= $
this
->model_user_api->getApi($
this
->config->get(
'config_api_id'
));
if
($api_info) {
$curl = curl_init();
if
(substr(HTTPS_CATALOG,
0
,
5
) ==
'https'
) {
curl_setopt($curl, CURLOPT_PORT,
443
);
}
curl_setopt($curl, CURLOPT_HEADER,
false
);
curl_setopt($curl, CURLINFO_HEADER_OUT,
true
);
curl_setopt($curl, CURLOPT_USERAGENT, $
this
->request->server[
'HTTP_USER_AGENT'
]);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,
false
);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER,
false
);
curl_setopt($curl, CURLOPT_FORBID_REUSE,
false
);
curl_setopt($curl, CURLOPT_RETURNTRANSFER,
true
);
curl_setopt($curl, CURLOPT_URL, HTTPS_CATALOG.
'index.php?route=api/login'
);
curl_setopt($curl, CURLOPT_POST,
true
);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($api_info));
$result = curl_exec($curl);
if
(!$result) {
$json[
'error'
] = sprintf($
this
->language->get(
'error_curl'
), curl_error($curl), curl_errno($curl));
}
else
{
$response = json_decode($result,
true
);
if
(isset($response[
'cookie'
])) {
$
this
->session->data[
'cookie'
] = $response[
'cookie'
];
}
curl_close($curl);
}
}
else
{
$json[
'error'
] = $
this
->language->get(
'error_action'
);
}
if
(isset($
this
->session->data[
'cookie'
])) {
$curl = curl_init();
if
(substr($url,
0
,
5
) ==
'https'
) {
curl_setopt($curl, CURLOPT_PORT,
443
);
}
curl_setopt($curl, CURLOPT_HEADER,
false
);
curl_setopt($curl, CURLINFO_HEADER_OUT,
true
);
curl_setopt($curl, CURLOPT_USERAGENT, $
this
->request->server[
'HTTP_USER_AGENT'
]);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,
false
);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER,
false
);
curl_setopt($curl, CURLOPT_FORBID_REUSE,
false
);
curl_setopt($curl, CURLOPT_RETURNTRANSFER,
true
);
curl_setopt($curl, CURLOPT_URL, HTTPS_CATALOG.$url);
if
($data) {
curl_setopt($curl, CURLOPT_POST,
true
);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
}
curl_setopt($curl, CURLOPT_COOKIE, session_name().
'='
.$
this
->session->data[
'cookie'
].
';'
);
$result = curl_exec($curl);
if
(!$result) {
$json[
'error'
] = sprintf($
this
->language->get(
'error_curl'
), curl_error($curl), curl_errno($curl));
}
else
{
$json= (array)json_decode($result);
}
curl_close($curl);
}
else
{
$json[
'error'
] = $
this
->language->get(
'error_action'
);
}
return
$json;
} </code>
|
数据分页Pagination
1
2
3
4
5
6
7
|
<code
class
=
"hljs lasso"
>$pagination =
new
Pagination();
$pagination->total = $affiliate_total;
$pagination->page = $page;
$pagination->limit = $
this
->config->get(
'config_limit_admin'
);
$pagination->url= $
this
->url->link(
'marketing/affiliate'
,
'token='
. $
this
->session->data[
'token'
] . $url.
'&page={page}'
,
'SSL'
);
$data[
'pagination'
] = $pagination->render();</code>
|
图片缩略图生成
原理,检测对应尺寸的缩略图文件是否存在,如果不存在或者文件创建时间小于原图创建时间,重新生成对应尺寸的图片,并保存到DIR_IMAGE目录中。
调用源码:
1
2
3
4
5
6
7
8
9
10
|
<code
class
=
"hljs erlang-repl"
>$
this
->load->model(
'tool/image'
);
if
($product[
'image'
]){
$image = $
this
->model_tool_image->resize($product[
'image'
],
$
this
->config->get(
'config_image_cart_width'
),
$
this
->config->get(
'config_image_cart_height'
));
}
else
{
$image = $
this
->model_tool_image->resize(
'no_image.png'
,
$
this
->config->get(
'config_image_cart_width'
),
$
this
->config->get(
'config_image_cart_height'
));
}</code>
|
备注如果没有缩略图,可使用no_image.png生成。
data[′placeholder′]=
this->model_tool_image->resize(‘no_image.png’, 100, 100);
后台对应设置图片尺寸:System -> Settings ->Store -> Image
config_image_cart_width 购物车产品缩略图大小
config_image_thumb_width 产品缩略图大小,包括手机端
config_image_popup_width 产品弹出图片大小
config_image_product_width 产品列表图片大小
OC的模板
控制器动作中调用模板的代码:
this?>response?>setOutput(
this->load->view(‘catalog/category_list.tpl’, $data));
OC模板为原生PHP,无额外模板解析,模板文件夹扩展名默认为*.tpl,存放在DIR_TEMPLATE
目录中。模板解析后的内容通过PHP 的ob_get_content读取缓冲内容返回到response对象中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<code
class
=
"hljs php"
>
public
function view($template, $data = array()) {
$file = DIR_TEMPLATE . $template;
if
(file_exists($file)) {
extract($data);
ob_start();
require($file);
$output = ob_get_contents();
ob_end_clean();
return
$output;
}
else
{
trigger_error(
'Error: Could not load template '
. $file .
'!'
);
exit();
}
}</code>
|