Zend Framework 实例教程
2010-04-30Zend Framework发布了!虽然仍处于开发初期,这个教程仍突出讲解目前几个最好的功能,并指导你完成一个简单程序的构建。
Zend最早在社区里发布了ZF。基于同样的想法,这个教程写来用于展示ZF现有的功能。由于这个教程是在线发布,我将在ZF变化时对其进行更新, 以便尽可能有效。
要求
Zend Framework要求PHP5。为了更好利用本教程的代码,你还需要Apache网页服务器。因为示范程序(一个新闻管理系统)用到了mod_rewrite。
这个教程的代码可以自由下载,所以你可以自己试一下。你可以从Brain Buld的网站下载到代码:http://brainbulb.com/zend-framework-tutorial.tar.gz。
下载ZF
当你开始这篇教程时,你需要下载ZF的最新版本。你可以用浏览器手工从http://framework.zend.com/download选 择tar.gz或zip文件进行下载,或者使用下列命令:
$ wget http://framework.zend.com/download/tgz
$ tar -xvzf ZendFramework-0.1.2.tar.gz
一旦你下载了预览版,把library目录放到方便的地方。在这个教程,我把library重 命名为lib以便有个简洁的目录结构:
app/
views/
controllers/
www/
.htaccess
index.php
lib/
www目录是文档根目录,controllers和views目 录是以后会用到的空目录,而lib目录来自你下载的预览版。
开始
我要介绍的第一个组件是Zend_Controller。从很多方面看,它为你开发的程序提供了基础,同时也部分决定了 Zend Framework不只是个组件的集合。但是,你在用之前需要将所有的得到的请求都放到一个简单的PHP脚本。本教程用的是mod_rewrite。
用mod_rewrite自身是一种艺术,但幸运的是,这个特殊的任务特别简单。如果你对mod_rewrite或 Apache的一般配置不熟悉,在文档根目录下创建一个.htaccess文件,并添加以下内容:
RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
Zend_Controller的一个 TODO项目就是取消对mod_rewrite的依赖。为了提供一个预览版的范例,本教程用了mod_rewrite。
如果你直接把这些内容添加到httpd.conf,你必须重启网页服务器。但如果你用.htaccess文 件,则什么都不必做。你可以放一些具体的文本到index.php并访问任意路径如/foo/bar做 一下快速测试。如你的域名为example.org,则访问http://example.org/foo/bar。
你还要设置ZF库的路径到include_path。你可以在php.ini设置,也可以直 接在你的.htaccess文件放下列内容:
php_value include_path "/path/to/lib"
Zend
Zend类包含了一些经常使用的静态方法的集合。下面是唯一一个你要手工添加的类:
<?php
include 'Zend.php';
?>
一旦你包含了Zend.php,你就已经包含了Zend类的所有的类方法。用loadClass()就 可以简单地加载其它类。例如,加载Zend_Controller_Front类:
<?php
include 'Zend.php';
Zend::loadClass('Zend_Controller_Front');
?>
include_path能理解loadclass()及ZF的组织和目录结构。我用它加载 所有其它类。
Zend_Controller
使用这个controller非常直观。事实上,我写本教程时并没有用到它丰富的文档。
我一开始是用一个叫Zend_Controller_Front的front controller。为了理解它是怎么工作的,请把下列代码放在你的index.php文件:
<?php
include 'Zend.php';
Zend::loadClass('Zend_Controller_Front');
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('/path/to/controllers');
$controller->dispatch();
?>
如果你更喜欢对象链结,可以用以下代码代替:
<?php
include 'Zend.php';
Zend::loadClass('Zend_Controller_Front');
$controller = Zend_Controller_Front::getInstance()
->setControllerDirectory('/path/to/controllers')
->dispatch();
?>
现在如果你访问/foo/bar,会有错误发生。没错!它让你知道发生了什么事。主要的问题是找不到IndexController.php文 件。
在你创建这个文件之前,应先理解一下ZF想让你怎样组织这些事情。ZF把访问请求给拆分开来。假如访问的是/foo/bar, 则foo是controller,而bar是action。它们的默认值都是index.
如果foo是controller,ZF就会去查找controllers目录下的FooController.php文 件。因为这个文件不存在,ZF就退回到IndexController.php。结果都没有找到,就报错了。
接下来,在controllers目录创建IndexController.php文件(可 以用setControllerDirectory()设置):
<?php
Zend::loadClass('Zend_Controller_Action');
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
echo 'IndexController::indexAction()';
}
}
?>
就如刚才说明的,IndexController类处理来自index controller或controller不存在的请求。indexAction()方法处理action为index的 访问。要记住的是index是controller和action的默认值。如果你访问/,/index或/index/index,indexAction()方 法就会被执行。 (最后面的斜杠并不会改变这个行为。) 而访问其他任何资源只会导致出错。
在继续做之前,还要在IndexController加上另外一个有用的类方法。不管什么时候访问一个不存在的控制器, 都要调用noRouteAction()类方法。例如,在FooController.php不存 在的条件下,访问/foo/bar就会执行noRouteAction()。但是访问/index/foo仍 会出错,因为foo是action,而不是controller.
将noRouteAction()添加到IndexController.php:
<?php
Zend::loadClass('Zend_Controller_Action');
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
echo 'IndexController::indexAction()';
}
public function noRouteAction()
{
$this->_redirect('/');
}
}
?>
例子中使用$this->_redirect('/')来描述执行noRouteAction()时, 可能发生的行为。这会将对不存在controllers的访问重定向到根文档(首页)。
现在创建FooController.php:
<?php
Zend::loadClass('Zend_Controller_Action');
class FooController extends Zend_Controller_Action
{
public function indexAction()
{
echo 'FooController::indexAction()';
}
public function barAction()
{
echo 'FooController::barAction()';
}
}
?>
如果你再次访问/foo/bar,你会发现执行了barAction(),因为bar是 action。现在你不只支持了友好的URL,还可以只用几行代码就做得这么有条理。酷吧!
你也可以创建一个__call()类方法来处理像/foo/baz这样未定义的 action。
<?php
Zend::loadClass('Zend_Controller_Action');
class FooController extends Zend_Controller_Action
{
public function indexAction()
{
echo 'FooController::indexAction()';
}
public function barAction()
{
echo 'FooController::barAction()';
}
public function __call($action, $arguments)
{
echo 'FooController:__call()';
}
}
?>
现在你只要几行代码就可以很好地处理用户的访问了,准备好继续。
Zend_View
Zend_View是一个用来帮助你组织好你的view逻辑的类。这对于模板-系统是不可知的,为了简单起见,本教程不 使用模板。如果你喜欢的话,不妨用一下。
记住,现在所有的访问都是由front controller进行处理。因此应用框架已经存在了,另外也必须遵守它。为了展示Zend_View的 一个基本应用,将IndexController.php修改如下:
<?php
Zend::loadClass('Zend_Controller_Action');
Zend::loadClass('Zend_View');
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$view = new Zend_View();
$view->setScriptPath('/path/to/views');
echo $view->render('example.php');
}
public function noRouteAction()
{
$this->_redirect('/');
}
}
?>
在views目录创建example.php文件:
<html>
<head>
<title>This Is an Example</title>
</head>
<body>
<p>This is an example.</p>
</body>
</html>
现在,如果你访问自己网站的根资源,你会看到example.php的内容。这仍没什么用,但你要清楚你要在以一种结构 和组织非常清楚的方式在开发网络应用。
为了让Zend_View的应用更清楚一点,,修改你的模板(example.php)包含 以下内容:
<html>
<head>
<title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
<?php echo $this->escape($this->body); ?>
</body>
</html>
现在已经添加了两个功能。$this->escape()类方法用于所有的输出。即使你自己创建输出,就像这个例 子一样。避开所有输出也是一个很好的习惯,它可以在默认情况下帮助你防止跨站脚本攻击(XSS)。
$this->title和$this->body属性用来展示动态数据。这些 也可以在controller中定义,所以我们修改IndexController.php以指定它们:
<?php
Zend::loadClass('Zend_Controller_Action');
Zend::loadClass('Zend_View');
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$view = new Zend_View();
$view->setScriptPath('/path/to/views');
$view->title = 'Dynamic Title';
$view->body = 'This is a dynamic body.';
echo $view->render('example.php');
}
public function noRouteAction()
{
$this->_redirect('/');
}
}
?>
现在你再次访问根目录,应该就可以看到模板所使用的这些值了。因为你在模板中使用的$this就是在Zend_View范 围内所执行的实例。
要记住example.php只是一个普通的PHP脚本,所以你完全可以做你想做的。只是应努力只在要求显示数据时才使 用模板。你的controller (controller分发的模块)应处理你全部的业务逻辑。
在继续之前,我想做最后一个关于Zend_View的提示。在controller的每个类方法内初始化$view对 象需要额外输入一些内容,而我们的主要目标是让快速开发网络应用更简单。如果所有模板都放在一个目录下,是否要在每个例子中都调用setScriptPath()也 存在争议。
幸运的是,Zend类包含了一个寄存器来帮助减少工作量。你可以用register()方法 把你的$view对象存储在寄存器:
<?php
Zend::register('view', $view);
?>
用registry()方法进行检索:
<?php
$view = Zend::registry('view');
?>
基于这点,本教程使用寄存器。
Zend_InputFilter
本教程讨论的最后一个组件是Zend_InputFilter。这个类提供了一种简单而有效的输入过滤方法。你可以通过 提供一组待过滤数据来进行初始化。
<?php
$filterPost = new Zend_InputFilter($_POST);
?>
这会将($_POST)设置为NULL,所以就不能直接进入了。Zend_InputFilter提 供了一个简单、集中的根据特定规则过滤数据的类方法集。例如,你可以用getAlpha()来获取$_POST['name']中 的字母:
<?php
/* $_POST['name'] = 'John123Doe'; */
$filterPost = new Zend_InputFilter($_POST);
/* $_POST = NULL; */
$alphaName = $filterPost->getAlpha('name');
/* $alphaName = 'JohnDoe'; */
?>
每一个类方法的参数都是对应要过滤的元素的关键词。对象(例子中的$filterPost)可以保护数据不被篡改,并能 更好地控制对数据的操作及一致性。因此,当你操纵输入数据,应始终使用Zend_InputFilter。
Zend_Filter提供与Zend_InputFilter方 法一样的静态方法。
构建新闻管理系统
虽然预览版提供了许多组件(甚至许多已经被开发),我们已经讨论了构建一个简单程序所需要的全部组件。在这里,你会对ZF的基本结构和设计有更清楚 的理解。
每个人开发的程序都会有所不同,而Zend Framework试图包容这些差异。同样,这个教程是根据我的喜好写的,请根据自己的偏好自行调整。
当我开发程序时,我会先做界面。这并不意味着我把时间都花在标签、样式表和图片上,而是我从一个用户的角度去考虑问题。因此我把程序看成是页面的集 合,每一页都是一个独立的网址。这个新闻系统就是由以下网址组成的:
/
/add/news
/add/comment
/admin
/admin/approve
/view/{id}
你可以直接把这些网址和controller联系起来。IndexController列出新闻,AddController添 加新闻和评论,AdminController处理一些如批准新闻之类的管理,ViewController特 定新闻和对应评论的显示。
如果你的FooController.php还在,把它删除。修改IndexController.php, 为业务逻辑以添加相应的action和一些注释:
<?php
Zend::loadClass('Zend_Controller_Action');
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
/* List the news. */
}
public function noRouteAction()
{
$this->_redirect('/');
}
}
?>
接下来,创建AddController.php文件:
<?php
Zend::loadClass('Zend_Controller_Action');
class AddController extends Zend_Controller_Action
{
function indexAction()
{
$this->_redirect('/');
}
function commentAction()
{
/* Add a comment. */
}
function newsAction()
{
/* Add news. */
}
function __call($action, $arguments)
{
$this->_redirect('/');
}
}
?>
记住AddController的indexAction()方法不能调用。当访问/add时 会执行这个类方法。因为用户可以手工访问这个网址,这是有可能的,所以你要把用户重定向到主页、显示错误或你认为合适的行为。
接下来,创建AdminController.php文件:
<?php
Zend::loadClass('Zend_Controller_Action');
class AdminController extends Zend_Controller_Action
{
function indexAction()
{
/* Display admin interface. */
}
function approveAction()
{
/* Approve news. */
}
function __call($action, $arguments)
{
$this->_redirect('/');
}
}
?>
最后,创建ViewController.php文件:
<?php
Zend::loadClass('Zend_Controller_Action');
class ViewController extends Zend_Controller_Action
{
function indexAction()
{
$this->_redirect('/');
}
function __call($id, $arguments)
{
/* Display news and comments for $id. */
}
}
?>
和AddController一样,index()方法不能调用,所以你可以使用你认为合适 的action。ViewController和其它的有点不同,因为你不知道什么才是有效的action。为了支持像/view/23这 样的网址,你要使用__call()来支持动态action。
数据库操作
因为Zend Framework的数据库组件还不稳定,而我希望这个演示可以做得简单一点。我使用了一个简单的类,用SQLite进行新闻条目和评论的存储和查询。
<?php
class Database
{
private $_db;
public function __construct($filename)
{
$this->_db = new SQLiteDatabase($filename);
}
public function addComment($name, $comment, $newsId)
{
$name = sqlite_escape_string($name);
$comment = sqlite_escape_string($comment);
$newsId = sqlite_escape_string($newsId);
$sql = "INSERT
INTO comments (name, comment, newsId)
VALUES ('$name', '$comment', '$newsId')";
return $this->_db->query($sql);
}
public function addNews($title, $content)
{
$title = sqlite_escape_string($title);
$content = sqlite_escape_string($content);
$sql = "INSERT
INTO news (title, content)
VALUES ('$title', '$content')";
return $this->_db->query($sql);
}
public function approveNews($ids)
{
foreach ($ids as $id) {
$id = sqlite_escape_string($id);
$sql = "UPDATE news
SET approval = 'T'
WHERE id = '$id'";
if (!$this->_db->query($sql)) {
return FALSE;
}
}
return TRUE;
}
public function getComments($newsId)
{
$newsId = sqlite_escape_string($newsId);
$sql = "SELECT name, comment
FROM comments
WHERE newsId = '$newsId'";
if ($result = $this->_db->query($sql)) {
return $result->fetchAll();
}
return FALSE;
}
public function getNews($id = 'ALL')
{
$id = sqlite_escape_string($id);
switch ($id) {
case 'ALL':
$sql = "SELECT id,
title
FROM news
WHERE approval = 'T'";
break;
case 'NEW':
$sql = "SELECT *
FROM news
WHERE approval != 'T'";
break;
default:
$sql = "SELECT *
FROM news
WHERE id = '$id'";
break;
}
if ($result = $this->_db->query($sql)) {
if ($result->numRows() != 1) {
return $result->fetchAll();
} else {
return $result->fetch();
}
}
return FALSE;
}
}
?>
(你可以用自己的解决方案随意替换这个类。这里只是为你提供一个完整示例的介绍,并非建议要这么实现。)
这个类的构造器需要SQLite数据库的完整路径和文件名,你必须自己进行创建。
<?php
$db = new SQLiteDatabase('/path/to/db.sqlite');
$db->query("CREATE TABLE news (
id INTEGER PRIMARY KEY,
title VARCHAR(255),
content TEXT,
approval CHAR(1) DEFAULT 'F'
)");
$db->query("CREATE TABLE comments (
id INTEGER PRIMARY KEY,
name VARCHAR(255),
comment TEXT,
newsId INTEGER
)");
?>
你只需要做一次,以后直接给出Database类构造器的完整路径和文件名即可:
<?php
$db = new Database('/path/to/db.sqlite');
?>
整合
为了进行整合,在lib目录下创建Database.php,loadClass()就 可以找到它。你的index.php文件现在就会初始化$view和$db并 存储到寄存器。你也可以创建__autoload()函数自动加载你所需要的类:
<?php
include 'Zend.php';
function __autoload($class)
{
Zend::loadClass($class);
}
$db = new Database('/path/to/db.sqlite');
Zend::register('db', $db);
$view = new Zend_View;
$view->setScriptPath('/path/to/views');
Zend::register('view', $view);
$controller = Zend_Controller_Front::getInstance()
->setControllerDirectory('/path/to/controllers')
->dispatch();
?>
接下来,在views目录创建一些简单的模板。index.php可以用来显示index视 图:
<html>
<head>
<title>News</title>
</head>
<body>
<h1>News</h1>
<?php foreach ($this->news as $entry) { ?>
<p>
<a href="/view/<?php echo $this->escape($entry['id']); ?>">
<?php echo $this->escape($entry['title']); ?>
</a>
</p>
<?php } ?>
<h1>Add News</h1>
<form action="/add/news" method="POST">
<p>Title:<br /><input type="text" name="title" /></p>
<p>Content:<br /><textarea name="content"></textarea></p>
<p><input type="submit" value="Add News" /></p>
</form>
</body>
</html>
view.php模板可以用来显示选定的新闻条目:
<html>
<head>
<title>
<?php echo $this->escape($this->news['title']); ?>
</title>
</head>
<body>
<h1>
<?php echo $this->escape($this->news['title']); ?>
</h1>
<p>
<?php echo $this->escape($this->news['content']); ?>
</p>
<h1>Comments</h1>
<?php foreach ($this->comments as $comment) { ?>
<p>
<?php echo $this->escape($comment['name']); ?> writes:
</p>
<blockquote>
<?php echo $this->escape($comment['comment']); ?>
</blockquote>
<?php } ?>
<h1>Add a Comment</h1>
<form action="/add/comment" method="POST">
<input type="hidden" name="newsId"
value="<?php echo $this->escape($this->id); ?>" />
<p>Name:<br /><input type="text" name="name" /></p>
<p>Comment:<br /><textarea name="comment"></textarea></p>
<p><input type="submit" value="Add Comment" /></p>
</form>
</body>
</html> 最后,admin.php模板可以用来批准新闻条目:
<html>
<head>
<title>News Admin</title>
</head>
<body>
<form action="/admin/approve" method="POST">
<?php foreach ($this->news as $entry) { ?>
<p>
<input type="checkbox" name="ids[]"
value="<?php echo $this->escape($entry['id']); ?>" />
<?php echo $this->escape($entry['title']); ?>
<?php echo $this->escape($entry['content']); ?>
</p>
<?php } ?>
<p>
Password:<br /><input type="password" name="password" />
</p>
<p><input type="submit" value="Approve" /></p>
</form>
</body>
</html>
使用到模板的地方,你只需要把注释替换成几行代码。如IndexController.php就变成下面这样:
<?php
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
/* List the news. */
$db = Zend::registry('db');
$view = Zend::registry('view');
$view->news = $db->getNews();
echo $view->render('index.php');
}
public function noRouteAction()
{
$this->_redirect('/');
}
}
?>
因为条理比较清楚,这个程序首页的整个业务逻辑只有四行代码。AddController.php更复杂一点,它需要更 多的代码:
<?php
class AddController extends Zend_Controller_Action
{
function indexAction()
{
$this->_redirect('/');
}
function commentAction()
{
/* Add a comment. */
$filterPost = new Zend_InputFilter($_POST);
$db = Zend::registry('db');
$name = $filterPost->getAlpha('name');
$comment = $filterPost->noTags('comment');
$newsId = $filterPost->getDigits('newsId');
$db->addComment($name, $comment, $newsId);
$this->_redirect("/view/$newsId");
}
function newsAction()
{
/* Add news. */
$filterPost = new Zend_InputFilter($_POST);
$db = Zend::registry('db');
$title = $filterPost->noTags('title');
$content = $filterPost->noTags('content');
$db->addNews($title, $content);
$this->_redirect('/');
}
function __call($action, $arguments)
{
$this->_redirect('/');
}
}
?>
因为用户在提交表单后被重定向,这个controller不需要视图。
在AdminController.php,你要处理显示管理界面和批准新闻两个action:
<?php
class AdminController extends Zend_Controller_Action
{
function indexAction()
{
/* Display admin interface. */
$db = Zend::registry('db');
$view = Zend::registry('view');
$view->news = $db->getNews('NEW');
echo $view->render('admin.php');
}
function approveAction()
{
/* Approve news. */
$filterPost = new Zend_InputFilter($_POST);
$db = Zend::registry('db');
if ($filterPost->getRaw('password') == 'mypass') {
$db->approveNews($filterPost->getRaw('ids'));
$this->_redirect('/');
} else {
echo 'The password is incorrect.';
}
}
function __call($action, $arguments)
{
$this->_redirect('/');
}
}
?>
最后是ViewController.php:
<?php
class ViewController extends Zend_Controller_Action
{
function indexAction()
{
$this->_redirect('/');
}
function __call($id, $arguments)
{
/* Display news and comments for $id. */
$id = Zend_Filter::getDigits($id);
$db = Zend::registry('db');
$view = Zend::registry('view');
$view->news = $db->getNews($id);
$view->comments = $db->getComments($id);
$view->id = $id;
echo $view->render('view.php');
}
}
?>
虽然很简单,但我们还是提供了一个功能较全的新闻和评论程序。最好的地方是由于有较好的设计,增加功能变得很简单。而且随着Zend Framework越来越成熟,只会变得更好。
更多信息
这个教程只是讨论了ZF表面的一些功能,但现在也有一些其它的资源可供参考。在http://framework.zend.com/manual/有 手册可以查询,Rob Allen在http://akrabat.com/zend-framework/介 绍了一些他使用Zend Framework的经验,而Richard Thomas也在http://www.cyberlot.net/zendframenotes提 供了一些有用的笔记。如果你有自己的想法,可以访问Zend Framework的新论坛:http://www.phparch.com/discuss/index.php/f/289//。
结束语
要对预览版进行评价是很容易的事,我在写这个教程时也遇到很多困难。总的来说,我想Zend Framework显示了承诺,加入的每个人都是想继续完善它。
深入剖析Zen cart模板目录结构
2009-12-19Zen Cart的模板设计说简单其实也挺简单的说复杂也比较复杂,需要一定的时间来熟悉。一旦你了解了它的结构,就会慢慢习惯了。
首先要阅读常见问答部分的:如何添加、制作新模板。 Zen Cart的设计没有什么特别,与以前设计HTML页面是一样的。只是整个页面分成了好几个部分,并加入了php代码。
通常,页面分为页眉(header),页脚(footer),边框(sideboxes)。所以设计页面的时候,要记住Zen Cart是如何组织这些页面的。
页面是通过CSS样式表来控制的。样式表控制表格单元的背景图案、字体的颜色和样式等等。所以,假如你需要修改边框标题栏的字体,那么查看样式表文件。
Zen Cart在页面添加图像有两种方式。可以使用图像目录的相对路径,或者在模板中用php变量定义图像。如果你使用https服务器,并且采用相对图像路径,那么https的图像目录下也要有同样的图像,否则https服务器很可能会给出警告提示。
Zen Cart可以设置成任意的html/flash的界面,只是比通常的html页面的设计费时。你可以从修改缺省的模板开始,先修改CSS文件和三栏格式的界面。开始先采用不同的颜色,很快就可以设计出完全不同的风格。
最后,在设计模板前要先计划好你网页的内容,事半功倍。
下面是zen cart页面各部分相对应模板文件的一个列表:
| 文件路径 | 注释 |
| index.php | 主文件 |
| includes/templates/[custom template folder]/common/html_header.php | 页面的head部分 |
| includes/templates/[custom template folder]/common/tpl_main_page.php | 页面的body部分 |
| includes/templates/[custom template folder]/common/tpl_header.php | 所有页面的页眉 |
| (column left) | |
| includes/templates/[custom template folder]/common/main_template_vars.php | 决定页面的内容部分,缺省为 ‘tmp_index_default.php’ |
| 首页 – 缺省 | |
| includes/templates/[custom template folder]/templates/tmp_index_default.php | 首页模板文件 |
| 首页 – 显示分类 | |
| includes/templates/[custom template folder]/templates/tpl_index_categories.php | 首页上显示分类时的模板文件 |
| includes/modules/[custom template folder]/pages/index/category_row.php | 选择要显示的分类 |
| includes/templates/[custom template folder]/templates/tpl_index_category_row.php | 显示分类 |
| 首页 – 显示指定分类 | |
| includes/templates/[custom template folder]/templates/tpl_index_product_list.php | 首页上显示指定的分类时采用的模板文件 |
| includes/modules/[custom template folder]/product_listing.php | 将商品数据添加到数组 |
| includes/templates/[custom template folder]/templates/tpl_modules_product_listing.php | 显示商品数量和商品导航菜单 |
| includes/templates/[custom template folder]/common/tpl_list_box_content.php | 显示商品数组 |
| 商品信息页面 | |
| includes/templates/[custom template folder]/templates/tpl_product_info_display.php | 显示单件商品信息 |
| 购物车页面 | |
| includes/templates/[custom template folder]/templates/tpl_shopping_cart_default.php | 购物车页面 |
| (column right) | |
| includes/templates/[custom template folder]/common/tpl_footer.php | 所有页面的页脚 |
有2篇文档很有参考价值:
http://www.zen-cart.com/modules/ipb/index.php?showtopic=9912
http://www.zen-cart.com/modules/xoopsfaq/index.php?cat_id=3#14
征婚信息
2009-06-11 一女程序员的征婚信息:
SELECT * FROM 男人们
WHERE (未婚 = TRUE OR 离异 = TRUE ) AND 同性恋 = FALSE AND 穷光蛋 = FALSE AND 条件 IN ('细心','温柔','体贴','贤惠','会做家务','会做饭','会逛街买东西','会浪漫','活泼','可爱','帅气','绅士','大度','气质','智慧','能带孩子') ;
下面俺也模仿征个婚:
SELECT * FROM 女人
WHERE 未婚 = TRUE AND 国籍='中国' AND (年龄>=24 AND 年龄<=28) AND 身高>=160cm AND 身高<=170cm AND 条件1 IN ('漂亮','健康','细心','温柔','体贴','贤惠','会做家务','会洗衣服','会做饭','气质','智慧','能带孩子',' 不会骂人') AND 条件2='不会花钱';
呵呵,说这世间有几女能看懂?看懂了就是奇女子了,呵呵奇女子可当真。
列表框多选数据至另一个列表框
2009-05-04答1:
<HTML>
<HEAD>
<TITLE>Menu Swapper</TITLE>
<META HTTP-EQUIV="The JavaScript Source" CONTENT="no-cache">
<SCRIPT LANGUAGE="JavaScript">
<!-- Begin
sortitems = 1; // Automatically sort items within lists? (1 or 0)
function move(fbox,tbox,movemod) {
for(var i=0; i<fbox.options.length; i++) {
if((fbox.options[i].selected || movemod) && fbox.options[i].value != "") {
var no = new Option();
no.value = fbox.options[i].value;
no.text = fbox.options[i].text;
tbox.options[tbox.options.length] = no;
fbox.options[i].value = "";
fbox.options[i].text = "";
}
}
BumpUp(fbox);
if (sortitems) SortD(tbox);
}
function BumpUp(box) {
for(var i=0; i<box.options.length; i++) {
if(box.options[i].value == "") {
for(var j=i; j<box.options.length-1; j++) {
box.options[j].value = box.options[j+1].value;
box.options[j].text = box.options[j+1].text;
}
var ln = i;
break;
}
}
if(ln < box.options.length) {
box.options.length -= 1;
BumpUp(box);
}
}
function SortD(box) {
var temp_opts = new Array();
var temp = new Object();
for(var i=0; i<box.options.length; i++) {
temp_opts[i] = box.options[i];
}
for(var x=0; x<temp_opts.length-1; x++) {
for(var y=(x+1); y<temp_opts.length; y++) {
if(temp_opts[x].text > temp_opts[y].text) {
temp = temp_opts[x].text;
temp_opts[x].text = temp_opts[y].text;
temp_opts[y].text = temp;
}
}
}
for(var i=0; i<box.options.length; i++) {
box.options[i].value = temp_opts[i].value;
box.options[i].text = temp_opts[i].text;
}
}
function restr(rbox,tbox,str) {
if(tbox.options.length){
rbox.value = tbox.options[0].value;
for(var i=1; i<tbox.options.length; i++) {
rbox.value = rbox.value+str+tbox.options[i].value;
}
}else rbox.value = ""
}
// End -->
</script>
</HEAD>
<BODY>
<center><center>
<form ACTION="" METHOD="POST">
<table border="0">
<tr>
<td>可选项目:<br>
<select multiple size="8" name="list1" style="width: 150">
<option value="11"> item 1.1 </option>
<option value="12"> item 1.2 </option>
<option value="13"> item 1.3 </option>
<option value="21"> item 2.1 </option>
<option value="22"> item 2.2 </option>
<option value="23"> item 2.3 </option>
</select>
</td>
<td>
<input type="button" value=">>" onclick="move(this.form.list1,this.form.list2,1)" name="B0" style="font-weight: bold; width: 32"><br>
<input type="button" value=">" onclick="move(this.form.list1,this.form.list2,0)" name="B1" style="font-weight: bold; width: 32"><br>
<input type="button" value="<" onclick="move(this.form.list2,this.form.list1,0)" name="B2" style="font-weight: bold; width: 32"><br>
<input type="button" value="<<" onclick="move(this.form.list2,this.form.list1,1)" name="B3" style="font-weight: bold; width: 32">
</td>
<td>已选项目:<br><select multiple size="8" name="list2" style="width: 150"></select></td>
</tr>
</table>
<input type="text" name="rst" value="" size="41">
<input type="button" value="go" onclick="restr(this.form.rst,this.form.list2,',')" name="B4" style="font-weight: bold; width: 32">
</form>
</center>
</center>
</body></html>
______________________________________________________________________________________________
答2:
<html>
<head>
<title>Untitled Document</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body bgcolor="#FFFFFF" text="#000000">
<SCRIPT LANGUAGE="JavaScript">
<!-- Begin
function moveOver()
{
var boxLength = document.choiceForm.choiceBox.length;
var selectedItem = document.choiceForm.available.selectedIndex;
var selectedText = document.choiceForm.available.options[selectedItem].text;
var selectedValue = document.choiceForm.available.options[selectedItem].value;
var i;
var isNew = true;
if (boxLength != 0) {
for (i = 0; i < boxLength; i++) {
thisitem = document.choiceForm.choiceBox.options[i].text;
if (thisitem == selectedText) {
isNew = false;
break;
}
}
}
if (isNew) {
newoption = new Option(selectedText, selectedValue, false, false);
document.choiceForm.choiceBox.options[boxLength] = newoption;
}
document.choiceForm.available.selectedIndex=-1;
}
function removeMe() {
var boxLength = document.choiceForm.choiceBox.length;
arrSelected = new Array();
var count = 0;
for (i = 0; i < boxLength; i++) {
if (document.choiceForm.choiceBox.options[i].selected) {
arrSelected[count] = document.choiceForm.choiceBox.options[i].value;
}
count++;
}
var x;
for (i = 0; i < boxLength; i++) {
for (x = 0; x < arrSelected.length; x++) {
if (document.choiceForm.choiceBox.options[i].value == arrSelected[x]) {
document.choiceForm.choiceBox.options[i] = null;
}
}
boxLength = document.choiceForm.choiceBox.length;
}
}
function saveMe() {
var strValues = "";
var boxLength = document.choiceForm.choiceBox.length;
var count = 0;
if (boxLength != 0) {
for (i = 0; i < boxLength; i++) {
if (count == 0) {
strValues = document.choiceForm.choiceBox.options[i].value;
}
else {
strValues = strValues + "," + document.choiceForm.choiceBox.options[i].value;
}
count++;
}
}
if (strValues.length == 0) {
alert("您没有选择任何内容");
}
else {
alert("您选择的内容如下:\r\n"+"第" + strValues+"条");
}
}
// End -->
</script>
<center>
<form name="choiceForm">
<table border=0>
<tr>
<td valign="top" width=175>
<div align="center">可选内容:<br>
<select name="available" onchange="moveOver();" size=10>
<option value="1">内容一</option>
<option value="2">内容二</option>
<option value="3">内容三</option>
<option value="4">内容四</option>
<option value="5">内容五</option>
<option value="6">内容六</option>
<option value="7">内容七</option>
<option value="8">内容八</option>
<option value="9">内容九</option>
<option value="10">内容十</option>
</select>
</div>
</td>
<td valign="top">
<div align="center">你的选择:<br>
<select multiple name="choiceBox" style="width:150;" size="10">
</select>
</div>
</td>
</tr>
<tr>
<td colspan=2 height=10>
<div align="center">
<input type="button" value="删除" onclick="removeMe();">
<input type="button" value="结果" onclick="saveMe();">
</div>
</td>
</tr>
</table>
</form>
</center>
</body>
</html>
______________________________________________________________________________________________
答3:
<table border=0 cellpadding=0 cellspacing=0><form name=meizz>
<tr><td>
<select id=list1 size=8 ondblclick="moveOption(this, this.form.list2)">
<option value=A>aaaaaaaaaa
<option value=B>bbbbbbbbbb
<option value=C>cccccccccc
<option value=D>dddddddddd
<option value=E>eeeeeeeeee
<option value=F>ffffffffff
<option value=G>gggggggggg
<option value=H>hhhhhhhhhh
</select></td>
<td width=40 align=center>
<input name=add type=button value=">>>" onclick="moveOption(this.form.list1, this.form.list2)"><br><br>
<input name=sub type=button value="<<<" onclick="moveOption(this.form.list2, this.form.list1)">
</td><td>
<select id=list2 size=8 ondblclick="moveOption(this, this.form.list1)">
</select>
</td></tr></form>
</table>
<script language="JavaScript"><!--
function moveOption(e1, e2){
try{
var e = e1.options[e1.selectedIndex];
e2.options.add(new Option(e.text, e.value));
e1.options.remove(e1.selectedIndex);
} catch(e){}
}
//--></script>
______________________________________________________________________________________________
答4:
还有我要把要移动的数据,就是选中状态,怎么处理呀?
______________________________________________________________________________________________
答5:
<table>
<tr>
<td valign=top>
<select name=s1 multiple size=4 style="width:100">
<option value=1>aaaaaa
<option value=2>bbbbbb
<option value=3>cccccc
<option value=4>dddddd
<option value=5>eeeeee
<option value=6>ffffff
<option value=7>gggggg
<option value=8>hhhhhh
</select>
</td>
<td valign=middle align=center>
<input type=button name=b4 value=">" onClick="move(1)"><br>
<input type=button name=b5 value="<" onClick="move(2)">
</td>
<td valign=top>
<select name=s2 multiple size=4 style="width:100">
</select>
</td>
</tr>
<tr>
<td>
<input type=button name=b1 value=向上 onClick="up()">
<input type=button name=b2 value=向下 onClick="down()">
</td>
<td>
<input type=button name=b3 value=查看 onClick="show()">
</td>
<td>
</td>
</tr>
</table>
<script>
function up() {
s = document.all.s1;
v = new Array();
for(i=0;i<s.length-1;i++) {
if(! s.options[i].selected && s.options[i+1].selected) {
v.value = s.options[i].value;
v.text = s.options[i].text;
v.selected = s.options[i].selected;
s.options[i].value = s.options[i+1].value;
s.options[i].text = s.options[i+1].text;
s.options[i].selected = s.options[i+1].selected;
s.options[i+1].value = v.value;
s.options[i+1].text = v.text;
s.options[i+1].selected = v.selected;
}
}
}
function down() {
s = document.all.s1;
v = new Array();
for(i=s.length-1;i>0;i--) {
if(! s.options[i].selected && s.options[i-1].selected) {
v.value = s.options[i].value;
v.text = s.options[i].text;
v.selected = s.options[i].selected;
s.options[i].value = s.options[i-1].value;
s.options[i].text = s.options[i-1].text;
s.options[i].selected = s.options[i-1].selected;
s.options[i-1].value = v.value;
s.options[i-1].text = v.text;
s.options[i-1].selected = v.selected;
}
}
}
function show() {
s = document.all.s1;
v = "";
for(i=0;i<s.length;i++)
v += s.options[i].value + ":" + s.options[i].text + "\n";
alert(v);
}
function move(m) {
if(m == 1) {
ss1 = document.all.s1;
ss2 = document.all.s2;
}
if(m == 2) {
ss1 = document.all.s2;
ss2 = document.all.s1;
}
v = new Array();
k = 0;
for(i=0;i<ss1.length;i++) {
if(ss1.options[i].selected) {
ss2.options[ss2.length] = new Option(ss1.options[i].text,ss1.options[i].value);
v[k] = i;
k++;
}
}
for(i=v.length-1;i>=0;i--)
ss1.options[v[i]] = null;
}
</script>
______________________________________________________________________________________________
答6:
在楼上的兄弟上面改了一下
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=Content-Type content="text/html; charset=unicode">
<META content="MSHTML 6.00.2600.0" name=GENERATOR></HEAD>
<BODY><table>
<tr>
<td valign=top>
<select name=s1 multiple size=4 style="WIDTH: 82px; HEIGHT: 167px">
<option value=1>aaaaaa
<option value=2>bbbbbb
<option value=3>cccccc
<option value=4>dddddd
<option value=5>eeeeee
<option value=6>ffffff
<option value=7>gggggg
<option value=8>hhhhhh</option>
</select>
</td>
<td valign=center align=middle>
<input type=button name=b4 value=">" onClick="move(1)"><br>
<input type=button name=b5 value="<" onClick="move(2)">
</td>
<td valign=top>
<select name=s2 multiple size=4 style="WIDTH: 99px; HEIGHT: 158px">
</select>
</td>
</tr>
<tr>
<td>
<input type=button name=b1 value=向上 onClick="up()">
<input type=button name=b2 value=向下 onClick="down()">
</td>
<td>
<input type=button name=b3 value=查看 onClick="show()">
</td>
<td>
</td>
</tr>
</table>
<script>
function up() {
s = document.all.s1;
v = new Array();
for(i=0;i<s.length-1;i++) {
if(! s.options[i].selected && s.options[i+1].selected) {
v.value = s.options[i].value;
v.text = s.options[i].text;
v.selected = s.options[i].selected;
s.options[i].value = s.options[i+1].value;
s.options[i].text = s.options[i+1].text;
s.options[i].selected = s.options[i+1].selected;
s.options[i+1].value = v.value;
s.options[i+1].text = v.text;
s.options[i+1].selected = v.selected;
}
}
}
function down() {
s = document.all.s1;
v = new Array();
for(i=s.length-1;i>0;i--) {
if(! s.options[i].selected && s.options[i-1].selected) {
v.value = s.options[i].value;
v.text = s.options[i].text;
v.selected = s.options[i].selected;
s.options[i].value = s.options[i-1].value;
s.options[i].text = s.options[i-1].text;
s.options[i].selected = s.options[i-1].selected;
s.options[i-1].value = v.value;
s.options[i-1].text = v.text;
s.options[i-1].selected = v.selected;
}
}
}
function show() {
s = document.all.s1;
v = "";
for(i=0;i<s.length;i++)
v += s.options[i].value + ":" + s.options[i].text + "\n";
alert(v);
}
function move(m) {
if(m == 1) {
ss1 = document.all.s1;
ss2 = document.all.s2;
}
if(m == 2) {
ss1 = document.all.s2;
ss2 = document.all.s1;
}
v = new Array();
k = 0;
for(i=0;i<ss1.length;i++) {
if(ss1.options[i].selected) {
ss2.options[ss2.length] = new Option(ss1.options[i].text,ss1.options[i].value);
ss2.options[ss2.length-1].selected = true // add By cpp2017
v[k] = i;
k++;
}
}
for(i=v.length-1;i>=0;i--)
ss1.options[v[i]] = null;
}
</script>
</BODY></HTML>
指定您的URL范式
2009-04-28
原文: Specify your canonical
发表于: 2009年2月12日星期四,12:30 PM
您可能会对URL形式不同造成的重复内容有所担心, 谷歌现在支持一种新的功能,使您可以指定您喜欢的URL格式。如果您的网站通过多种不同形式的URL向访问者提供完全相同或非常类似的内容,那么通过这种 功能您可以自主控制出现在搜索结果中的您网站的URL格式。同时这也有 助于将那些影响您网页声望值的因素更固定地指向您所青睐的URL格式上。
让我们以一个出售瑞典鱼的网页为例,假设我们所青睐的URL格式和所对应的内容是下面这样的:
http://www.example.com/product.php?item=swedish-fish
然而,访问者和谷歌机器人实际上可以通过另外的URL形式访问到这一内容。尽管URL的核心部分与您青睐的URL格式很相近,但是他们依据排序的参数或分类浏览种类的不同而向用户提供略有差别的网页。
http://www.example.com/product.php?item=swedish-fish&category=gummy-candy
或者,也有可能他们有着完全相同的内容,但是URL看起来并不相同,比如下面的URL还带有跟踪参数或者会话ID:
http://www.example.com/product.php?item=swedish-fish&trackingid=1234&sessionid=5678
现在,您可以将如下语句<link rel="canonical" href="http://www.example.com/product.php?item=swedish-fish"/>
加入到其他您不倾向于在搜索引擎出现的URL的<head>代码中,就能指定您喜欢的URL格式。
比如您不希望以下两种URL格式在搜索结果中出现:
http://www.example.com/product.php?item=swedish-fish&category=gummy-candy
http://www.example.com/product.php?item=swedish-fish&trackingid=1234&sessionid=5678
只要您将上文中的
语句加入到上述两个网页的<head>代码部分,那么谷歌就会知道以上两个网址实际上是被建议指向您指定的标准URL: http://www.example.com/product.php?item=swedish-fish上。 其他的URL属性,比如PageRank和相关的其他因素,也都会自动指向该标准URL。
这个标准同时也被其他搜索引擎在抓取和索引您网站时所接受和使用。
以下我们将以FAQ的形式,解答一些您可能存在的疑问:
从强制性与否来说,请问rel="canonical"是一个建议,还是一个指令?
是一个建议。这是一个我们非常自豪的功能,您可以以此提示搜索引擎考虑您对URL格式的喜好。
我能用相对路径来指定我的URL规范么,比如 <link rel="canonical" href="product.php?item=swedish-fish"/>
?
可以,在这里使用相对路径是可以被正确识别的,如果您在代码中指定了link,那么相对路径都会以此base URL为基础。
我可以将URL范式使用在不是完全相同内容的其他网页上吗?
我们允许这些网页之间有些细微差别,比如归在不同类目下的同一产品网页。
如果被指定为规范格式的URL返回404,怎么办呢?
我们会继续访问和抓取您的内容,并应用一些联想功能去寻找一个URL范式,但是我们强烈建议您将一个可访问的URL设置成URL范式。
如果我指定的URL范式并没有被索引会怎样?
就像网络上所有的公共内容一样,我们会努力发现和寻找您指定的URL范式,一旦我们索引到它,我们就会立即将您的rel="canonical"付诸考虑。
我的URL范式可以是一个重定向URL么?
可以,您可以指定一个发生重定向的URL作为URL范式,谷歌会继续跟踪这个重定向并尝试去抓取它。
如果我不小心指定了互相矛盾的URL范式怎么办?
不用担心,我们的算法是很聪明并宽容的,我们会跟踪抓取这个URL范式链,但是我们还是强烈建议您尽快将URL范式指定为特定单一URL形式,从而确保您的搜索结果早日得到优化。
这个link tag可以被用来建议一个在其他域名上的URL么?
不可以。如果您需要转移到一个不同的域名上,那么301永久重定向对您来说更合适。谷歌现在只能认可在不同子域名下的URL范式的指定。所以,站长们可以将www.example.com和example.com, 及help.example.com互相指定为范式,但是不能将example.com和example-widgets.com互相指定为范式。
听起来不错,能给我举一个现实中的例子么?
我们有一个真实的例子wikia.com。比如,您在http://starwars.wikia.com/wiki/Nelvana_Limited 的源代码中可以发现,该网页已经把http://starwars.wikia.com/wiki/Nelvana指定为了URL范式。通过使用rel="canonical",两个网页的PageRank被整合计算,避免了分散计算的流失,同时搜索结果中也只会包含网站管理员所指定的URL形式。
如果您未能应用URL范式指定您心仪的URL形式,您也不要担心,我们会尽我们最大努力,选择一个更优化的URL形式,并将声望等属性值进行相应转移处理,就像我们以前做的那样(英文)。
补充:这个link tag现在也被Ask.com,微软Live Search和Yahoo!搜索等搜索引擎所支持。
