CodeIgnite-Web-开发蓝图-全-
CodeIgnite Web 开发蓝图(全)
原文:
zh.annas-archive.org/md5/60926f4a40c03f166ee550fb9dbe94b3译者:飞龙
前言
本书包含八个项目。这些项目是故意设计成可以扩展和修改的,也就是说,尽可能,我尝试以这种方式构建每个项目,以便你可以轻松地应用自己的要求,而且你不需要连续几周研究代码才能弄清楚它是如何工作的。
按照目前的章节顺序进行,当然会得到一个功能齐全的项目,但总有扩展的空间,如果你选择扩展并添加功能,你可以轻松做到这一点。
相反,每个项目都可以拆解,特定的代码段可以被提取出来并用于与本书完全不同的其他项目中。我已经以几种方式做到了这一点——尽可能保持代码冗长且简单。代码被保持在小型、可管理的块中;我尽量使所有代码与 CodeIgniter 文档中使用的代码示例保持一致(因此,它将遵循熟悉的流程和外观)。
我还尝试为每个项目编写文档。每章的开始将包含每个项目的线框、网站地图、文件树布局和每个表中每个项目的数据字典,并在代码本身中添加了代码的解释。
我试图讨论为什么某个东西存在,而不是仅仅对某个东西的陈旧解释;这样做是为了希望解释为什么某个东西存在能帮助你理解代码与你可能考虑的任何变更或修订的相关性。
本书涵盖的内容
第一章,简介和共享项目资源,介绍了本书并记录了一个初始的开发环境——安装 Twitter Bootstrap、安装 CodeIgniter 以及开发本书所有章节都使用的几个共享通用资源。
第二章,URL 缩短器,讨论了创建一个允许用户输入 URL 的应用程序。该应用程序将编码此 URL 并生成一个新的、更短的 URL,并在其后附加一个唯一的代码——然后将其保存到数据库中。这个 URL 将被提供给用户以供他们分发和使用。一旦点击,我们将开发的应用程序将查看 URL,在该 URL 中找到唯一的代码,并在数据库中查找它。如果找到,应用程序将加载原始 URL 并将用户重定向到它。
第三章,讨论论坛,讨论了创建一个允许用户创建初始问题或提议的应用程序。这个问题将显示在一种公告板上;这是讨论线程的开始。其他用户可以点击这些用户的讨论并回复他们,如果他们愿意的话。
第四章, 创建一个照片分享应用程序,讲述了创建一个允许用户上传图片的小型应用程序。然后生成一个唯一的 URL 并将其与上传文件的详细信息一起保存到数据库中。这些信息提供给用户以供分发。一旦点击该 URL,上传的图片将从文件系统中检索并显示给用户。
第五章, 创建一个新闻订阅注册,包含一个项目,允许用户注册到联系人数据库中,在这种情况下,是一个新闻订阅注册数据库。用户可以修改他们的设置(设置可以是任何你希望的内容:他们希望收到的电子邮件内容类型或他们是否希望接收 HTML 或纯文本电子邮件)。应用程序甚至支持从未来的新闻通讯中退订。
第六章, 创建一个身份验证系统,包含一个用于管理您可能开发的系统中的用户的应用程序,可能是本书中最大的章节。提供了一个简单的 CRUD 环境,允许您添加、编辑和删除用户。反过来,用户可以自行注册,甚至在他们忘记密码时重置密码。
第七章, 创建一个电子商务网站,讲述了一个小型但简洁的电子商务应用程序,该应用程序利用 CodeIgniter 的购物车类来支持一个简单的商店。用户可以通过不同的类别过滤产品,将产品添加到购物车中,修改购物车中的项目(调整项目数量),或者完全从购物车中删除项目。
第八章, 创建一个待办事项列表,讲述了创建一个允许用户创建他们需要完成的任务的应用程序。任务可以指定一个截止日期,即一种截止日期。任务在 HTML 表中显示。逾期任务的行被赋予红色背景色以表明其重要性。完成的任务可以设置为已完成,并通过划线表示已完成。最后,可以删除任务以删除旧的和不需要的项目。
第九章, 创建一个职位发布板,讲述了如何创建一个职位发布板。鼓励用户通过填写 HTML 表单在职位发布板上发布广告。表单的内容将被验证并添加到当前可用的职位数据库中。寻找工作的其他用户可以搜索这些职位。这些用户可以搜索所有职位,或者输入搜索查询以查看是否存在特定的职位。
本书所需条件
以下是需要的东西:
-
您需要一个计算机和一个*AMP 环境(MAMP、WAMP、LAMP 等)
-
本书所需的 CodeIgniter 框架
本书面向的对象
简而言之,这本书适合任何人;您不需要有之前的 CodeIgniter 经验——然而,这显然会有帮助。话虽如此,这本书并不是真正针对初学者的,但这绝对不是障碍;别忘了,CodeIgniter 是一个易于使用的框架,并且可以很容易地掌握。
惯例
在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名如下所示:“创建或打开一个 .htaccess 文件。”
代码块设置如下:
$this->load->model('Urls_model');
if ($res = $this->Urls_model->save_url($data)) {
$page_data['success_fail'] = 'success';
$page_data['encoded_url'] = $res;
} else {
$page_data['success_fail'] = 'fail';
}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
if ($this->form_validation->run() == FALSE) {
// Set initial values for the view
$page_data = array('success_fail' => null,
'encoded_url' => false);
任何命令行输入或输出都应如下编写:
user@server:/path/to/codeigniter$ php tools/spark install -v1.0.0 example-spark
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“输入以下命令并点击确定。”
注意
警告或重要注意事项以如下方式显示。
小贴士
小贴士和技巧看起来像这样。
读者反馈
我们欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中获得最大价值的标题。
如要向我们发送一般反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书籍标题。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 书籍的骄傲拥有者,我们有许多事情可以帮助您充分利用您的购买。
下载示例代码
您可以从www.packtpub.com下载您所购买的 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
错误清单
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
侵权
互联网上对版权材料的侵权是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即向我们提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌侵权材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章. 引言和共享项目资源
这一章的目的是什么?我希望用这一章作为本书所有其他章节和项目的入门指南。我希望你们将引言作为一个包含本书中所有项目共享资源的通用资源使用。
引言将涵盖后续章节中项目所需的第三方软件、库、助手等安装内容。将这些资源放在这一章,可以避免项目被重复的代码淹没,并使项目代码尽可能保持简洁。
在本章中,我们将涵盖以下主题:
-
本书概述
-
下载 CodeIgniter
-
下载并安装 Twitter Bootstrap
-
创建适用于所有项目的通用页眉和页脚文件
-
安装 Sparks
-
通用语言项
通用资源
本书使用的通用资源将在接下来的章节中讨论。
Twitter Bootstrap
本书每一章中的项目都使用 Twitter Bootstrap。我们将下载 Bootstrap,并在我们的文件系统中找到一个合适的位置。然后我们将创建页眉和页脚文件。本书中的所有项目都将调用这些页眉和页脚文件(使用 CodeIgniter 函数$this->load->view()来显示视图)。然而,这些项目实际上并不包含页眉和页脚的代码——只有页眉和页脚之间的有效代码(你可能认为是在<body>和</body>标签之间的内容)将在每个项目中详细说明。
页眉和页脚
各个项目的菜单将有所不同。在页眉文件中,我们将包含显示菜单的代码,但实际的菜单 HTML 内容将包含在每个项目的章节中。页脚文件包含每个页面的关闭 HTML 标记。
下载 CodeIgniter
我们需要一份 CodeIgniter 的副本来开始。这本书并不是针对初学者的,所以你已经有 CodeIgniter 安装副本或者至少足够了解如何跳过这一部分的可能性相当高;然而,简要地介绍 CodeIgniter 的安装是有意义的,这样我们就可以在后续章节中有所讨论!
首先,访问ellislab.com/codeigniter/user-guide/installation/downloads.html。你会看到以下截图所示的内容。这是 CodeIgniter 下载页面。在撰写本文时,当前的 CodeIgniter 版本是 2.2.0;这就是为什么截图在 2.2.0 版本旁边写着当前版本;然而,无论你在阅读本书时哪个版本是最新的,你都应该使用这个版本。
点击当前版本链接,CodeIgniter 将开始下载。

下载完成后,导航到您保存文件的位置;这将是一个压缩归档文件。解压缩它并将该文件的内容移动到您的网络根目录下的一个文件夹中。
关于路由、配置和数据库使用的具体细节在每个章节中都有(这些细节特定于该章节)。
PHP 的新版本
如果您在 PHP 的新版本上运行 CodeIgniter,可能会遇到错误。有一个针对此问题的解决方案,在ellislab.com/forums/viewthread/244510/上有解释。一个名为milan.petrak的帖子作者描述了一个解决方案。可以总结如下:
-
打开
/path/to/codeigniter/system/core/common.php文件并找到第 257 行。 -
找到以下行:
$_config[0] =& $config; with return $_config[0]; return $_config[0] =& $config; -
保存
common.php文件。
这可能在 CodeIgniter 的后续版本中得到永久修复,但到目前为止,这是修复方法。
安装 Twitter Bootstrap
Twitter Bootstrap 是一个前端 HTML5 框架,它允许任何人轻松构建可靠的界面。在撰写本文时,所使用的 Bootstrap 版本是 3.1.1。
我们将在本书中使用 Twitter Bootstrap 为所有视图文件和模板提供框架。我们将探讨如何下载 Twitter Bootstrap 以及如何在 CodeIgniter 文件系统中安装它。
首先,我们需要下载 Twitter Bootstrap 文件。为此,请执行以下步骤:
-
打开您的网络浏览器,并访问 Bootstrap 下载链接
getbootstrap.com/getting-started。您将看到以下截图所示的内容:![安装 Twitter Bootstrap]()
-
找到下载 Bootstrap链接并点击它(如前一个截图所示);下载将自动开始。
下载完成后,转到您的机器上的位置并解压缩归档文件。在解压缩的文件中,您应该看到以下结构类似的内容:
bootstrap/
├── css/
│ ├── bootstrap-theme.css
│ ├── bootstrap-theme.css.map
│ ├── bootstrap-theme.min.css
│ └── bootstrap.css
│ └── bootstrap.css.map
│ └── bootstrap.min.css
├── js/
│ ├── bootstrap.js
│ └── bootstrap.min.js
└── fonts/
├── glyphicons-halflings-regular.eot
├── glyphicons-halflings-regular.svg
├── glyphicons-halflings-regular.ttf
└── glyphicons-halflings-regular.woff
将bootstrap文件夹移动到您的 CodeIgniter 安装中,以便文件层次结构看起来如下:
/path/to/web/root/
├── application/
└── views/
├── common
├── header.php
├── footer.php
├── system/
├── bootstrap/
├── index.php
├── license.txt
在前面的树结构中,application和system目录与 CodeIgniter 有关,同样,index.php和license.txt文件也是如此;然而,bootstrap目录包含您 Bootstrap 下载的内容。
我还指出了常见header.php和footer.php文件的位置。这些文件在本书中到处使用,并作为其他每个视图文件的包装器。
从地址栏移除 index.php
当 CodeIgniter 运行时,可以从网络浏览器地址栏中移除index.php。这可以通过以下步骤完成:
-
创建或打开一个
.htaccess文件。如果尚未存在.htaccess文件,您可以使用以下方法创建一个:-
Linux/Mac:打开一个终端窗口并输入以下命令:
touch /path/to/CodeIgniter/.htaccess -
Windows:在你的 CodeIgniter 根目录中创建一个文本文件,命名为
file.htaccess。按住 Windows 键,然后按 R 打开 运行 对话框。输入以下命令并点击 确定:ren "C:\path\to\CodeIgniter\file.htaccess" .htaccess
-
-
一旦打开
.htaccess文件,请在文件顶部写入以下行:<IfModule mod_rewrite.c> RewriteEngine on RewriteCond $1 !^(index\.php|images|robots\.txt) RewriteRule ^(.*)$ index.php/$1 [L] </IfModule>
安装和使用 Sparks
很长时间以来,你必须在网上搜索并从各种地方下载代码——博客、代码仓库等等——以找到和使用扩展、库以及其他有用的代码片段。有用的 CodeIgniter 安装散布在互联网上;因此,它们可能很难找到。Sparks 作为 CodeIgniter 扩展的单一点参考。安装和使用都很简单,并包含数千个有用的 CodeIgniter 扩展:
-
如果你正在使用 Mac 或 Linux,那么命令行界面对你来说是开放的。使用系统上的终端应用程序,导航到你的 CodeIgniter 应用程序的根目录,并输入以下行:
php -r "$(curl -fsSL http://getsparks.org/go-sparks)"如果你的安装成功,你应该会看到以下类似的内容:
user@server:/path/to/codeigniter$ php -r "$(curl -fsSL http://getsparks.org/go-sparks)" Pulling down spark manager from http://getsparks.org/static/install/spark-manager-0.0.9.zip ... Pulling down Loader class core extension from http://getsparks.org/static/install/MY_Loader.php.txt ... Extracting zip package ... Cleaning up ... Spark Manager has been installed successfully! Try: `php tools/spark help` -
如果你正在使用 Windows,那么你需要手动下载并解压 Sparks;为此,请遵循以下说明或查看 GetSparks 网站上的最新版本说明:
-
在顶级(根目录)或你的 CodeIgniter 目录中创建一个名为
tools的文件夹。 -
前往 常规安装 部分,下载 Sparks 包。
-
将下载的文件解压到你在步骤 1 中创建的
tools文件夹中。 -
从
getsparks.org/static/install/MY_Loader.php.txt下载 Loader 类扩展。 -
将文件
MY_Loader.php.txt重命名为MY_Loader.php,并将其移动到你的 CodeIgniter 实例中的application/core/MY_Loader.php位置。
-
现在,Sparks 已安装在你的 CodeIgniter 实例中,你可以开始安装扩展和包。
要从 Sparks 安装包,请在命令行窗口中输入以下内容:
php tools/spark install [Package Version] Spark Name
在这里,Package Version 是你希望安装的 Spark 的特定版本。你不需要声明版本,如果你不指定,Sparks 将默认下载最新版本。Spark Name 是你希望安装的 Spark 的名称;例如,要安装默认安装中包含的 example-spark(版本 1.0.0),在命令行窗口中输入以下内容:
php tools/spark install -v1.0.0 example-spark
如果安装成功,你应该会看到以下类似的内容:
user@server:/path/to/codeigniter$ php tools/spark install -v1.0.0 example-spark
[ SPARK ] Retrieving spark detail from getsparks.org
[ SPARK ] From Downtown! Retrieving spark from Mercurial repository at https://url/of/the/spark/repo
[ SPARK ] Spark installed to ./sparks/example-spark/1.0.0 - You're on fire!
创建共享的头部和尾部视图
本书中的每个项目都将使用我们在这里创建的通用头部和尾部文件;导航菜单将因每个项目而异,因此我们将在这本书的项目章节中构建这些文件。但现在,让我们看看通用的头部和尾部文件:
-
在
/path/to/codeigniter/application/views/common/创建header.php文件,并将以下代码添加到其中:<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <link rel="shortcut icon" href="<?php echo base_url('bootstrap/ico/favicon.ico'); ?>"> <title><?php echo $this->lang->line('system_system_name'); ?></title> <!-- Bootstrap core CSS --> <link href="<?php echo base_url('bootstrap/css/bootstrap.min.css'); ?>" rel="stylesheet"> <!-- Bootstrap theme --> <link href="<?php echo base_url('bootstrap/css/bootstrap-theme.min.css'); ?>" rel="stylesheet"> <!-- Custom styles for this template --> <link href="<?php echo base_url('bootstrap/css/theme.css');?>" rel="stylesheet"> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="img/html5shiv.js"></script> <script src="img/respond.min.js"></script> <![endif]--> </head> <body role="document"> <!-- END header.php --> <div class="container theme-showcase" role="main">前面的视图文件包含文档头部部分的 HTML。也就是说,这个 HTML 在本书的每个项目中都会使用,它包含了从打开的
html标签、打开和关闭head标签,到打开body标签的 HTML 标记。 -
在
/path/to/codeigniter/application/views/common/创建footer.php文件,并将以下代码添加到其中:</div> <!-- /container --> <link href="<?php echo base_url('bootstrap/css/bootstrap.min.css'); ?>" rel="stylesheet"> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="img/jquery.min.js"></script> <script src="img/bootstrap.min.js');?>"></script> <script src="img/docs.min.js');?>"></script> </body> </html>前面的代码块包含用于本书中每个项目的视图文件的 HTML 标记。
通用语言项
在本书的每个项目中,我们将创建一个特定的语言文件,包含与该特定项目相关的特定语言项。然而,也有一些通用语言元素,我们不会在每个项目中重复(因为没有必要);因此,我们可以在这里使用它们。
语言项主要涵盖屏幕元素,如一般导航、一般错误和成功消息,以及 CRUD 操作(编辑、删除等)。
考虑到这一点,让我们继续创建基础语言文件,它将作为本书各章节的模板。
在 /path/to/codeigniter/application/language/english/ 创建 en_admin_lang.php 文件,并将以下代码添加到其中:
// Common form elements
$lang['common_form_elements_next'] = "Next...";
$lang['common_form_elements_save'] = "Save...";
$lang['common_form_elements_cancel'] = "Cancel";
$lang['common_form_elements_go'] = "Go...";
$lang['common_form_elements_go_back'] = "Go back";
$lang['common_form_elements_submission_error'] = "There were errors with the form:";
$lang['common_form_elements_success_notifty'] = "Success:";
$lang['common_form_elements_error_notifty'] = "Error:";
$lang['common_form_elements_actions'] = "Actions";
$lang['common_form_elements_action_edit'] = "Edit";
$lang['common_form_elements_action_delete'] = "Delete";
$lang['common_form_elements_active'] = "Active";
$lang['common_form_elements_inactive'] = "Inactive";
$lang['common_form_elements_seccessful_change'] = "Your changes have been saved";
$lang['common_form_elements_seccessful_delete'] = "The item has been deleted";
$lang['common_form_elements_yes'] = "Yes";
$lang['common_form_elements_no'] = "No";
$lang['common_form_elements_to'] = "to";
$lang['common_form_elements_from'] = "from";
$lang['common_form_elements_history'] = "History";
前面的语言项主要是针对 HTML 表单和数据表,例如提交、编辑、删除和历史按钮的文本。还包括一般的错误或成功消息。如果您愿意,可以随意添加。
创建 MY_Controller 文件
本书中的所有项目都使用 MY_Controller 文件;这对所有项目都是一样的。
在 /path/to/codeigniter/application/core/ 创建 MY_Controller.php 文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class MY_Controller extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->helper('form');
$this->load->helper('url');
$this->load->helper('security');
$this->load->helper('language');
// Load language file
$this->lang->load('en_admin', 'english');
}
}
如您所见,我们加载了适用于所有项目的通用助手,例如 form 助手和 language 助手,以及其他一些助手。语言文件也在这里加载。
项目中的所有控制器都扩展自这个 MY_Controller 文件,而不是默认的 CI_Controller 文件。
自动加载通用系统资源
我们还自动加载了各种资源,例如数据库访问和会话管理支持。我们需要指定我们正在使用这些资源。
在您的文本编辑器中打开位于 /path/to/codeigniter/application/config/ 的 autoload.php 文件,并找到以下行:
$autoload['libraries'] = array();
将此行替换为以下内容:
$autoload['libraries'] = array('database', 'session');
这将确保访问数据库和管理会话所需的资源始终在我们身边。
安全考虑
无论你正在编写什么程序,你的两个主要优先事项都是安全和可维护性;也就是说,你的应用程序应该尽可能安全,并且应该以其他人可以轻松编程和扩展你正在做的事情的方式编写。我无法讨论可维护性——这取决于你——但我可以就 CodeIgniter 和安全提供指导。
然而,我应该说的是,没有任何安全措施是百分之百万无一失的。即使是花费数亿美元在系统上的银行和安全机构仍然会被黑客攻击,我们有什么机会呢?好吧,我们能做的就是尽量减少有人可能做些可能危害我们的代码或数据库的事情的机会。
移动系统文件夹
你应该将你的系统文件夹移出你的网站根目录。这是为了让除了网站服务器之外的其他任何东西都尽可能难以访问。看看主index.php文件中的那一行:
$system_path = 'system';
确保将前面的行修改为以下内容:
$system_path = '../system';
因此,如果我们把system文件夹从网站根目录向上移动一级,我们会使用../约定,将其添加到system之前。
错误信息
显然,你不想真的向外界显示错误信息。随着时间的推移,每个人都会了解你网站的架构以及它的弱点在哪里,尤其是如果你允许在生产环境中显示 SQL 错误的话。
因此,你应该将主index.php文件中的环境变量从development改为production。这将抑制错误报告;404 和 500 错误仍然会被捕获并正常显示,但 SQL 错误和其他类似错误将被抑制。
对于这个,看看index.php文件中的以下代码:
define('ENVIRONMENT', 'development');
/*
*---------------------------------------------------------------
* ERROR REPORTING
*---------------------------------------------------------------
*
* Different environments will require different levels of error reporting.
* By default development will show errors but testing and live will hide them.
*/
if (defined('ENVIRONMENT'))
{
switch (ENVIRONMENT)
{
case 'development':
error_reporting(E_ALL);
break;
case 'testing':
case 'production':
error_reporting(0);
break;
default:
exit('The application environment is not set correctly.');
}
}
看看粗体行(第一行)。这一行已经将 CodeIgniter 设置为开发模式;要改为其他任何模式(特别是实时模式),将粗体行改为以下内容:
define('ENVIRONMENT', 'production');
所有的错误现在都将被抑制。
查询绑定
查询绑定是一个好主意;它使你的查询更容易阅读;使用 CodeIgniter 绑定的查询会自动转义,从而提高查询的安全性。语法很简单;例如,考虑以下查询:
$query = "SELECT * FROM `users` WHERE user_email = ? AND user_level = ?";
看看查询的末尾;你可以看到我们在通常使用变量的地方使用了问号;这通常看起来像这样:
$query = "SELECT * FROM `users` WHERE user_email = $user_email AND user_level = $user_level";
CodeIgniter 是如何知道问号的意思,以及 CodeIgniter 是如何将正确的值放入查询中的?看看第二行:
$this->db->query($query, array($user_email, $user_level));
这就是它如何将值匹配到正确的问号。我们使用$this->db->query()CodeIgniter 函数,向它传递两个参数。第一个是$query变量(包含实际查询),第二个是一个数组。数组中的每个位置都与 SQL 字符串中问号的位置相对应。
摘要
现在,你会发现我们已经准备好开始这本书,并且一切准备就绪,准备应对每一章。
记住,每个章节的代码以及每个章节的 SQL 都可以在 Packt 网站上找到;这将帮助你避免需要手动输入所有这些内容。
第二章. URL 缩短器
互联网上有很多 URL 缩短工具;然而,总有一些空间可以加入一些乐趣,有时个人或公司需要自己的解决方案,而不是仅仅使用外部提供商。本章的项目正是如此——开发一个任何人都可以使用的基于 CodeIgniter 的 URL 缩短器。
要制作这个应用程序,我们需要做一些事情:我们将创建两个控制器,一个用于创建缩短的 URL,另一个用于将缩短的 URL 重定向到其实际的网络位置。
我们将创建语言文件来存储文本,为将来可能实现的多语言支持打下基础。
我们还将修改config/routes.php文件——这是为了确保缩短后的 URL 尽可能短。
然而,这个应用程序,以及本书中的其他所有应用程序,都依赖于我们在第一章中做的基本设置,引言和共享项目资源;虽然你可以将大量代码块放入你几乎已经拥有的任何应用程序中,但请记住,我们在第一章中做的设置是这个章节的基础。
本章我们将涵盖以下主题:
-
设计和线框图
-
创建数据库
-
调整
routes.php文件 -
创建模型
-
创建视图
-
创建控制器
-
整合所有内容
所以,无需多言,让我们开始吧。
设计和线框图
在我们开始构建之前,我们应该始终看看我们计划构建什么。
首先,简要描述我们的意图:我们计划构建一个应用程序,将显示一个简单的表单给用户。我们将鼓励用户在表单中输入 URL 并提交该表单。
将生成一个唯一代码并将其与用户输入的 URL 关联起来。这个 URL 和唯一代码将被保存到数据库中。
用户将看到一个包含我们刚刚生成的唯一代码的新 URL。这个唯一代码将被附加到我们正在构建的应用程序的 URL 上。如果用户(或任何人)点击这个链接,应用程序将在数据库中查找这个唯一代码。如果唯一代码存在,它将把用户重定向到与该唯一代码关联的原始 URL。
因此,让我们看看一些线框图,以帮助我们理解这可能在屏幕上看起来是什么样子:

这是用户将看到的第一个页面。用户被邀请在文本框中输入 URL 并点击Go按钮。
页面将被提交,并将生成代码。这两段代码和原始 URL 都将保存到数据库中。然后,用户将看到我们为他们创建的新 URL。他们可以将该 URL 复制到剪贴板(用于粘贴到电子邮件等)或立即点击它。以下截图显示了这一过程:

文件概览
我们将为这个应用程序创建六个文件,如下所示:
-
/path/to/codeigniter/application/models/urls_model.php:此文件提供对数据库的访问,并允许我们创建url_code,将记录保存到数据库中,并从数据库中检索原始 URL。 -
/path/to/codeigniter/application/views/create/create.php:此文件为我们提供界面,用户界面表单以及任何需要通知用户其操作或系统操作的提示信息。 -
/path/to/codeigniter/application/views/nav/top_nav.php:此文件在页面顶部提供导航栏。 -
/path/to/codeigniter/application/controllers/create.php:此文件对用户输入的 URL 进行验证检查,调用任何助手等。 -
/path/to/codeigniter/application/controllers/go.php:此文件为缩短 URL 提供支持。它从 URI(第一部分)获取唯一的代码参数,将其发送到Urls_model,如果存在,则将用户重定向到相关的url_address。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php:此文件为应用程序提供语言支持。
前六个文件的文件结构如下:
application/
├── controllers/
│ ├── create.php
│ ├── go.php
├── models/
│ ├── urls_model.php
├── views/create/
│ ├── create.php
├── views/nav/
│ ├── top_nav.php
├── language/english/
├── en_admin_lang.php
小贴士
下载示例代码
您可以从www.packtpub.com下载您购买的所有 Packt 出版物的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
创建数据库
好的,您应该已经按照第一章中所述设置了 CodeIgniter 和 Bootstrap,简介和共享项目资源。如果没有,那么您应该知道本章中的代码是专门针对第一章中的设置编写的。然而,这并不是世界末日——代码可以轻松应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到您的数据库中:
CREATE DATABASE `urls`;
USE `urls`;
CREATE TABLE `urls` (
`url_id` int(11) NOT NULL AUTO_INCREMENT,
`url_code` varchar(10) NOT NULL,
`url_address` text NOT NULL,
`url_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`url_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
小贴士
如果您不想将数据库命名为 urls,也可以不这样做。如果您愿意,可以将其重命名为其他名称;只需确保相应地更新 config/database.php 文件。
让我们来看看数据库中的每个项目代表什么:
| 元素 | 描述 |
|---|---|
url_id |
这是主键。 |
url_code |
这包含由 urls_model.php 的 save_url() 函数生成的唯一代码。这是附加到缩短 URL 上的代码。 |
url_address |
这是在 create.php 视图文件中用户实际输入的 URL。它将是用户将被重定向到的 URL。 |
url_created_at |
这是记录添加时创建的 MySQL 时间戳。这是必要的,这样我们就有了一个关于记录创建时间的概念;此外,它还给我们提供了一个方法,如果您愿意,可以使用 cron 脚本来清除数据库中的旧记录。 |
我们还需要对 config/database.php 文件进行修改——即设置数据库访问详情、用户名、密码等。
打开 config/database.php 文件并找到以下行:
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'your username';
$db['default']['password'] = 'your password';
$db['default']['database'] = 'urls';
编辑前几行的值。确保用更适合您设置和情况的值替换这些值——因此输入您的用户名、密码等。
调整 routes.php 文件
我们希望有短 URL——实际上越短越好。用户点击的 URL 如果尽可能短会更好;因此,如果我们从 URL 中移除某些内容以使其更短,那将是一个好主意——例如,控制器名称和函数名称。我们将使用 CodeIgniter 的路由功能来实现这一点。这可以按以下方式完成:
-
打开
config/routes.php文件进行编辑并找到以下行(文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。最初,在 CodeIgniter 应用程序中,默认控制器设置为
welcome。然而,我们不需要那个;相反,我们希望默认控制器是create。所以,考虑以下行:$route['default_controller'] = "welcome";用以下代码替换它:
$route['default_controller'] = "create"; -
我们还需要为
go控制器设置路由规则。我们需要移除控制器和函数名称(通常在 URI 中的前两个参数)。以下有两行代码(以粗体突出显示);将这些两行代码添加到404_override路由下面,这样文件现在看起来如下所示:$route['default_controller'] = "create"; $route['404_override'] = ''; $route['create'] = "create/index"; $route['(:any)'] = "go/index";
现在,那些细心的读者中的一些人可能已经看到了最后一行的 (:any) 类型;有些人可能想知道那是什么意思。
CodeIgniter 支持一种简单的正则表达式类型,这使得对未知 URL 的路由变得容易得多。(:any) 类型告诉 CodeIgniter,任何未定义的 URI 模式(我们也在定义 create)都应该路由到 go/index。
创建模型
Urls_model 包含三个函数;显然它包含我们的 __construct() 函数,但我们目前不关注它,因为它没有做任何事情,只是引用了其父类。
相反,让我们看看两个函数 save_url() 和 fetch_url()。正如它们的名称所暗示的,一个将信息保存到数据库中,另一个从数据库中获取信息。现在,让我们去创建代码,稍后我们将详细讨论每个函数的功能:创建 urls_model.php 模型文件,并向其中添加以下代码:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Urls_model extends CI_Model {
function __construct() {
parent::__construct();
}
function save_url($data) {
/*
Let's see if the unique code already exists in
the database. If it does exist then make a new
one and we'll check if that exists too.
Keep making new ones until it's unique.
When we make one that's unique, use it for our url
*/
do {
$url_code = random_string('alnum', 8);
$this->db->where('url_code = ', $url_code);
$this->db->from('urls');
$num = $this->db->count_all_results();
} while ($num >= 1);
$query = "INSERT INTO `urls` (`url_code`, `url_address`) VALUES (?,?) ";
$result = $this->db->query($query, array($url_code, $data['url_address']));
if ($result) {
return $url_code;
} else {
return false;
}
}
function fetch_url($url_code) {
$query = "SELECT * FROM `urls` WHERE `url_code` = ? ";
$result = $this->db->query($query, array($url_code));
if ($result) {
return $result;
} else {
return false;
}
}
}
让我们看看 save_url()。注意 PHP 构造 do...while;它看起来像以下这样:
do {
// something
} while ('…a condition is not met');
这意味着 在条件不满足的情况下执行某些操作。
现在,考虑到这一点,思考我们的问题。我们必须将用户在表单中输入的 URL 与一个唯一值关联起来。我们将使用这个唯一值来表示真实的 URL。
现在用连续的数字(1,2,3,… 1000)作为我们的唯一值是没有意义的,因为有人可以遍历这些数字并获取到每个人的 URL。这可能不是如此可怕的安全风险,因为据推测,所有页面都可以从互联网上访问,但这仍然不是一个好主意。因此,唯一值不仅必须是唯一的,还必须是随机的,并且不容易被过路人猜测。此外,这个唯一值必须在数据库中只存在一次。
为了确保这一点,我们必须检查唯一值是否已经存在,如果存在,就生成一个新的唯一代码,并在数据库中再次检查。
因此,让我们更详细地看看 save_url() 函数中的 do while 构造。以下是代码:
do {
$url_code = random_string('alnum', 8);
$this->db->where('url_code = ', $url_code);
$this->db->from('urls');
$num = $this->db->count_all_results();
} while ($num>= 1);
我们使用 CodeIgniter 的 String 辅助函数及其 random_string() 函数(确保你在控制器构造函数中使用 $this->load->helper('string'); 包含 String 辅助函数)。random_string() 函数将创建(正如其名称所暗示的)一个随机字符串,我们将使用它作为唯一代码。
在这种情况下,我们要求 random_string() 给我们一个由数字和大小写字母组成的字符串;这个字符串的长度不应超过 8 位数字。
然后,我们查看数据库以查看我们是否已经为 random_string() 生成了一个代码。我们将使用 $this->db->count_all_results(); CodeIgniter 函数来计算匹配结果的数量。
如果唯一字符串已经存在,那么 $this->db->count_all_results(); 返回的数字将等于 1(因为它已经存在)。如果发生这种情况,我们将回到 do while 构造的开始,并重新生成一个新代码。
我们会一直这样做,直到找到一个在数据库中不存在的代码。当我们找到时,我们就跳出 do while 循环,并将这个唯一代码以及原始 URL 保存到数据库中。
现在让我们看看fetch_url()函数。我们想查看数据库中是否有与用户输入的$url_code(在这种情况下,他们点击了一个 URL)相对应的记录。fetch_url()函数接受由控制器传递给它的函数参数$url_code,并在数据库中查找它。如果找到,则将整个记录(表行)返回给控制器;如果没有找到,则返回 false。控制器相应地处理这个 false 结果(显示错误)。
创建视图
在本节中,我们将创建两个视图文件,如下所示:
-
/path/to/codeigniter/application/models/views/create/create.php -
/path/to/codeigniter/application/models/views/nav/top_nav.php
不要忘记导航文件(views/nav/top_nav.php)是本书每个章节独有的。
创建视图文件–views/create/create.php
create.php文件是用户创建短网址时看到的视图文件;它包含用户将输入原始 URL 的 HTML 表单以及任何交互元素,如错误或成功消息。
创建create/create.php视图文件,并将以下代码添加到其中:
<div class="page-header">
<h1><?php echo $this->lang->line('system_system_name'); ?></h1>
</div>
<p><?php echo $this->lang->line('encode_instruction_1'); ?></p>
<?php if (validation_errors()) : ?>
<?php echo validation_errors(); ?>
<?php endif ; ?>
<?php if ($success_fail == 'success') : ?>
<div class="alert alert-success">
<strong><?php echo $this->lang->line('common_form_elements_success_notifty'); ?></strong> <?php echo $this->lang->line('encode_encode_now_success'); ?>
</div>
<?php endif ; ?>
<?php if ($success_fail == 'fail') : ?>
<div class="alert alert-danger">
<strong><?php echo $this->lang->line('common_form_elements_error_notifty'); ?> </strong> <?php echo $this->lang->line('encode_encode_now_error'); ?>
</div>
<?php endif ; ?>
<?php echo form_open('create') ; ?>
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input type="text" class="form-control" name="url_address" placeholder="<?php echo $this->lang->line('encode_type_url_here'); ?>">
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><?php echo $this->lang->line('encode_encode_now'); ?></button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div><!-- /.row -->
<?php echo form_close() ; ?>
<br />
<?php if ($encoded_url == true) : ?>
<div class="alert alert-info">
<strong><?php echo $this->lang->line('encode_encoded_url'); ?> </strong>
<?php echo anchor($encoded_url, $encoded_url) ; ?>
</div>
<?php endif ; ?>
创建视图文件–views/nav/top_nav.php
本书中的每个项目都在页面顶部有自己的导航栏。这一章也不例外,尽管这个项目的实际导航选项有限——主要是因为我们正在构建的应用程序实际上只做了一件事。因此,创建nav/top_nav.php视图文件,并将以下代码添加到其中:
<!-- Fixed navbar -->
<div class="navbarnavbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><?php echo $this->lang->line('system_system_name'); ?></a>
</div>
<div class="navbar-collapse collapse">
<ul class="navnavbar-nav">
<li class="active"><?php echo anchor('create', 'Create') ; ?></li>
</ul>
</div><!--/.navbar-collapse -->
</div>
</div>
<div class="container theme-showcase" role="main">
创建控制器
在这个项目中,有两个控制器。第一个create控制器负责向用户显示初始表单并验证输入。第二个go控制器将用户重定向到原始 URL。
不要忘记控制器扩展了core/MY_Controller.php文件,并继承了那里加载的辅助函数。
创建控制器文件–controllers/create.php
本项目中的create控制器负责与用户的初始接触;也就是说,它加载视图文件views/create.php(向用户显示表单)并处理输入——验证等。我们将在下一部分查看它,但首先让我们创建控制器:
创建控制器文件create.php,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Create extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
$this->form_validation->set_rules('url_address', $this->lang->line('create_url_address'), 'required|min_length[1]|max_length[1000]|trim');
if ($this->form_validation->run() == FALSE) {
// Set initial values for the view
$page_data = array('success_fail' => null,
'encoded_url' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
} else {
// Begin to build data to be passed to database
$data = array(
'url_address' => $this->input->post('url_address'),
);
$this->load->model('Urls_model');
if ($res = $this->Urls_model->save_url($data)) {
$page_data['success_fail'] = 'success';
$page_data['encoded_url'] = $res;
} else {
// Some sort of error, set to display error message
$page_data['success_fail'] = 'fail';
}
// Build link which will be displayed to the user
$page_data['encoded_url'] = base_url() . '/' . $res;
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
}
}
}
因此,create控制器为我们做了以下事情:
-
表单验证,检查输入是否符合预期
-
将
url_address打包好,以便传递给Urls_model -
处理任何错误和成功消息
让我们通过查看控制器加载时发生的情况来遍历控制器。由于我们使用 CodeIgniter 的表单验证过程,您会知道($this->form_validation->run() == FALSE)将触发显示视图文件,如下所示:
if ($this->form_validation->run() == FALSE) {
// Set initial values for the view
$page_data = array('success_fail' => null,
'encoded_url' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
} else {
...
在我们显示视图文件之前,我们为视图文件create/create.php设置了一些变量值。这些值决定了成功和错误消息的显示方式。这些值存储在$page_data数组中(参见前面代码中的粗体文本)。
假设表单验证没有错误,我们从 POST 数组中获取url_address并将其打包成一个数组,如下所示:
$data = array(
'url_address' => $this->input->post('url_address'),
);
我们随后加载Urls_model并将$data数组发送到Urls_model的save_url()函数:
$this->load->model('Urls_model');
if ($res = $this->Urls_model->save_url($data)) {
$page_data['success_fail'] = 'success';
$page_data['encoded_url'] = $res;
} else {
$page_data['success_fail'] = 'fail';
}
注意
我已经移除了注释,以便于解释。
当操作成功时,模型将返回我们存储在$page_data['encoded_url']中的url_code。
然后将create/create.php视图文件传递给它,该文件将向用户显示成功消息和他们的缩短后的 URL。
创建控制器文件–控制器/go.php
go控制器是流程的另一端。也就是说,create.php控制器创建缩短的 URL 并将其保存到数据库中,而go.php控制器负责获取一个 URL,在uri段中查找$url_code,在数据库中查看它是否存在,如果存在,则将用户重定向到与之关联的实际网络地址。听起来很简单,事实上也是如此。
创建go.php控制器文件并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Go extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
}
public function index() {
if (!$this->uri->segment(1)) {
redirect (base_url());
} else {
$url_code = $this->uri->segment(1);
$this->load->model('Urls_model');
$query = $this->Urls_model->fetch_url($url_code);
if ($query->num_rows() == 1) {
foreach ($query->result() as $row) {
$url_address = $row->url_address;
}
redirect (prep_url($url_address));
} else {
$page_data = array('success_fail' => null,
'encoded_url' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
}
}
}
}
go控制器实际上只有在以下行之后才开始工作:
if (!$this->uri->segment(1)) {
redirect (base_url());
} else {
...
前面的行检查 URL 中是否存在第一个段。通常,第一个和第二个段由控制器和函数名称占用(因为 URL 中的顺序通常是控制器/函数/参数)。然而,因为我们希望 URL 尽可能短(或者至少这是我们的想法),所以我们从第一个参数中获取我们的唯一代码。把它想象成将通常位于第三个参数中的内容向左移动。所以,向上两级意味着原来在第三个段中的内容现在位于第一个段。
我们该如何做呢?我们如何将一个参数(我们的唯一代码)作为第一个参数而不是控制器名称?控制器和函数名称去哪里了,为什么它们被移除后仍然可以正常工作?
我们当然会修改routes.php文件;这一点在本章的早期已经解释过了。
无论如何,让我们回到我们的代码。如果 URL 中没有项目,那么这个控制器实际上没有什么可做的。因此,我们将用户重定向到base_url()函数,该函数将加载默认控制器(设置为autoload.php);在这种情况下,默认控制器是create.php文件。
现在,假设存在一个第一个参数,我们将继续到控制器的下一部分,即计算$url_code的部分,如下面的代码所示:
$url_code = $this->uri->segment(1);
$this->load->model('Urls_model');
$query = $this->Urls_model->fetch_url($url_code);
if ($query->num_rows() == 1) {
foreach ($query->result() as $row) {
$url_address = $row->url_address;
}
redirect (prep_url($url_address));
} else {
...
看一下前面的代码。我们获取第 1 个uri段,并将其分配给$url_code变量。我们需要检查这个代码是否存在于数据库中,因此我们加载Urls_model并调用Urls_model的fetch_url()函数,将$url_code传递给它。
fetch_url()方法将在数据库中查找与$url_code中的值相对应的记录。如果没有找到任何内容,它将返回false,导致控制器加载create/create.php视图。
然而,如果找到了记录,fetch_url()将返回 Active Record 对象。我们现在遍历对象,挑选出url_address,并将其存储为本地变量$url_address,如下所示:
foreach ($query->result() as $row) {
$url_address = $row->url_address;
}
现在,我们在$url_address变量中有了原始 URL。我们只需直接将其传递给redirect()CodeIgniter 函数,正如其名称所暗示的,这将把用户重定向到原始 URL。
注意在redirect()函数内部使用prep_url()CodeIgniter 函数。可以这样做:
redirect (prep_url($url_address));
prep_url()函数将确保 URL 开头有http://,如果它还没有的话
创建语言文件
从 HTML 中提取文本或将文本存储在其他文件中,如控制器,会使维护应用程序或添加多种语言变得噩梦。始终将语言保存在单独的专用文件中是个好主意。考虑到这一点,我们将为这个应用程序创建一个语言文件。
创建语言文件en_admin_lang.php并将其中的以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "URLs a Swinger";
// Encode
$lang['encode_instruction_1']= "Enter a URL in the text box below and we'll shorten it";
$lang['encode_encode_now']= "Shorten Now";
$lang['encode_encode_now_success']= "Your URL was successfully shortened - check it out below";
$lang['encode_encode_now_error']= "We could not shorten your url, see below for why";
$lang['encode_type_url_here']= "Write the URL here";
$lang['create_url_address'] = "Write the URL here";
$lang['encode_encoded_url']= "Hey look at this, your shortenedurl is:";
将所有这些放在一起
现在我们已经对配置文件进行了所有修改,创建了数据库,并创建了应用程序运行所需的所有文件(控制器、模型、视图等),让我们简要地浏览几个场景,以确保我们知道应用程序的功能。
创建缩短 URL
让我们考虑一个例子,其中 Lucy 访问 URL 缩短器应用程序,CodeIgniter 调用create控制器,显示create/create.php视图文件。以下是一系列事件:
-
Lucy 在文本输入中输入一个 URL 并点击立即缩短。
-
在提交表单后,控制器验证 URL。URL 验证成功,验证没有返回错误。
-
Lucy 输入的 URL 随后被发送到
Urls_model的save_url()函数,该函数创建一个唯一的代码。save_url()函数使用 PHP 构造do while和 Active Record 数据库查询来创建一个在数据库中不存在的唯一代码。 -
一旦创建了一个不存在的代码,它就会与 MySQL 时间戳一起保存到数据库中。
-
应用程序随后向 Lucy 显示一条成功消息,通知她 URL 已正确保存。它还显示 URL,供她点击或(更可能的是)复制粘贴到其他地方。
获取 URL
让我们考虑一个例子,其中 Jessica 收到来自 Lucy 的电子邮件,其中包含缩短后的 URL。以下是一系列事件:
-
杰西卡打开电子邮件并点击了其中的网址。
-
她的电脑打开浏览器并带她来到我们的应用。由于
create控制器不是第一个uri段,因此运行go控制器(我们在routes.php文件中设置了这一点)。 -
go控制器加载Urls_model,传递url_code(位于uri的第一个段中)。Urls_model的fetch_url()函数在数据库中查找代码,如果找到,它将实际与该代码关联的网址返回给go控制器。 -
go控制器将浏览器重定向到模型提供的 URL。 -
杰西卡很高兴,因为她可以看露西给她发的可爱猫咪视频!哎呀!
摘要
所以,这就是我们了!我们有一个相当不错的 URL 短链应用。它当然不是功能丰富或最先进的,但它能工作,如果你愿意,可以进一步扩展。也许你可以添加用户账户或为高级功能收费?
目前它使用 Twitter Bootstrap 作为前端,所以可能需要个性化的外观提升,不同的风格、外观和感觉,但目前的界面友好且对移动设备响应良好。
在下一章中,我们将创建一个论坛,允许某人创建讨论,然后让人们评论和回复。
将提供一个简单的管理员审核系统,以帮助防止任何不适当的恶作剧,例如名人裸照或信号情报被发布,或者类似的事情——除非你对此类事情感兴趣,在这种情况下,我听说伦敦的厄瓜多尔大使馆午餐做得非常好;不过,几个月后你可能就会厌倦了!
第三章。讨论论坛
讨论论坛可以是一个非常有用的内部公司项目资源,或者允许客户在项目上进行互动,例如。
讨论论坛是围绕特定主题或话题创建社区的一种极好方式,它充当一种维基。它们是关于某物或讨论的记录,包含观点和概念的变化历史,并记录围绕主题或主题的思考演变。它们也可以用来谈论猫。
为了创建这个应用程序,我们将创建三个控制器:一个用于处理讨论,一个用于处理评论,以及一个用于处理我们可能需要的任何管理功能,例如管理评论和讨论。
我们将创建一个语言文件来存储文本,以便在需要时提供多语言支持。
我们将对config.php文件进行修改,以允许加密支持,这对于会话和密码支持是必要的。
我们将创建所有必要的视图文件,甚至一个.css文件来帮助 Bootstrap 处理一些视图。
这个应用程序,以及这本书中的所有其他应用程序,都依赖于我们在第一章中完成的基本设置,简介和共享项目资源,尽管你可以将大量代码段放入你可能已经拥有的任何应用程序中;请记住,第一章中完成设置是本章的基础。
值得注意的是应用程序的限制。这个应用程序包含最基本的讨论论坛功能。我们在创建用户的过程中创建用户;然而,没有用户管理——包括这一点将是对应用程序代码的大量扩展,并且稍微超出了讨论论坛的范围。
当有人使用不在users表中存储的电子邮件地址创建评论或讨论时,将创建用户。为他们生成一个密码,并基于该密码创建一个哈希值。
由于这个应用程序会自动为它们创建密码,你可能希望告诉他们密码是什么——也许通过发送电子邮件。然而,你可能不希望他们能够登录。这取决于你——如果你愿意,功能都在那里。
在本章中,我们将涵盖:
-
设计和线框图
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
将所有内容整合在一起
因此,无需多言,让我们开始吧。
设计和线框图
和往常一样,在我们开始构建之前,我们应该看看我们计划构建的内容。
首先,我们需要简要描述我们的意图;我们计划开发一个应用程序,让用户可以查看任何现有的讨论页面,并且如果他们愿意,可以对该页面进行评论。用户还可以创建新的讨论,其他用户也可以对它们进行评论。
让我们看看一个网站地图:

现在,让我们逐一查看每个项目,并简要了解它们的功能:
-
首页:想象一下这是一个索引——路由的起点。用户将访问首页,然后被重定向到第 2 点(查看所有讨论页面)。
-
查看所有讨论:这将以列表形式显示所有讨论。我们还将进行一些筛选(例如,最新的首先,最受欢迎的其次,等等)。用户可以点击讨论标题,然后被重定向到查看讨论页面。
-
查看讨论/添加评论:此页面显示初始评论(由创建讨论的人撰写)以及所有其他用户添加的后续评论和贡献。用户可以通过在查看讨论页面底部填写表格来加入讨论。
-
新建讨论:用户可以创建新的讨论。然后,这个讨论将作为新的讨论出现在查看所有讨论页面。
我们现在开始查看仅管理员可用的功能(主要是讨论和评论审核),具体如下:
-
管理员登录:这是一个简单的登录脚本。它与第六章中使用的登录脚本不同,创建认证系统。
-
版主仪表板:此页面以列表形式显示所有待审阅的讨论和评论以及选项,以便允许或拒绝它们。
现在我们对网站的结构和形式有了相当好的了解,让我们看看每个页面的线框图。
查看所有讨论页面
以下截图显示了前一个图中第 2 点(查看所有讨论页面)的线框图。用户可以看到所有当前讨论,讨论创建者撰写的初始文本(这作为讨论主题的简要介绍),迄今为止的总评论数,以及将讨论排序为最新/最旧的排序方法等。

查看讨论/添加评论页面
以下截图显示了第 3 点(查看讨论/添加评论页面)的线框图。您可以看到,此页面显示初始讨论文本和所有回复。在回复列表底部有一个表格,允许用户加入讨论。顶部还有一个新建讨论链接;这将用户带到第 4 点(新建讨论页面)。
注意每个评论标题旁边的标记链接。如果用户点击此链接,则评论将被立即标记供管理员审查。例如,假设有人写了一些关于著名好莱坞演员的内容,或者是一些可能被认为是诽谤性的疯狂内容,比如宇宙飞船;这样的评论可以被标记供审查。如果被认为安全,可以将其设置为安全;然而,如果不安全,可以将其删除,以防止评论者被跟随到任何地方,出现在他们的工作场所,与邻居交谈等等——这是一个纯粹假设的、非现实世界、完全虚构的例子,这种情况从未发生过,甚至一次都没有。

新讨论页面
以下截图显示了从第 4 点(新讨论页面)的线框图。您可以看到用户可以创建新讨论的表单。用户被邀请输入讨论标题、他们的名字和初始讨论文本。一旦用户已将所有相关信息输入到表单中,他们点击 Go 按钮,表单将通过 create() 讨论控制器函数进行验证。

管理员仪表板页面
以下截图显示了从第 6 点(管理员仪表板页面)的仪表板截图。从该区域,管理员可以查看任何被标记的讨论和评论,并对它们进行管理,批准或删除。

文件概览
我们将为这个应用程序创建 15 个文件;这些文件如下:
-
/path/to/codeigniter/application/models/discussions_model.php: 此文件提供对数据库表discussions的读写访问。 -
/path/to/codeigniter/application/models/comments_model.php: 此文件提供对数据库表comments的读写访问。 -
/path/to/codeigniter/application/models/admin_model.php: 此文件提供对数据库的读写访问,使管理员能够管理讨论和评论。 -
/path/to/codeigniter/application/views/discussions/new.php: 此文件提供了一个界面,允许用户创建新的讨论;它还向用户显示任何错误或成功消息。 -
/path/to/codeigniter/application/views/discussions/view.php: 此文件为我们提供了一个界面,允许用户查看所有活跃的讨论。它还提供了过滤界面选项(例如,排序)。 -
/path/to/codeigniter/application/views/comments/view.php: 此文件为我们提供了一个界面,用于显示与用户相关的所有其他用户所写的单个讨论的所有评论。在此视图文件的底部还有一个表单,允许用户通过创建评论来加入讨论。与添加评论相关的任何验证或成功消息也将在此视图文件中显示。 -
/path/to/codeigniter/application/views/admin/dashboard.php: 此文件显示需要审核的评论和/或讨论列表。 -
/path/to/codeigniter/application/views/admin/login.php: 此文件为管理员提供了一个登录表单。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 此文件在页面顶部提供了一个导航栏。 -
/path/to/codeigniter/application/controllers/discussions.php:discussions控制器管理新讨论的创建并向普通用户显示讨论列表。 -
/path/to/codeigniter/application/controllers/comments.php:comments控制器管理新评论的创建并将它们链接到讨论中。它还向普通用户显示评论列表。 -
/path/to/codeigniter/application/controllers/admin.php:admin控制器处理管理员的登录、显示待审核的讨论和评论以及审核这些讨论和评论。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php: 此文件为应用程序提供语言支持。 -
/path/to/codeigniter/application/views/common/login_header.php: 此文件包含特定的 HTML 标记,用于正确显示登录表单。 -
/path/to/codeigniter/bootstrap/css/signin.css: 这是一个包含特定 CSS 代码的 CSS 脚本,用于正确显示登录表单。
前述 15 个文件的文件结构如下:
application/
├── controllers/
│ ├── discussions.php
│ ├── comments.php
│ ├── admin.php
├── models/
│ ├── comments_model.php
│ ├── discussions_model.php
│ ├── admin_model.php
├── views/discussions/
│ ├── view.php
│ ├── new.php
├── views/comments/
│ ├── view.php
├── views/admin/
│ ├── login.php
│ ├── dashboard.php
├── views/nav/
│ ├── top_nav.php
├── views/common/
│ ├── login_header.php
├── language/english/
│ ├── en_admin_lang.php
bootstrap/
├── css/
├── signin.css
注意列表中的最后一项:“signin.css”。它位于bootstrap/css/文件夹中,与 CodeIgniter 的application文件夹处于同一级别。我们在第一章,简介和共享项目资源中安装了 Twitter Bootstrap。在本章中,我们将介绍将bootstrap文件夹放置在此文件夹级别和位置的方法。
创建数据库
好的,你应该已经按照第一章,简介和共享项目资源中描述的方式设置了 CodeIgniter 和 Bootstrap。如果没有,那么你应该知道,本章中的代码是专门针对第一章的设置编写的。然而,如果你没有这样做,也没有关系——代码可以轻松地应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到您的数据库中:
CREATE DATABASE 'discuss_forum';
USE 'discuss_forum';
DROP TABLE IF EXISTS 'ci_sessions';
CREATE TABLE 'ci_sessions' (
'session_id' varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '0',
'ip_address' varchar(16) COLLATE utf8_bin NOT NULL DEFAULT '0',
'user_agent' varchar(120) COLLATE utf8_bin DEFAULT NULL,
'last_activity' int(10) unsigned NOT NULL DEFAULT '0',
'user_data' text COLLATE utf8_bin NOT NULL,
PRIMARY KEY ('session_id'),
KEY 'last_activity_idx' ('last_activity')
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
DROP TABLE IF EXISTS 'comments';
CREATE TABLE 'comments' (
'cm_id' int(11) NOT NULL AUTO_INCREMENT,
'ds_id' int(11) NOT NULL,
'cm_body' text NOT NULL,
'cm_created_at' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
'usr_id' int(11) NOT NULL,
'cm_is_active' int(1) NOT NULL,
PRIMARY KEY ('cm_id')
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS 'discussions';
CREATE TABLE 'discussions' (
'ds_id' int(11) NOT NULL AUTO_INCREMENT,
'usr_id' int(11) NOT NULL,
'ds_title' varchar(255) NOT NULL,
'ds_body' text NOT NULL,
'ds_created_at' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
'ds_is_active' int(1) NOT NULL,
PRIMARY KEY ('ds_id')
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS 'users';
CREATE TABLE 'users' (
'usr_id' int(11) NOT NULL AUTO_INCREMENT,
'usr_name' varchar(25) NOT NULL,
'usr_hash' varchar(255) NOT NULL,
'usr_email' varchar(125) NOT NULL,
'usr_created_at' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
'usr_is_active' int(1) NOT NULL,
'usr_level' int(1) NOT NULL,
PRIMARY KEY ('usr_id')
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
小贴士
如果您不想调用数据库discuss_forum,也可以不这样做。如果您愿意,可以将其重命名为其他名称;只需确保相应地更新config/database.php文件。
您会看到我们创建的第一个表是ci_sessions;我们需要这个表来允许 CodeIgniter 管理会话,特别是登录用户。然而,这只是从CodeIgniter 用户指南中可用的标准会话表,因此我不会包括表的描述,因为它不是技术特定于这个应用程序的。但是,如果您感兴趣,可以在ellislab.com/codeigniter/user-guide/libraries/sessions.html找到描述。
好的,让我们逐个查看每个表中的每个条目,看看它的含义。下表描述了comments表:
| 表:comments |
|---|
| 元素 |
cm_id |
ds_id |
cm_body |
cm_created_at |
usr_id |
cm_is_active |
下表描述了discussions表:
| 表:discussions |
|---|
| 元素 |
ds_id |
usr_id |
ds_title |
ds_body |
ds_created_at |
ds_is_active |
下表描述了users表:
| 表:users |
|---|
| 元素 |
usr_id |
usr_name |
usr_hash |
usr_email |
usr_created_at |
usr_is_active |
usr_level |
在这个早期阶段,讨论这个应用程序中用户的概念非常重要。我们不会真正使用任何详细的用户管理,用户只有在添加评论或创建讨论时输入他们的电子邮件地址时才会被创建。我们在这里创建用户是因为这将使您在您自己的时间里扩展此功能以管理用户变得容易,如果您愿意的话。
我们还需要对 config/database.php 文件进行修改——即设置数据库访问详情、用户名和密码等。步骤如下:
-
打开
config/database.php文件,找到以下行:$db['default']['hostname'] = 'localhost'; $db['default']['username'] = 'your username'; $db['default']['password'] = 'your password'; $db['default']['database'] = 'urls'; -
编辑前述行中的值,确保用更具体于您的设置和情况的值替换这些值。输入您的用户名、密码等。
调整 config.php 文件
在此文件中,我们需要配置一些内容以支持会话和加密。因此,打开 config/config.php 文件,并根据本节描述进行更改。
我们需要设置一个加密密钥。会话以及 CodeIgniter 的加密功能都需要在 $config 数组中设置一个加密密钥,因此请执行以下步骤:
-
找到以下行:
$config['encryption_key'] = '';更改为以下内容:
$config['encryption_key'] = 'a-random-string-of-alphanum-characters';小贴士
现在,实际上不要将此值更改为一个随机的字母数字字符序列,而是改为,嗯,一个随机的字母数字字符序列——如果这说得通。是的,你知道我的意思。
-
找到以下行:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; $config['sess_encrypt_cookie'] = FALSE; $config['sess_use_database'] = FALSE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;将它们更改为以下内容:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = TRUE; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;
调整routes.php文件
我们希望将用户重定向到discussions控制器而不是默认的 CodeIgniter welcome控制器。为此,我们需要修改routes.php文件中的默认控制器设置以反映这一点,可以按照以下方式操作:
-
打开
config/routes.php文件进行编辑,并找到以下行(文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。最初在一个 CodeIgniter 应用程序中,默认控制器设置为
welcome;然而,我们不需要这个。我们希望默认控制器是discussions。所以,找到以下行:$route['default_controller'] = "welcome";将其更改为以下内容:
$route['default_controller'] = "discussions";
创建模型
我们将为这个应用程序创建三个模型;具体如下:
-
discussions_model.php:这有助于管理与discussions表的交互 -
comments_model.php:这有助于管理与comments表的交互 -
admin_model.php:这有助于管理与users表的交互
创建模型文件 - models/discussions_model.php
discussions_model.php模型文件有三个函数;这些是fetch_discussions()、fetch_discussion()和flag()。fetch_discussions()函数获取多个讨论,fetch_discussion()函数获取单个讨论,而flag()函数将讨论设置为需要管理员审核的讨论。
创建此模型文件的步骤如下:
创建/path/to/codeigniter/application.models/discussion_model.php文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Discussions_model extends CI_Model {
function __construct() {
parent::__construct();
}
让我们先看看fetch_discussions()函数。fetch_discussions()函数将返回对discussions控制器index()函数的数据库查询结果。它接受两个默认设置为null的参数。这些是$filter和$direction,它们用于向查询字符串添加过滤和排序。
以下查询将仅返回活跃的讨论——也就是说,任何ds_is_active值未设置为0的讨论。discussions_model(稍后讨论)的flag()函数将一个讨论设置为非活动状态:
function fetch_discussions($filter = null, $direction = null) {
$query = "SELECT * FROM 'discussions', 'users'
WHERE 'discussions'.'usr_id' = 'users'.'usr_id'
AND 'discussions'.'ds_is_active' != '0' ";
如果filter变量最初为null,那么我们需要将结果排序为升序。在以下代码中,我们测试$filter是否等于null;如果不等于,则$dir = 'ASC'将方向设置为升序。如果$filter不是null,那么我们将进入 PHP if语句并查看$direction的值。我们执行 PHP switch case过程以快速确定$direction的值是ASC还是DESC,并相应地将$dir的值写入ASC或DESC:
if ($filter != null) {
if ($filter == 'age') {
$filter = 'ds_created_at';
switch ($direction) {
case 'ASC':
$dir = 'ASC';
break;
case 'DESC':
$dir = 'DESC';
break;
default:
$dir = 'ASC';
}
}
} else {
$dir = 'ASC';
}
接下来,执行查询并分析其返回值。如果查询成功,则$result返回到discussions控制器的index()函数。discussions控制器的index()函数然后将此查询结果存储在$page_data['query']数组项中,并将其传递给discussions/view.php视图文件。这在这里显示:
$query .= "ORDER BY 'ds_created_at' " . $dir;
$result = $this->db->query($query, array($dir));
if ($result) {
return $result;
} else {
return false;
}
}
function fetch_discussion($ds_id) {
$query = "SELECT * FROM 'discussions', 'users' WHERE 'ds_id' = ?
AND 'discussions'.'usr_id' = 'users'.'usr_id'";
return $result = $this->db->query($query, array($ds_id));
}
现在,让我们看看create($data)函数。该函数接受一个数组(命名为$data)作为其唯一参数。$data数组包含以下项:
-
usr_email:这是从views/discussions/new.php中的表单中填充的 -
usr_id:这是由模型本身通过在数据库中查找来填充的 -
usr_name:这是从views/discussions/new.php中的表单中填充的 -
ds_title:这是从views/discussions/new.php中的表单中填充的 -
ds_body:这是从views/discussions/new.php中的表单中填充的
我们希望将这个讨论论坛与一个用户关联起来。尽管在这个应用程序中我们并不真正管理用户,但我们仍然想这样做,因为这可能在将来对我们有用。要将讨论与用户关联起来,我们需要找到一个现有的用户 ID(users.usr_id)或者创建一个新的用户并分配该 ID。
这个函数首先查看users表,看看$data['usr_email']中的电子邮件地址是否已经在数据库中存在。如果存在,则从users表中提取usr_id并写入到$data['usr_id'];这将一直存储到我们更新到discussions表:
function create($data) {
// Look and see if the email address already exists in the users
// table, if it does return the primary key, if not create them
// a user account and return the primary key.
$usr_email = $data['usr_email'];
$query = "SELECT * FROM 'users' WHERE 'usr_email' = ? ";
$result = $this->db->query($query,array($usr_email));
if ($result->num_rows() > 0) {
foreach ($result->result() as $rows) {
$data['usr_id'] = $rows->usr_id;
}
} else {
如果电子邮件地址在users表中不存在,则创建一个记录。使用random_string() CodeIgniter 函数生成密码。密码存储在$password变量中,并传递给sha1 CodeIgniter 函数以生成哈希字符串:
$password = random_string('alnum', 16);
$hash = $this->encrypt->sha1($password);
用户提交的$hash值以及usr_email和usr_name被添加到$user_data数组中。还添加到$user_data数组中的一些管理员标志,如usr_is_active和usr_level。
默认情况下,usr_is_active标志设置为1;如果您想添加用户管理功能,可以将其设置为任何其他您希望的价值。默认情况下,usr_level标志设置为1;如果您想添加用户管理功能,可以将其设置为任何其他您希望的价值:
$user_data = array('usr_email' => $data['usr_email'],
'usr_name' => $data['usr_name'],
'usr_is_active' => '1',
'usr_level' => '1',
'usr_hash' => $hash);
$user_data数组被插入到数据库中。如果您愿意,您可以发送一封包含用户密码的电子邮件;这仅因为您想添加用户管理功能。新创建的用户 ID 通过$this->db->insert_id()返回并存储在$data['usr_id']中。这在这里显示:
if ($this->db->insert('users',$user_data)) {
$data['usr_id'] = $this->db->insert_id();
// Send email with password???
}
}
一旦用户 ID 存储在$data数组中,我们创建一个新的数组$discussion_data。$discussion_data数组包含创建讨论所需的所有数据,如下所示:
-
ds_title:这是从views/discussions/new.php中的表单中填充的 -
ds_body:这是从views/discussions/new.php中的表单中填充的 -
usr_id:这是通过数据库查找填充的 -
ds_is_active:这是在创建$discussion_data数组时设置的
一旦创建$discussion_data数组,我们就将其记录写入讨论表:
$discussion_data = array('ds_title' => $data['ds_title'],
'ds_body' => $data['ds_body'],
'usr_id' => $data['usr_id'],
'ds_is_active' => '1');
如果插入成功,我们返回TRUE;如果未成功,我们返回FALSE。
此模型还包含flag()函数。flag()函数使用UPDATE命令将ds_is_active列设置为0。这意味着讨论将不会显示给用户,因为discussions_model的fetch_discussions()函数只会返回ds_is_active设置为1的讨论。如下所示:
if ($this->db->insert('discussions',$discussion_data) ) {
return $this->db->insert_id();
} else {
return false;
}
}
flag()函数接受一个参数——即由discussions控制器传递的讨论的主键。当用户点击views/discussions/view.php文件中讨论标题旁边的flag链接时,会调用discussions控制器的flag()函数。flag链接的第三个uri段是讨论的主键。
我们使用 CodeIgniter 的 Active Record 功能来更新数据库中的讨论记录,将ds_is_active设置为0。将ds_is_active设置为0将立即阻止讨论在views/discussions/view.php中显示,并使其出现在管理员部分以供审核:
function flag($ds_id) {
$this->db->where('ds_id', $ds_id);
if ($this->db->update('discussions', array('ds_is_active' => '0'))) {
return true;
} else {
return false;
}
}
}
创建模型文件——comments_model.php
comments_model.php模型文件包含三个函数;这些是fetch_comments()、new_comment()和flag()。fetch_comments()函数获取属于讨论论坛的所有活动评论。new_comment()函数通过外键将评论添加到与讨论论坛关联的数据库中。最后,flag()函数将评论设置为需要审核的评论。
创建/path/to/codeigniter/application/models/comments_model.php文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Comments_model extends CI_Model {
function __construct() {
parent::__construct();
}
此模型中有三个函数。具体如下:
-
fetch_comments(): 这将从comments表中获取与当前讨论相关联的所有活动评论。 -
new_comments(): 这将在comments表中创建一个新的记录。评论与users.usr_id和discussions.ds_id相关联。 -
flag():这通过将comments.cm_is_active设置为0来设置一个评论为需要审核的评论。
fetch_comments()函数接受一个参数——$ds_id——这是数据库中讨论的主键。我们取这个主键,在数据库中查找属于该讨论的评论和属于评论的用户,如下所示:
function fetch_comments($ds_id) {
$query = "SELECT * FROM 'comments', 'discussions', 'users'
WHERE 'discussions'.'ds_id' = ?
AND 'comments'.'ds_id' = 'discussions'.'ds_id'
AND 'comments'.'usr_id' = 'users'.'usr_id'
AND 'comments'.'cm_is_active' = '1'
ORDER BY 'comments'.'cm_created_at' DESC " ;
$result = $this->db->query($query, array($ds_id));
这些评论随后作为 Active Record 数据库结果对象返回。或者,如果发生错误,则返回布尔值false,如下所示:
if ($result) {
return $result;
} else {
return false;
}
}
new_comment()函数接受一个参数,即$data数组。这个数组在comments控制器中填充,如下所示:
function new_comment($data) {
// Look and see if the email address already exists in the users
// table, if it does return the primary key, if not create them
// a user account and return the primary key.
首先,我们检查评论者使用的电子邮件地址是否已经在数据库中存在;我们这样做是因为我们可能想要以后添加禁止特定用户的功能,删除特定用户的帖子,或者甚至开发允许用户登录并查看他们以前帖子的功能:
$usr_email = $data['usr_email'];
$query = "SELECT * FROM 'users' WHERE 'usr_email' = ? ";
$result = $this->db->query($query,array($usr_email));
if ($result->num_rows() > 0) {
如果我们在代码中到达这里,那么显然电子邮件地址已经在数据库中,所以我们抓取用户的主键并将其存储在 $data['usr_id'] 中;稍后,我们将将其保存到评论中:
foreach ($result->result() as $rows) {
$data['usr_id'] = $rows->usr_id;
}
} else {
如果我们到达这里,那么该用户不存在,所以我们将在 users 表中创建他们,并使用 $this->d->insert_id() CodeIgniter 函数返回主键:
$password = random_string('alnum', 16);
$hash = $this->encrypt->sha1($password);
$user_data = array('usr_email' => $data['usr_email'],
'usr_name' => $data['usr_name'],
'usr_is_active' => '1',
'usr_level' => '1',
'usr_hash' => $hash);
if ($this->db->insert('users',$user_data)) {
$data['usr_id'] = $this->db->insert_id();
}
}
$comment_data = array('cm_body' => $data['cm_body'],
'ds_id' => $data['ds_id'],
'cm_is_active' => '1',
'usr_id' => $data['usr_id']);
现在,我们使用 CodeIgniter Active Record 函数 $this->db->insert() 将评论保存到 comments 表中。如下所示:
if ($this->db->insert('comments',$comment_data) ) {
return $this->db->insert_id();
} else {
return false;
}
}
function flag($cm_id) {
$this->db->where('cm_id', $cm_id);
if ($this->db->update('comments', array('cm_is_active' => '0'))) {
return true;
} else {
return false;
}
}
}
创建模型文件 – admin_model.php
admin_model.php 模型中有四个函数,具体如下:
-
dashboard_fetch_comments():从数据库中检索已标记为待审核的评论。 -
dashboard_fetch_discussions():从数据库中检索已标记为待审核的讨论。 -
update_comments():根据版主的决策更新评论,如果评论被批准则将cm_is_active的值更改为1,如果不批准则删除它。 -
update_discussions():根据版主的决策更新讨论,如果批准则将cm_is_active的值更改为1,如果不批准则删除它。如果删除讨论,则与该讨论关联的所有评论也将被删除。
创建 /path/to/codeigniter/application/models/admin_model.php 文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Admin_model extends CI_Model {
function __construct() {
parent::__construct();
}
以下函数将从数据库中检索所有待审核的评论。如果 comments.cm_is_active 设置为 0,则评论需要审核。数据库被查询,所有待审核的评论将返回给 admin 控制器。这个结果最终将在 views/admin/dashboard.php 文件中循环遍历:
function dashboard_fetch_comments() {
$query = "SELECT * FROM 'comments', 'users'
WHERE 'comments'.'usr_id' = 'users'.'usr_id'
AND 'cm_is_active' = '0' ";
$result = $this->db->query($query);
if ($result) {
return $result;
} else {
return false;
}
}
以下函数将从数据库中检索所有待审核的讨论。如果 discussions.ds_is_active 设置为 0,则讨论需要审核。数据库被查询,所有待审核的讨论将返回给 admin 控制器。这个结果最终将在 views/admin/dashboard.php 文件中循环遍历:
function dashboard_fetch_discussions() {
$query = "SELECT * FROM 'discussions', 'users'
WHERE 'discussions'.'usr_id' = 'users'.'usr_id'
AND 'ds_is_active' = '0' ";
$result = $this->db->query($query);
if ($result) {
return $result;
} else {
return false;
}
}
function does_user_exist($email) {
$this->db->where('usr_email', $email);
$query = $this->db->get('users');
return $query;
}
当管理员审核评论时,由 admin 控制器函数调用的以下函数。如果评论被认为是可以的,则更新 comments.cm_is_active 并将其设置为 1。然而,如果它不可行,则从 comments 表中删除该评论:
function update_comments($is_active, $id) {
if ($is_active == 1) {
$query = "UPDATE 'comments' SET 'cm_is_active' = ? WHERE 'cm_id' = ? " ;
if ($this->db->query($query,array($is_active,$id))) {
return true;
} else {
return false;
}
} else {
$query = "DELETE FROM 'comments' WHERE 'cm_id' = ? " ;
if ($this->db->query($query,array($id))) {
return true;
} else {
return false;
}
}
}
当管理员正在管理讨论时,以下函数会被admin控制器函数调用。如果讨论被认为是可以的,那么discussions.ds_is_active会被更新并设置为1。然而,如果它不可行,那么讨论将从discussions表中删除。属于该讨论的所有评论也将从comments表中删除:
function update_discussions($is_active, $id) {
if ($is_active == 1) {
$query = "UPDATE 'discussions' SET 'ds_is_active' = ? WHERE 'ds_id' = ? " ;
if ($this->db->query($query, array($is_active,$id))) {
return true;
} else {
return false;
}
} else {
$query = "DELETE FROM 'discussions' WHERE 'ds_id' = ? " ;
if ($this->db->query($query,array($id))) {
$query = "DELETE FROM 'comments' WHERE 'ds_id' = ? " ;
if ($this->db->query($query,array($id))) {
return true;
}
} else {
return false;
}
}
}
}
创建视图
这个应用程序中有六个视图文件,具体如下:
-
discussions/view.php:这个文件显示所有活跃的讨论 -
discussions/new.php:这个文件向用户显示一个表单,允许他们创建一个讨论 -
comments/view.php:这个文件显示讨论中的所有活跃评论 -
nav/top_nav.php:这个文件包含顶部导航链接 -
admin/login.php:这个文件显示用户的登录表单;别忘了添加signin.css脚本,你可以在本章后面找到它 -
common/login_header.php:views/admin/login.php视图需要与应用程序其余部分不同的资源,这由这个头部支持
讨论区
discussions/view.php视图文件显示所有活跃讨论的列表以及排序选项。
创建/path/to/codeigniter/views/discussions/view.php文件,并向其中添加以下代码:
SORT: <?php echo anchor('discussions/index/sort/age/' . (($dir == 'ASC') ? 'DESC' : 'ASC'),'Newest '
. (($dir == 'ASC') ? 'DESC' : 'ASC'));?>
<table class="table table-hover">
<thead>
<tr>
<th><?php echo $this->lang->line('discussions_title') ; ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($query->result() as $result) : ?>
<tr>
<td>
<?php echo anchor('comments/index/'.$result->ds_id,$result->ds_title) . ' '
. $this->lang->line('comments_created_by') . $result->usr_name; ?>
<?php echo anchor('discussions/flag/'.$result->ds_id,
$this->lang->line('discussion_flag')) ; ?>
<br />
<?php echo $result->ds_body ; ?>
</td>
</tr>
<?php endforeach ; ?>
</tbody>
</table>
看看前几行。我们以 CodeIgniter 的anchor()语句开始。让我们更仔细地看看链接的代码:
SORT: <?php echo anchor('discussions/index/sort/age/' . (($dir == 'ASC') ? 'DESC' : 'ASC'),'Newest ' . (($dir == 'ASC') ? 'DESC' : 'ASC'));?>
让我们将这个内容分解成更小的部分:
-
anchor('discussions/index/age/sort/' .:这设置了discussions控制器index()函数的链接,并按年龄(创建日期——discussions.ds_created_at)进行排序,但方向是什么?嗯… -
(($dir == 'ASC') ? 'DESC' : 'ASC'),:$dir的值来自discussions控制器的index()函数。它是当前排序的方向。然后我们使用 PHP 三元运算符在方向之间切换。它有点像 if/else 语句,但更紧凑。它的工作方式是这样的:如果变量等于(或不等于)另一个变量,则执行 A,否则执行 B。例如,作为一个 if/else 语句,代码将是如下所示:if ($dir == 'ASC') { return 'DESC'; } else { return 'ASC'; }因此,链接的第二部分将根据
$dir中保存的值在ASC和DESC之间切换。现在,让我们看看其余的部分。 -
'Newest ' . (($dir == 'ASC') ? 'DESC' : 'ASC'));?>:这是用户将看到的链接文本。您可以看到我们再次使用了三元运算符来显示文本,在Newest ASC和Newest DESC之间切换。
视图的其余部分相当简单;我们只是遍历来自讨论index()函数的数据库结果,在遍历过程中显示所有活跃的讨论。
评论
评论视图向用户显示所选讨论的所有有效评论列表。
创建/path/to/codeigniter/application/views/comments/view.php文件,并向其中添加以下代码:
<!-- Discussion - initial comment -->
<?php foreach ($discussion_query->result() as $discussion_result) : ?>
<h2>
<?php echo $discussion_result->ds_title; ?><br />
<small><?php echo $this->lang->line('comments_created_by') . $discussion_result->usr_name . $this->lang->line('comments_created_at') . $discussion_result->ds_created_at; ?></small>
</h2>
<p class="lead"><?php echo $discussion_result->ds_body; ?></p>
<?php endforeach ; ?>
<!-- Comment - list of comments -->
<?php foreach ($comment_query->result() as $comment_result) : ?>
<li class="media">
<a class="pull-left" href="#">
<img class="media-object" src="img/profile.svg" />
</a>
<div class="media-body">
<h4 class="media-heading"><?php echo $comment_result->usr_name . anchor('comments/flag/'.$comment_result->ds_id . '/' . $comment_result->cm_id,$this->lang->line('comments_flag')) ; ?></h4>
<?php echo $comment_result->cm_body ; ?>
</div>
</li>
<?php endforeach ; ?>
<!-- Form - begin form section -->
<br /><br />
<p class="lead"><?php echo $this->lang->line('comments_form_instruction');?></p>
<?php echo validation_errors(); ?>
<?php echo form_open('comments/index','role="form"') ; ?>
<div class="form-group col-md-5">
<label for="comment_name"><?php echo $this->lang->line('comments_comment_name');?></label>
<input type="text" name="comment_name" class="form-control" id="comment_name" value="<?php echo set_value('comment_name'); ?>">
</div>
<div class="form-group col-md-5">
<label for="comment_email"><?php echo $this->lang->line('comments_comment_email');?></label>
<input type="email" name="comment_email" class="form-control" id="comment_email" value="<?php echo set_value('comment_email'); ?>">
</div>
<div class="form-group col-md-10">
<label for="comment_body"><?php echo $this->lang->line('comments_comment_body');?></label>
<textarea class="form-control" rows="3" name="comment_body" id="comment_body"><?php echo set_value('comment_body'); ?></textarea>
</div>
<div class="form-group col-md-11">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button>
</div>
<?php echo form_hidden('ds_id',$ds_id) ; ?>
<?php echo form_close() ; ?>
注意表单中的以下行:
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button>
你会注意到我们使用了一个不在代码示例中的lang文件中的行;这是因为common_form_elements_go行可以在第一章中找到,引言和共享项目资源。
新讨论
新讨论视图向用户显示一个表单以及可能需要传达的任何验证错误信息。
创建/path/to/codeigniter/application/views/discussions/new.php文件,并向其中添加以下代码:
<!-- Form - begin form section -->
<br /><br />
<p class="lead"><?php echo $this->lang->line('discussion_form_instruction');?></p>
<?php echo validation_errors(); ?>
<?php echo form_open('discussions/create','role="form"') ; ?>
<div class="form-group col-md-5">
<label for="usr_name"><?php echo $this->lang->line('discussion_usr_name');?></label>
<input type="text" name="usr_name" class="form-control" id="usr_name" value="<?php echo set_value('usr_name'); ?>">
</div>
<div class="form-group col-md-5">
<label for="usr_email"><?php echo $this->lang->line('discussion_usr_email');?></label>
<input type="email" name="usr_email" class="form-control" id="usr_email" value="<?php echo set_value('usr_email'); ?>">
</div>
<div class="form-group col-md-10">
<label for="ds_title"><?php echo $this->lang->line('discussion_ds_title');?></label>
<input type="text" name="ds_title" class="form-control" id="ds_title" value="<?php echo set_value('ds_title'); ?>">
</div>
<div class="form-group col-md-10">
<label for="ds_body"><?php echo $this->lang->line('discussion_ds_body');?></label>
<textarea class="form-control" rows="3" name="ds_body" id="ds_body"><?php echo set_value('ds_body'); ?></textarea>
</div>
<div class="form-group col-md-11">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button>
</div>
<?php echo form_close() ; ?>
注意表单中的以下行:
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button>
你会注意到我们使用了一个不在代码示例中的lang文件中的行;这是因为common_form_elements_go行可以在第一章中找到,引言和共享项目资源。
我们向用户提供创建新讨论的选项。我们显示表单元素,让他们输入用户名、电子邮件、讨论标题以及他们的讨论文本。
表单提交给discussion控制器的create()函数,在那里它将进行验证,并显示任何验证错误。
top_nav文件
本书中的每个项目都有自己的导航文件,这也不例外。top_nav文件是标准的 Bootstrap 导航代码;然而,有几个 Codeigniter 的anchor()函数提供了 URL 链接和文本。
创建/path/to/codeigniter/application/views/common/top_nav.php文件,并向其中添加以下代码:
<!-- Fixed navbar -->
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><?php echo $this->lang->line('system_system_name'); ?></a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li <?php if ($this->uri->segment(1) == '') {echo 'class="active"';} ; ?>><?php echo anchor('/', $this->lang->line('top_nav_view_discussions')) ; ?></li>
<li <?php if ($this->uri->segment(1) == 'discussions') {echo 'class="active"';} ; ?>><?php echo anchor('discussions/create', $this->lang->line('top_nav_new_discussion')) ; ?></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><?php echo anchor('admin/login', $this->lang->line('top_nav_login')) ; ?></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container theme-showcase" role="main">
登录视图
登录视图在管理员想要登录时向其显示表单和任何错误信息。
创建/path/to/codeigniter/application/views/admin/login.php文件,并向其中添加以下代码:
<?php if (isset($login_fail)) : ?>
<div class="alert alert-danger"><?php echo $this->lang->line('admin_login_error') ; ?></div>
<?php endif ; ?>
<?php echo validation_errors(); ?>
<div class="container">
<?php echo form_open('admin/login', 'class="form-signin" role="form"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('admin_login_header') ; ?></h2>
<input type="email" name="usr_email" class="form-control" placeholder="<?php echo $this->lang->line('admin_login_email') ; ?>" required autofocus>
<input type="password" name="usr_password" class="form-control" placeholder="<?php echo $this->lang->line('admin_login_password') ; ?>" required>
<button class="btn btn-lg btn-primary btn-block" type="submit"><?php echo $this->lang->line('admin_login_signin') ; ?></button>
<?php echo form_close() ; ?>
</div>
这里没有太多要说的——一切如你所预期。我们向用户显示一个表单,让他们输入他们的电子邮件地址和密码,错误信息显示在表单上方。
表单提交给admin控制器的login()函数,该函数将处理用户的登录技术过程。如果登录成功,用户将被重定向到admin控制器的dashboard()函数。
登录头文件
admin/login.php文件需要与讨论论坛应用的其他部分不同的文件和资源。因此,我们将创建一个特定于登录页面的头文件。
创建/path/to/codeigniter/application/common/login_header.php文件,并向其中添加以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" href="<?php echo base_url('bootstrap/ico/favicon.ico'); ?>">
<title><?php echo $this->lang->line('system_system_name'); ?></title>
<!-- Bootstrap core CSS -->
<link href="<?php echo base_url('bootstrap/css/bootstrap.min.css'); ?>" rel="stylesheet">
<!-- Bootstrap theme -->
<link href="<?php echo base_url('bootstrap/css/bootstrap-theme.min.css'); ?>" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="<?php echo base_url('bootstrap/css/signin.css');?>" rel="stylesheet">
<!-- Just for debugging purposes. Don't actually copy this line! -->
<!--[if lt IE 9]><script src="img/ie8-responsive-file-warning.js"></script><![endif]-->
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="img/html5shiv.js"></script>
<script src="img/respond.min.js"></script>
<![endif]-->
</head>
<body>
仪表板
仪表板视图能够向管理员用户(在这种情况下,是一个版主)显示所有等待审核的讨论论坛和评论。这些内容以列表形式显示在表格中,每个项目都为版主提供两个选项:允许和禁止。
点击允许将设置讨论(discussions.ds_is_active)或评论(comments.cm_is_active)的活跃状态为 1,使它们再次对普通用户可见。但是,禁止将它们从数据库中删除。如果禁止的是讨论论坛,则与该讨论相关联的所有评论也将被删除。
创建 /path/to/codeigniter/application/views/admin/dashboard.php 文件,并将以下代码添加到其中:
<h1 id="tables" class="page-header">Dashboard</h1>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<td>Actions</td>
</tr>
</thead>
<tbody>
<?php if ($discussion_query->num_rows() > 0) : ?>
<?php foreach ($discussion_query->result() as $row) : ?>
<tr>
<td><?php echo $row->ds_id ; ?></td>
<td><?php echo $row->usr_name ; ?></td>
<td><?php echo $row->usr_email ; ?></td>
<td><?php echo anchor('admin/update_item/ds/allow/'.
$row->ds_id,$this->lang->line('admin_dash_allow')) .
' ' . anchor('admin/update_item/ds/disallow/'.
$row->ds_id,$this->lang->line('admin_dash_disallow')) ; ?>
</td>
</tr>
<tr>
<td colspan="3"><?php echo $row->ds_title; ?></td>
<td></td>
</tr>
<tr>
<td colspan="3"><?php echo $row->ds_body; ?></td>
<td></td>
</tr>
<?php endforeach ; ?>
<?php else : ?>
<tr>
<td colspan="4">No naughty forums here, horay!</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<td>Actions</td>
</tr>
</thead>
<tbody>
<?php if ($comment_query->num_rows() > 0) : ?>
<?php foreach ($comment_query->result() as $row) : ?>
<tr>
<td><?php echo $row->cm_id ; ?></td>
<td><?php echo $row->usr_name ; ?></td>
<td><?php echo $row->usr_email ; ?></td>
<td><?php echo anchor('admin/update_item/cm/allow/'.
$row->cm_id,$this->lang->line('admin_dash_allow')) .
' ' . anchor('admin/update_item/cm/disallow/'.
$row->cm_id,$this->lang->line('admin_dash_disallow')) ; ?>
</td>
</tr>
<tr>
<td colspan="3"><?php echo $row->cm_body; ?></td>
<td></td>
</tr>
<?php endforeach ; ?>
<?php else : ?>
<tr>
<td colspan="4">No naughty comments here, horay!</td>
</tr>
<?php endif; ?>
</tbody>
</table>
signin.css 文件
signin.css 文件是正确显示登录表单所必需的;这是与从 Twitter Bootstrap 资源中可用的 signin.css 文件相同的文件。
创建 /path/to/codeigniter/bootstrap/css/signin.css 文件,并将以下代码添加到其中:
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
创建控制器
我们将为这个应用程序创建三个控制器。这些是:
-
discussions.php: 这从数据库中的discussions表中获取讨论,并允许用户创建新的讨论 -
comments.php: 这从数据库中的comments表中获取评论,并允许用户通过在讨论论坛中添加评论来加入讨论 -
admin.php: 这包含基本的行政功能、登录功能和审核选项
讨论控制器
discussions.php 控制器负责显示所有有效的讨论,处理新讨论的创建,并对任何讨论进行标记以供审核。discussions 控制器包含三个函数,如下所示:
-
index(): 这显示所有有效的讨论 -
create(): 这将创建一个新的讨论,并处理任何表单验证 -
flag(): 这通过调用discussions_model.php中的flag()函数,将discussions.ds_is_active设置为0来处理讨论的审核
创建 /path/to/codeigniter/application/controllers/discussions.php 文件,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Discussions extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
$this->load->library('encrypt');
$this->load->model('Discussions_model');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
if ($this->uri->segment(3)) {
$filter = $this->uri->segment(4);
$direction = $this->uri->segment(5);
$page_data['dir'] = $this->uri->segment(5);
} else {
$filter = null;
$direction = null;
$page_data['dir'] = 'ASC';
}
$page_data['query'] = $this->Discussions_model->fetch_discussions($filter,$direction);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('discussions/view', $page_data);
$this->load->view('common/footer');
}
public function create() {
public function create() {
$this->form_validation->set_rules('usr_name', $this->lang->line('discussion_usr_name'), 'required|min_length[1]|max_length[255]');
$this->form_validation->set_rules('usr_email', $this->lang->line('discussion_usr_email'), 'required|min_length[1]|max_length[255]');
$this->form_validation->set_rules('ds_title', $this->lang->line('discussion_ds_title'), 'required|min_length[1]|max_length[255]');
$this->form_validation->set_rules('ds_body', $this->lang->line('discussion_ds_body'), 'required|min_length[1]|max_length[5000]');
if ($this->form_validation->run() == FALSE) {
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('discussions/new');
$this->load->view('common/footer');
} else {
$data = array('usr_name' => $this->input->post('usr_name'),
'usr_email' => $this->input->post('usr_email'),
'ds_title' => $this->input->post('ds_title'),
'ds_body' => $this->input->post('ds_body')
);
if ($ds_id = $this->Discussions_model->create($data)) {
redirect('comments/index/'.$ds_id);
} else {
// error
// load view and flash sess error
}
}
}
public function flag() {
$ds_id = $this->uri->segment(3);
if ($this->Discussions_model->flag($ds_id)) {
redirect('discussions/');
} else {
// error
// load view and flash sess error
}
}
}
逐个函数进行考虑,我们首先从 index() 函数开始。index() 函数负责向用户显示所有活跃的讨论。
代码首先检查第三个 uri 段中是否有值。
如果存在值,则表示用户已点击排序的升序或降序链接;我们将在稍后讨论这一点,但在此期间,我们假设第三部分没有值。
由于没有值,我们将 $filter 和 $direction 设置为 NULL,但将 $page_data['dir'] 设置为 ASC(代表升序)。这是因为在最初,讨论论坛按创建日期的降序显示;然而,排序链接需要写成与当前显示相反的方向。将 $page_data['dir'] 设置为 ASC(升序)将使排序链接中的 URL 准备就绪,以便我们可能需要点击它。
然后,我们要求 discussions_model.php 中的 fetch_discussions() 函数获取所有活跃讨论,并将两个变量作为参数传递给它:$filter 和 $direction。这两个变量默认设置为 null。fetch_discussions() 函数将知道不要应用这些过滤器。
排序链接的方向将在升序和降序之间翻转——始终与当前显示的相反。这种翻转是在视图文件中完成的(如果你非常严格,这可能不是最佳位置,但我认为这是一个你会觉得明显的地方,所以就这样吧)。
提示
查看本章前面关于 discussions/view.php 视图文件的代码和解释,以全面了解翻转功能。
现在,让我们看看 create() 函数;我们最初设置验证规则并检查表单是否已提交(或已提交带有错误)。假设它已无错误地提交,我们将帖子数据保存到 $data 数组中:
$data = array('usr_name' => $this->input->post('usr_name'),
'usr_email' => $this->input->post('usr_email'),
'ds_title' => $this->input->post('ds_title'),
'ds_body' => $this->input->post('ds_body'));
一旦所有表单元素都打包到 $data 数组中,我们就将其发送到 discussions_model 的 create() 函数以写入数据库。
如果插入操作成功,模型将返回新讨论的主键,如果出现错误,则返回 false。
我们测试插入操作返回值。如果插入成功,我们将用户重定向到 comments 控制器的 index() 函数,并传递模型返回的 $ds_id 值。用户然后可以看到他们的讨论,这已经准备好供评论:
if ($ds_id = $this->Discussions_model->create($data)) {
redirect('comments/index/'.$ds_id);
} else {
...
如果出现错误,则我们没有新的主键,因此无法重定向用户。在这个项目中,这已被留空;你可以实现自己的策略来处理这种行为;也许你可以通过电子邮件通知他们,或者将错误写入屏幕。
comments 控制器
comments 控制器管理所有与标记(用于审核)和创建用户讨论评论相关的事项。comments 控制器有两个功能,具体如下:
-
index(): 这将显示特定讨论论坛的所有评论,并处理提交——即验证用户的评论。 -
flag(): 这允许用户标记一条评论供管理员审核。数据库中的comments.cm_is_active值对该特定评论设置为0。
创建 /path/to/codeigniter/application/controllers/comments.php 文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Comments extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
$this->load->library('form_validation');
$this->load->model('Discussions_model');
$this->load->model('Comments_model');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
if ($this->input->post()) {
$ds_id = $this->input->post('ds_id');
} else {
$ds_id = $this->uri->segment(3);
}
$page_data['discussion_query'] = $this->Discussions_model->fetch_discussion($ds_id);
$page_data['comment_query'] = $this->Comments_model->fetch_comments($ds_id);
$page_data['ds_id'] = $ds_id;
$this->form_validation->set_rules('ds_id', $this->lang->line('comments_comment_hidden_id'), 'required|min_length[1]|max_length[11]');
$this->form_validation->set_rules('comment_name', $this->lang->line('comments_comment_name'), 'required|min_length[1]|max_length[25]');
$this->form_validation->set_rules('comment_email', $this->lang->line('comments_comment_email'), 'required|min_length[1]|max_length[255]');
$this->form_validation->set_rules('comment_body', $this->lang->line('comments_comment_body'), 'required|min_length[1]|max_length[5000]');
if ($this->form_validation->run() == FALSE) {
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('comments/view', $page_data);
$this->load->view('common/footer');
} else {
$data = array('cm_body' => $this->input->post('comment_body'),
'usr_email' => $this->input->post('comment_email'),
'usr_name' => $this->input->post('comment_name'),
'ds_id' => $this->input->post('ds_id')
);
if ($this->Comments_model->new_comment($data)) {
redirect('comments/index/'.$ds_id);
} else {
// error
// load view and flash sess error
}
}
}
public function flag() {
$cm_id = $this->uri->segment(4);
if ($this->Comments_model->flag($cm_id)) {
redirect('comments/index/'.$this->uri->segment(3));
} else {
// error
// load view and flash sess error
}
}
}
让我们从 index() 函数开始。index() 函数将首先显示特定讨论的所有评论。为此,它需要知道要查看哪个讨论。所以,让我们回顾一下。discussions 控制器将显示活跃讨论的列表。
以下是从我们之前更详细地查看的 discussions/view.php 文件中的代码段。此代码将遍历一组数据库结果,以表格行显示每个活跃的讨论。
查看以下用粗体突出显示的行:
<!-- Comment - list of comments -->
<?php foreach ($comment_query->result() as $comment_result) : ?>
<li class="media">
<a class="pull-left" href="#">
<img class="media-object" src="img/profile.svg" />
</a>
<div class="media-body">
<h4 class="media-heading"><?php echo $comment_result->usr_name . anchor('comments/flag/'.$comment_result->ds_id . '/' . $comment_result->cm_id,$this->lang-
>line('comments_flag')) ; ?></h4>
<?php echo $comment_result->cm_body ; ?>
</div>
</li>
<?php endforeach ; ?>
这行显示了用户可以通过点击讨论标题链接查看讨论及其相关评论的 URL,该链接看起来如下:
comments/index/id-of-discussion
我们可以将 id-of-discussion 作为链接的第三个参数传递给 comments 控制器的 index() 函数。这就是我们接手故事的地方。comments 控制器的 index() 函数检查是否存在第三个 uri 段(如果没有,则可能是创建评论的表单已被提交,而不会存在于 uri 段中)。
它将获取讨论的 ID 并将其存储为 $ds_id 变量:
if ($this->input->post()) {
$ds_id = $this->input->post('ds_id');
} else {
$ds_id = $this->uri->segment(3);
}
然后,我们为 comments/view.php 文件底部的“添加评论”表单定义了一些验证规则,以便 CodeIgniter 应用。
comments/view.php 文件不仅包含一个用于显示选定讨论当前评论的 foreach() 循环,还包含一个带有姓名和电子邮件文本字段以及正文文本区域的表单。这是用户可以输入他们的姓名、电子邮件和评论文本,然后提交评论的地方。
此外,还有一个名为 ds_id 的隐藏字段,其中包含选定讨论的主键。我们需要在表单中将其作为隐藏元素,因为当表单提交时,第三个 uri 段将消失。将讨论 ID 作为隐藏表单元素将允许 index() 在提交新评论表单时保持评论与讨论之间的关系。
假设表单没有错误,并且无需报告需要用户注意的事项即可提交,index() 函数将尝试将评论写入数据库中的 comments 表。
在此之前,我们需要将所有数据打包成一个数组,该数组将被传递给 Comments_model。请看以下代码:
$data = array('cm_body' => $this->input->post('comment_body'),
'usr_email' => $this->input->post('comment_email'),
'usr_name' => $this->input->post('comment_name'),
'ds_id' => $this->input->post('ds_id')
);
在这里,你可以看到我们已经包含了所有帖子元素,包括 ds_id(用粗体突出显示)。现在它已准备好发送到 new_comment() 模型函数以将其插入数据库:
if ($this->Comments_model->new_comment($data)) {
redirect('comments/index/'.$ds_id);
} else {
// error
// load view and flash sess error
}
new_comment() 模型函数在成功插入时将返回 true,否则返回 false。如果成功,则将用户重定向到 comments 控制器的 index() 函数,并将 $ds_id 作为第三个参数传递,index() 函数将从那里开始,显示与选定讨论相关的所有活跃评论。
现在,让我们继续到 flag() 函数。flag() 函数将使用户能够表明评论需要管理员进行审核。
回到 discussions 控制器,discussions 控制器将显示活跃讨论的列表。
以下是从comments/view.php中提取的代码片段,我们之前已经详细查看过。这段代码将遍历一组数据库结果,以表格形式显示每条活跃的评论:
<!-- Comment - list of comments -->
<?php foreach ($comment_query->result() as $comment_result) : ?>
<li class="media">
<a class="pull-left" href="#">
<img class="media-object" src="img/profile.svg" />
</a>
<div class="media-body">
<h4 class="media-heading"><?php echo $comment_result->usr_name . anchor('comments/flag/'.$comment_result->ds_id . '/' . $comment_result->cm_id,$this->lang-
>line('comments_flag')) ; ?></h4>
<?php echo $comment_result->cm_body ; ?>
</div>
</li>
<?php endforeach ; ?>
看一下加粗的行:
anchor('comments/flag/'.$comment_result->ds_id . '/' . $comment_result->cm_id,$this->lang->line('comments_flag'))
这一行包含一个 CodeIgniter 的anchor()语句,带有comments/flag/id-of-comment URL。正是这一行代码在每条评论旁边创建了标记链接。
看看第三个和第四个参数。第三个参数是讨论 ID(discussions.ds_id),第四个是评论 ID(comments.cm_id);这两个都在comments_model的flag()函数中使用。这段代码看起来如下:
public function flag() {
$cm_id = $this->uri->segment(4);
if ($this->Comments_model->flag($cm_id)) {
redirect('comments/index/'.$this->uri->segment(3));
} else {
// error
// load view and flash sess error
}
}
如果插入操作返回true,则我们将用户重定向到comments控制器的index()函数,并附带讨论论坛 ID。
管理控制器
admin控制器包含运行评论和讨论审核以及登录用户所需的所有函数。它包含以下函数:
-
index():每个控制器都需要一个索引函数,这就是它。index()函数将检查用户是否已登录,如果没有,则将其重定向到其他地方。 -
login():login()函数处理用户登录系统的过程。 -
dashboard():这个函数负责显示所有需要审核的评论和讨论。 -
update_item():这个函数负责应用管理员的决策,即是否批准或删除评论或讨论。
创建/path/to/codeigniter/application/controllers/admin.php文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Admin extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
$this->load->library('form_validation');
$this->load->model('Admin_model');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
if ($this->session->userdata('logged_in') == FALSE) {
redirect('admin/login');
}
redirect('admin/dashboard');
}
public function login() {
$this->form_validation->set_rules('usr_email', $this->lang->line('admin_login_email'), 'required|min_length[1]|max_length[255]');
$this->form_validation->set_rules('usr_password', $this->lang->line('admin_login_password'), 'required|min_length[1]|max_length[25]');
if ($this->form_validation->run() == FALSE) {
$this->load->view('common/login_header');
$this->load->view('nav/top_nav');
$this->load->view('admin/login');
$this->load->view('common/footer');
} else {
$usr_email = $this->input->post('usr_email');
$usr_password = $this->input->post('usr_password');
$query = $this->Admin_model->does_user_exist($usr_email);
if ($query->num_rows() == 1) { // One matching row found
foreach ($query->result() as $row) {
// Call Encrypt library
$this->load->library('encrypt');
// Generate hash from a their password
$hash = $this->encrypt->sha1($usr_password);
// Compare the generated hash with that in the database
if ($hash != $row->usr_hash) {
// Didn't match so send back to login
$page_data['login_fail'] = true;
$this->load->view('common/login_header');
$this->load->view('nav/top_nav');
$this->load->view('admin/login',$page_data);
$this->load->view('common/footer');
} else {
$data = array(
'usr_id' => $row->usr_id,
'usr_email' => $row->usr_email,
'logged_in' => TRUE
);
// Save data to session
$this->session->set_userdata($data);
redirect('admin/dashboard');
}
}
}
}
}
public function dashboard() {
if ($this->session->userdata('logged_in') == FALSE) {
redirect('admin/login');
}
$page_data['comment_query'] = $this->Admin_model->dashboard_fetch_comments();
$page_data['discussion_query'] = $this->Admin_model->dashboard_fetch_discussions();
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('admin/dashboard',$page_data);
$this->load->view('common/footer');
}
public function update_item() {
if ($this->session->userdata('logged_in') == FALSE) {
redirect('admin/login');
}
if ($this->uri->segment(4) == 'allow') {
$is_active = 1;
} else {
$is_active = 0;
}
if ($this->uri->segment(3) == 'ds') {
$result = $this->Admin_model->update_discussions($is_active, $this->uri->segment(5));
} else {
$result = $this->Admin_model->update_comments($is_active, $this->uri->segment(5));
}
redirect('admin');
}
}
让我们先从index()函数开始解决这个问题。由于admin控制器仅供已登录用户使用,index()函数将检查会话中是否存在名为logged_in的项目。如果logged_in等于FALSE,则表示用户未登录,因此他们将被重定向到login()函数。
这非常简单,我们不会在上面花费更多时间;然而,更复杂的函数是login()。正如其名称所示,login()函数负责管理管理员管理员的登录过程。
login()函数的第一步是定义usr_email和usr_pwd表单元素的验证规则。这些规则将决定在admin/login.php视图文件中用户提交的数据如何进行验证。
我们立即测试以查看表单是否已提交:
if ($this->form_validation->run() == FALSE) {
...
如果表单未提交,我们将加载视图文件以显示登录表单,并等待用户的响应。
然而,如果已经提交,则表单将根据验证标准进行验证;如果验证通过,我们尝试确定用户是否目前在数据库中存在:
$query = $this->Admin_model->does_user_exist($usr_email);
if ($query->num_rows() == 1) {
...
如果恰好找到一个匹配的电子邮件地址,那么我们将尝试确定用户的密码是否正确。我们使用 $this->load->library('encrypt') 加载 CodeIgniter 库,并从用户在登录表单中提供的密码生成一个哈希值:
$hash = $this->encrypt->sha1($usr_password);
我们然后将该哈希值与数据库中属于用户的哈希值进行比较:
if ($hash != $row->usr_hash) {
...
如果不匹配,则我们加载登录表单并显示错误消息。然而,如果匹配,则用户必须已经输入了正确的密码;因此,我们通过为他们创建一个 CodeIgniter 会话来登录他们:
$data = array(
'usr_id' => $row->usr_id,
'usr_email' => $row->usr_email,
'logged_in' => TRUE
);
// Save data to session
$this->session->set_userdata($data);
redirect('admin/dashboard');
用户随后被重定向到仪表板。仪表板将显示任何需要审核的评论和讨论。
创建语言文件
与本书中的所有项目一样,我们正在使用语言文件来向用户提供服务。这样,你可以启用多区域/多语言支持。
创建 /path/to/codeigniter/application/language/english/en_admin_lang.php 文件,并将其中的以下代码添加到该文件中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "Forum";
// Top Nav
$lang['top_nav_view_discussions'] = "Home";
$lang['top_nav_new_discussion'] = "New Discussion";
$lang['top_nav_login'] = "Login";
// Discussions
$lang['discussions_title'] = "Discussions";
$lang['discussions_num_comments'] = 'Comments';
// Comments
$lang['comments_form_instruction'] = "Join in, add your comment below.";
$lang['comments_flag'] = ' [Flag]';
$lang['comments_created_by'] = 'Created by ';
$lang['comments_created_at'] = ' at ';
$lang['comments_comment_name'] = 'Your name';
$lang['comments_comment_email'] = 'Your email';
$lang['comments_comment_body'] = 'Comment';
// Discussions
$lang['discussion_form_instruction'] = "Create your own discussion, fill in the form below";
$lang['discussion_flag'] = ' [Flag]';
$lang['discussion_usr_name'] = 'Your name';
$lang['discussion_usr_email'] = 'Your email';
$lang['discussion_ds_title'] = 'Discussion title';
$lang['discussion_ds_body'] = 'Your question, point etc';
// Admin - login
$lang['admin_login_header'] = "Please sign in";
$lang['admin_login_email'] = "Email";
$lang['admin_login_password'] = "Password";
$lang['admin_login_signin'] = "Signin...";
$lang['admin_login_error'] = "Whoops! Something went wrong - have another go!";
$lang['admin_dash_allow'] = "Allow";
$lang['admin_dash_disallow'] = "Disallow";
将所有这些放在一起
现在我们已经创建了应用所需的所有文件和资源,让我们通过几个场景来运行,以便我们可以很好地了解它们是如何一起工作的。
用户创建一个讨论论坛
让我们考虑一个例子,David 在他的浏览器中访问讨论论坛。以下是一系列步骤:
-
David 点击顶部导航栏中的 New Discussion 链接。
-
CodeIgniter 在
discussions控制器中加载了create()函数。 -
create()函数显示discussions/new.php视图文件,该文件向用户显示一个表单,使他们能够输入他们的姓名、电子邮件、讨论标题和讨论正文文本。 -
David 点击 Go 按钮提交表单。表单被提交到
discussion控制器的create()函数。 -
discussion控制的create()函数验证表单。假设没有错误,create()函数将帖子数据打包成一个数组,并将其发送到discussions_model的create()函数。 -
create()模型函数会在users数据库表中查找电子邮件地址是否已存在。如果已存在,则返回用户的主键并将其添加到讨论的 Active Record 插入中。然而,如果电子邮件地址不存在,则模型函数会创建它。相反,返回这个插入的主键。 -
创建一个密码并从它生成一个哈希值。然而,密码不会被存储,David 也不会被告知密码是什么;这可能是一个你可能不希望的功能,但你可以轻松添加代码通过电子邮件发送 David 他的密码,如果你希望的话。
用户在讨论论坛上发表评论
让我们考虑一个例子,Ed 在他的浏览器中访问讨论论坛。以下是一系列事件:
-
CodeIgniter 加载默认控制器——在这种情况下,是
discussion控制器。 -
discussion控制器使用discussions_model的fetch_discussions()函数从discussions数据库表获取最新的讨论,并将它们传递到discussions/view.php视图文件中显示。 -
Ed 喜欢其中一个讨论论坛的声音,点击了论坛的名称。
-
CodeIgniter 加载了
comments控制器的index()函数。index()函数获取第三个uri段(讨论论坛 ID—discussions.ds_id),并将其传递给comments_model的fetch_comments()函数。 -
评论在
comments/view.php视图文件中显示。 -
Ed 阅读了评论历史,并认为世界会从他的观点中受益。
-
Ed 滚动到页面底部,那里有添加评论的表单。Ed 输入了他的名字、电子邮件和评论,然后点击了Go。
-
表单提交到
comments的create()函数。create()函数将验证表单。假设没有错误,create()函数将把表单数据打包成一个数组,并将其发送到comments_model的create()函数。 -
create()模型函数检查users数据库表,看电子邮件地址是否已经存在。如果存在,则返回用户的主键并将其添加到评论的 Active Record 插入中。然而,如果电子邮件地址不存在,则模型函数会创建它。在这种情况下,返回插入的主键。 -
创建了一个密码,并从它生成了一个哈希值。但是,密码不会被存储,Ed 也不会被告知密码是什么;这可能是一个你可能不希望的功能,但如果你希望,你可以轻松地添加代码通过电子邮件发送 Ed 他的密码。
-
Ed 被重定向到讨论论坛,在那里他可以看到他的评论。
一个用户不喜欢一条评论并将其标记为需要审核
让我们考虑一个例子,其中 Nigel 正在查看一个讨论,并看到一条他认为有必要进行审核的评论。步骤如下:
-
他非常愤怒,按下了评论旁边的flag链接。
-
CodeIgniter 加载了
comments的flag()函数。用于访问此函数的 URL 是comments/flag/id-of-discussion/id-of-comment。 -
CodeIgniter 将
id-of-comment传递给comments_model的flag()函数,这将把comments.cm_is_active设置为0。这将从讨论中移除评论并将其放入审核仪表板。 -
如果评论更新成功,CodeIgniter 将重定向 Nigel 到他所查看的讨论。
一个审核员审查等待审核的评论
让我们考虑一个例子,其中 Nick 登录到他的管理员账户。步骤如下:
-
admin控制器加载了dashboard()函数。 -
dashboard()函数加载了等待审核的评论和讨论列表。 -
Nick 看到了评论和讨论的全文,以及两个选项:允许和禁止。
-
尼克看到有两个评论需要审核。
-
尼克阅读了第一条评论并决定它是可以接受的;他点击了允许链接。链接的结构是
admin/update_item/cm/allow/id-or-comment。 -
CodeIgniter 加载了
admin的update_item()函数。 -
update_item()函数获取需要更新的项目类型(评论:cm和讨论:ds);在这种情况下,尼克正在更新uri中的第一个段落的评论,即cm。第二个uri段落是allow,第三个uri段落是评论的 ID (comments.cm_id)。 -
admin_model的update_comments()函数被调用,将comments.cm_is_active设置为1。这允许评论再次显示。 -
尼克还注意到还有一条待审核的评论。他阅读了评论并认为这或许不是最好的评论,他希望将其删除。
-
他点击了拒绝链接。链接的结构是
admin/update_item/cm/disallow/id-or-comment。 -
CodeIgniter 加载了
admin的update_item()函数。 -
update_item()函数获取需要更新的项目类型(评论:cm和讨论:ds);在这种情况下,尼克正在更新uri中的第一个段落的评论,即cm。第二个uri段落是disallow,第三个uri段落是评论的 ID (comments.cm_id)。 -
admin_model的update_comments()函数被调用。由于$is_active被设置为0,我们不会允许评论显示,而是将其删除。PHP 的 if/else 语句计算出$is_active的值,执行 else 部分,并调用 MySQL 的DELETE命令,永久地从数据库中删除评论。
摘要
我们在本章中做了很多工作;我们创建了多个文件,有很多内容需要吸收。然而,这个项目为你提供了一个讨论论坛的基础系统。你可能希望添加用户管理(尤其是当涉及到发送用户密码时),假设你希望人们登录?一旦他们登录后,他们会做什么?这些都是由你来定义的,但现在你已经有了基础系统;这允许你构建更多。
第四章。创建照片共享应用程序
目前有很多图像共享网站。它们都具有大致相同的结构:用户上传图像,该图像可以被共享,允许其他人查看该图像。可能对图像的查看设置了限制或约束,可能图像仅在特定时间段内或特定日期内可见,但总体结构是相同的。我很高兴地宣布,这个项目与此完全相同。
我们将创建一个应用程序,允许用户分享图片;这些图片可以通过一个唯一的 URL 访问。为了制作这个应用程序,我们将创建两个控制器:一个用于处理图像上传,另一个用于处理存储图像的查看和显示。
我们将创建一个语言文件来存储文本,以便在需要时支持多种语言。
我们将创建所有必要的视图文件和一个与数据库交互的模型。
然而,这个项目以及本书中的所有其他项目都依赖于我们在第一章中完成的基本设置,简介和共享项目资源,尽管你可以将代码的大段内容复制并粘贴到几乎任何你已有的项目中。请记住,第一章中完成的设置是这个章节的基础。
在本章中,我们将涵盖:
-
设计和线框图
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
整合所有内容
所以,无需多言,让我们开始吧。
设计和线框图
和往常一样,在我们开始构建之前,我们应该看看我们打算构建什么。
首先,简要描述我们的意图:我们计划构建一个应用程序,允许用户上传图像。该图像将被存储在一个具有唯一名称的文件夹中。还会生成一个包含唯一代码的 URL,该 URL 和代码将被分配给该图像。可以通过该 URL 访问图像。
使用唯一的 URL 来访问该图像的想法是为了我们可以控制对该图像的访问,例如允许图像仅被查看一定次数,或者仅在特定时间段内。
总之,为了更好地了解正在发生的事情,让我们看一下以下网站地图:

所以这就是网站地图。首先要注意的是这个网站有多简单。这个项目只有三个主要区域。让我们逐一了解每个项目,并简要了解它们的功能:
-
创建:想象一下这是一个起点。用户将看到一个简单的表单,允许他们上传图像。一旦用户按下上传按钮,他们就会被引导到
do_upload。 -
do_upload:上传的图像会进行大小和文件类型的验证。如果通过验证,则生成一个唯一的八位字符字符串。然后,这个字符串被用作我们将创建的文件夹的名称。这个文件夹位于主
upload文件夹中,上传的图像被保存在其中。然后,图像的详细信息(图像名称、文件夹名称等)传递到数据库模型,为图像 URL 生成另一个唯一的代码。然后,这个唯一的代码、图像名称和文件夹名称被保存到数据库中。然后用户会看到一个消息,告知他们图像已上传,并创建了一个 URL。用户还会看到他们上传的图像。
-
go:这将接受某人输入浏览器地址栏的 URL、
img src标签或其他方法提供的 URL。go项目将查看 URL 中的唯一代码,查询数据库以查看该代码是否存在,如果存在,则获取文件夹名称和图像名称,并将图像返回给调用它的方法。
现在我们对网站的结构和形式有了相当好的了解,让我们来看看每个页面的线框图。
创建项目
以下截图显示了上一节中讨论的 create 项目的线框图。用户会看到一个简单的表单,允许他们上传图像。

do_upload 项目
以下截图显示了上一节中讨论的 do_upload 项目的线框图。用户会看到他们上传的图像和将其他用户引导到该图像的 URL。

go 项目
以下截图显示了上一节中描述的 go 项目的线框图。go 控制器会从 URL 中获取唯一的代码,尝试在数据库表 images 中找到它,如果找到,则提供与之关联的图像。只提供图像,而不是实际的 HTML 标记。

文件概述
这是一个相对较小的项目,总的来说,我们只需要创建七个文件,如下所示:
-
/path/to/codeigniter/application/models/image_model.php:这为我们提供了对images数据库表的读写访问。此模型还会从create控制器获取上传信息和唯一的文件夹名称(我们将在其中存储上传的图像),并将其存储到数据库中。 -
/path/to/codeigniter/application/views/create/create.php:这为我们提供了一个界面,允许用户上传文件。它还会向用户显示任何错误消息,例如文件类型错误、文件大小过大等。 -
/path/to/codeigniter/application/views/create/result.php:在图像成功上传后,此页面会向用户显示图像以及查看该图像所需的 URL。 -
/path/to/codeigniter/application/views/nav/top_nav.php:在页面顶部提供了一个导航栏。 -
/path/to/codeigniter/application/controllers/create.php:这个文件对用户上传的图像进行验证检查,创建一个唯一命名的文件夹来存储上传的图像,并将这些信息传递给模型。 -
/path/to/codeigniter/application/controllers/go.php:这个文件对用户输入的 URL 进行验证检查,查找 URL 中的唯一代码并尝试在数据库中找到这个记录。如果找到了,它将显示存储在磁盘上的图像。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php:这个文件为应用程序提供语言支持。
前七个文件的文件结构如下:
application/
├── controllers/
│ ├── create.php
│ ├── go.php
├── models/
│ ├── image_model.php
├── views/create/
│ ├── create.php
│ ├── result.php
├── views/nav/
│ ├── top_nav.php
├── language/english/
│ ├── en_admin_lang.php
创建数据库
好的,你应该已经按照第一章,简介和共享项目资源中描述的步骤设置了 CodeIgniter 和 Bootstrap;如果没有,那么你应该知道,本章中的代码是专门针对第一章,简介和共享项目资源中的设置编写的。然而,如果你没有这样做,也并非世界末日。代码可以轻松地应用于你独立开发的其它项目和应用程序。
首先,我们将构建数据库。将以下 MySQL 代码复制到你的数据库中:
CREATE DATABASE `imagesdb`;
USE `imagesdb`;
DROP TABLE IF EXISTS `images`;
CREATE TABLE `images` (
`img_id` int(11) NOT NULL AUTO_INCREMENT,
`img_url_code` varchar(10) NOT NULL,
`img_url_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`img_image_name` varchar(255) NOT NULL,
`img_dir_name` varchar(8) NOT NULL,
PRIMARY KEY (`img_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
好的,让我们来看看每个表中的每一项,看看它们代表什么:
| 表:images |
|---|
| 元素 |
img_id |
img_url_code |
img_url_created_at |
img_image_name |
img_dir_name |
我们还需要对 config/database.php 文件进行修改,即设置数据库访问详情,用户名,密码等。
打开 config/database.php 文件,找到以下行:
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'your username';
$db['default']['password'] = 'your password';
$db['default']['database'] = 'imagesdb';
修改上述代码中的值,确保用你设置和情况更具体的值替换它们——所以输入你的用户名、密码等。
调整 config.php 和 autoload.php 文件
在这个项目中,我们实际上不需要调整 config.php 文件,因为我们并没有真正使用会话或类似的东西。所以,我们不需要加密密钥或数据库信息。
因此,请确保你不在 config/autoload.php 文件中自动加载会话,否则你会得到错误,因为我们没有在 config/config.php 文件中设置任何会话变量。
调整 routes.php 文件
我们希望将用户重定向到 create 控制器而不是默认的 CodeIgniter welcome 控制器。为此,我们需要修改 routes.php 文件中的默认控制器设置以反映这一点。步骤如下:
-
打开
config/routes.php文件进行编辑,并找到以下行(在文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。最初,在 CodeIgniter 应用程序中,默认控制器设置为
welcome。然而,我们不需要这个,我们希望默认控制器是create,所以找到以下行:$route['default_controller'] = "welcome";用以下行替换它:
$route['default_controller'] = "create"; $route['404_override'] = ''; -
然后我们需要添加一些规则来规范我们如何处理传入的 URL 和表单提交。
在前两行代码(默认控制器和 404 覆盖)下方留出几行空白,然后添加以下三行代码:
$route['create'] = "create/index"; $route['(:any)'] = "go/index"; $route['create/do_upload'] = "create/do_upload";
创建模型
在这个项目中只有一个模型,image_model.php。它包含创建和重置密码的特定函数。
创建/path/to/codeigniter/application/models/image_model.php文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Image_model extends CI_Model {
function __construct() {
parent::__construct();
}
function save_image($data) {
do {
$img_url_code = random_string('alnum', 8);
$this->db->where('img_url_code = ', $img_url_code);
$this->db->from('images');
$num = $this->db->count_all_results();
} while ($num >= 1);
$query = "INSERT INTO `images` (`img_url_code`, `img_image_name`, `img_dir_name`) VALUES (?,?,?) ";
$result = $this->db->query($query, array($img_url_code, $data['image_name'], $data['img_dir_name']));
if ($result) {
return $img_url_code;
} else {
return flase;
}
}
function fetch_image($img_url_code) {
$query = "SELECT * FROM `images` WHERE `img_url_code` = ? ";
$result = $this->db->query($query, array($img_url_code));
if ($result) {
return $result;
} else {
return false;
}
}
}
在这个模型中有两个主要函数,如下所示:
-
save_image():这生成一个与上传图像关联的唯一代码,并将图像名称和文件夹名称保存到数据库中。 -
fetch_image(): 这根据提供的唯一代码从数据库中获取图像的详细信息。
好的,让我们先来看save_image()。save_image()函数接受来自create控制器的一个数组,包含image_name(来自上传过程)和img_dir_name(这是图像存储的文件夹)。
使用如下所示的do…while循环生成一个唯一的代码:
$img_url_code = random_string('alnum', 8);
首先创建一个包含字母数字字符的八字符长字符串。do…while循环检查此代码是否已在数据库中存在,如果已存在,则生成新的代码。如果它不存在,则使用此代码:
do {
$img_url_code = random_string('alnum', 8);
$this->db->where('img_url_code = ', $img_url_code);
$this->db->from('images');
$num = $this->db->count_all_results();
} while ($num >= 1);
然后使用以下代码将此代码和$data数组的内容保存到数据库中:
$query = "INSERT INTO `images` (`img_url_code`, `img_image_name`, `img_dir_name`) VALUES (?,?,?) ";
$result = $this->db->query($query, array($img_url_code, $data['image_name'], $data['img_dir_name']));
如果INSERT操作成功,则返回$img_url_code,如果失败,则返回false。实现此目的的代码如下:
if ($result) {
return $img_url_code;
} else {
return false;
}
创建视图
在这个项目中只有三个视图,如下所示:
-
/path/to/codeigniter/application/views/create/create.php: 这向用户显示一个表单,允许他们上传图像。 -
/path/to/codeigniter/application/views/create/result.php: 这显示一个用户可以使用它将其他人转发到图像的链接,以及图像本身。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 这显示顶层菜单。在这个项目中,它非常简单,包含项目名称和一个链接,可以跳转到create控制器。
所以,这就是我们的视图,正如我所说的,由于这是一个简单的项目,所以只有三个视图。现在,让我们创建每个视图文件。
-
创建
/path/to/codeigniter/application/views/create/create.php文件,并将以下代码添加到其中:<div class="page-header"> <h1><?php echo $this->lang->line('system_system_name'); ?></h1> </div> <p><?php echo $this->lang->line('encode_instruction_1'); ?></p> <?php echo validation_errors(); ?> <?php if (isset($success) && $success == true) : ?> <div class="alert alert-success"> <strong><?php echo $this->lang->line('common_form_elements_success_notifty'); ?></strong><?php echo $this->lang->line('encode_encode_now_success'); ?> </div> <?php endif ; ?> <?php if (isset($fail) && $fail == true) : ?> <div class="alert alert-danger"> <strong><?php echo $this->lang->line('common_form_elements_error_notifty'); ?> </strong><?php echo $this->lang->line('encode_encode_now_error'); ?> <?php echo $fail ; ?> </div> <?php endif ; ?> <?php echo form_open_multipart('create/do_upload');?> <input type="file" name="userfile" size="20" /> <br /> <input type="submit" value="upload" /> <?php echo form_close() ; ?> <br /> <?php if (isset($result) && $result == true) : ?> <div class="alert alert-info"> <strong><?php echo $this->lang->line('encode_upload_url'); ?> </strong> <?php echo anchor($result, $result) ; ?> </div> <?php endif ; ?>这个视图文件可以被认为是主视图文件;在这里,用户可以上传他们的图像。错误信息也在这里显示。
-
创建
/path/to/codeigniter/application/views/create/result.php文件,并将以下代码添加到其中:<div class="page-header"> <h1><?php echo $this->lang->line('system_system_name'); ?></h1> </div> <?php if (isset($result) && $result == true) : ?> <strong><?php echo $this->lang->line('encode_encoded_url'); ?> </strong> <?php echo anchor($result, $result) ; ?> <br /> <img src="img/' . $file_name ;?>" /> <?php endif ; ?>此视图将向用户显示编码后的图片资源 URL(以便他们可以复制和分享),以及实际的图片本身。
-
创建
/path/to/codeigniter/application/views/nav/top_nav.php文件,并将以下代码添加到其中:<!-- Fixed navbar --> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#"><?php echo $this->lang->line('system_system_name'); ?></a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li class="active"><?php echo anchor('create', 'Create') ; ?></li> </ul> </div><!--/.nav-collapse --> </div> </div> <div class="container theme-showcase" role="main">此视图相当基础,但仍然发挥着重要作用。它显示了一个返回到
create控制器的index()函数的选项。
创建控制器
在这个项目中,我们将创建两个控制器,如下所示:
-
/path/to/codeigniter/application/controllers/create.php:此文件处理创建用于存储图片的唯一文件夹,并执行文件的上传。 -
/path/to/codeigniter/application/controllers/go.php:此文件从数据库中检索唯一代码,并返回与该代码关联的任何图片。
这些是我们为该项目创建的两个控制器,现在让我们继续创建它们。
创建 /path/to/codeigniter/application/controllers/create.php 文件,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Create extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper(array('string'));
$this->load->library('form_validation');
$this->load->library('image_lib');
$this->load->model('Image_model');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
$page_data = array('fail' => false,
'success' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
}
public function do_upload() {
$upload_dir = '/filesystem/path/to/upload/folder/';
do {
// Make code
$code = random_string('alnum', 8);
// Scan upload dir for subdir with same name
// name as the code
$dirs = scandir($upload_dir);
// Look to see if there is already a
// directory with the name which we
// store in $code
if (in_array($code, $dirs)) { // Yes there is
$img_dir_name = false; // Set to false to begin again
} else { // No there isn't
$img_dir_name = $code; // This is a new name
}
} while ($img_dir_name == false);
if (!mkdir($upload_dir.$img_dir_name)) {
$page_data = array('fail' => $this->lang->line('encode_upload_mkdir_error'),
'success' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
}
$config['upload_path'] = $upload_dir.$img_dir_name;
$config['allowed_types'] = 'gif|jpg|jpeg|png';
$config['max_size'] = '10000';
$config['max_width'] = '1024';
$config['max_height'] = '768';
$this->load->library('upload', $config);
if ( ! $this->upload->do_upload()) {
$page_data = array('fail' => $this->upload->display_errors(),
'success' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
} else {
$image_data = $this->upload->data();
$page_data['result'] = $this->Image_model->save_image(array('image_name' => $image_data['file_name'], 'img_dir_name' => $img_dir_name));
$page_data['file_name'] = $image_data['file_name'];
$page_data['img_dir_name'] = $img_dir_name;
if ($page_data['result'] == false) {
// success - display image and link
$page_data = array('fail' => $this->lang->line('encode_upload_general_error'));
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
} else {
// success - display image and link
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/result', $page_data);
$this->load->view('common/footer');
}
}
}
}
让我们从 index() 函数开始。index() 函数将 $page_data 数组的 fail 和 success 元素设置为 false。这将阻止任何初始消息显示给用户。加载视图,特别是 create/create.php 视图,其中包含图片上传表单的 HTML 标记。
当用户在 create/create.php 中提交表单后,表单将被提交到 create 控制器的 do_upload() 函数。正是这个函数负责将图片上传到服务器。
首先,do_upload() 定义了 upload 文件夹的初始位置。这存储在 $upload_dir 变量中。
接下来,我们进入一个 do…while 结构。它看起来像这样:
do {
// something
} while ('…a condition is not met');
这意味着 在条件不满足的情况下执行某些操作。现在考虑到这一点,思考我们的问题——我们必须将上传的图片保存到一个文件夹中。这个文件夹必须有一个唯一的名称。所以我们将生成一个包含八个字母数字字符的随机字符串,然后查看是否存在具有该名称的文件夹。考虑到这一点,让我们详细查看代码:
do {
// Make code
$code = random_string('alnum', 8);
// Scan uplaod dir for subdir with same name
// name as the code
$dirs = scandir($upload_dir);
// Look to see if there is already a
// directory with the name which we
// store in $code
if (in_array($code, $dirs)) { // Yes there is
$img_dir_name = false; // Set to false to begin again
} else { // No there isn't
$img_dir_name = $code; // This is a new name
}
} while ($img_dir_name == false);
因此,我们使用以下代码行创建一个只包含字母数字字符的八个字符的字符串:
$code = random_string('alnum', 8);
然后,我们使用 PHP 函数 scandir() 在 $upload_dir 中查找。这将存储所有目录名称在 $dirs 变量中,如下所示:
$dirs = scandir($upload_dir);
我们随后使用 PHP 函数 in_array() 在 scandir() 的目录列表中查找 $code 中的值。
如果我们没有找到匹配项,那么 $code 中的值不应被采用,因此我们将采用那个值。如果找到了值,那么我们将 $img_dir_name 设置为 false,这将由 do…while 循环的最后一行捕获:
...
} while ($img_dir_name == false);
不管怎样,现在我们已经有了我们的唯一文件夹名称,我们将尝试创建它。我们使用 PHP 函数 mkdir(),将其传递给 $upload_dir 与 $img_dir_name 连接的结果。如果 mkdir() 返回 false,则表单将再次显示,并显示语言文件中设置的 encode_upload_mkdir_error 消息,如下所示:
if (!mkdir($upload_dir.$img_dir_name)) {
$page_data = array('fail' => $this->lang->line('encode_upload_mkdir_error'),
'success' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
}
一旦创建了文件夹,我们就设置上传过程的配置变量,如下所示:
$config['upload_path'] = $upload_dir.$img_dir_name;
$config['allowed_types'] = 'gif|jpg|jpeg|png';
$config['max_size'] = '10000';
$config['max_width'] = '1024';
$config['max_height'] = '768';
在这里,我们指定我们只想上传 .gif、.jpg、.jpeg 和 .png 文件。我们还指定图片大小不能超过 10,000 KB(尽管你可以将其设置为任何你想要的值——记得调整你的 php.ini 文件中的 upload_max_filesize 和 post_max_size PHP 设置,如果你想上传非常大的文件)。
我们还设置了图像必须的最小尺寸。与文件大小一样,你可以根据需要调整它。
然后,我们加载 upload 库,并传递给它的配置设置,如下所示:
$this->load->library('upload', $config);
接下来,我们将尝试上传。如果上传失败,CodeIgniter 函数 $this->upload->do_upload() 将返回 false。我们将寻找这个返回值,并在它返回 false 时重新加载上传页面。我们还将传递具体的错误原因,为什么它失败了。这个错误存储在 $page_data 数组的 fail 项中。可以按照以下方式完成:
if ( ! $this->upload->do_upload()) {
$page_data = array('fail' => $this->upload->display_errors(),
'success' => false);
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('create/create', $page_data);
$this->load->view('common/footer');
} else {
...
然而,如果没有失败,我们将从上传中获取 CodeIgniter 生成的信息。我们将按照以下方式将其存储在 $image_data 数组中:
$image_data = $this->upload->data();
然后,我们尝试在数据库中存储上传记录。我们调用 Image_model 的 save_image 函数,将其 file_name 从 $image_data 数组中传递给它,以及 $img_dir_name,如下所示:
$page_data['result'] = $this->Image_model->save_image(array('image_name' => $image_data['file_name'], 'img_dir_name' => $img_dir_name));
然后,我们测试 save_image() 函数的返回值;如果成功,则 Image_model 将返回模型中生成的唯一 URL 代码。如果失败,则 Image_model 将返回布尔值 false。
如果返回 false,则表单将加载一个通用错误。如果成功,则加载 create/result.php 视图文件。我们向其中传递唯一的 URL 代码(用户需要的链接),以及文件夹名称和图像名称,这是正确显示图像所必需的。
创建 /path/to/codeigniter/application/controllers/go.php 文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Go extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
}
public function index() {
if (!$this->uri->segment(1)) {
redirect (base_url());
} else {
$image_code = $this->uri->segment(1);
$this->load->model('Image_model');
$query = $this->Image_model->fetch_image($image_code);
if ($query->num_rows() == 1) {
foreach ($query->result() as $row) {
$img_image_name = $row->img_image_name;
$img_dir_name = $row->img_dir_name;
}
$url_address = base_url() . 'upload/' . $img_dir_name .'/' . $img_image_name;
redirect (prep_url($url_address));
} else {
redirect('create');
}
}
}
}
go 控制器只有一个主函数,index()。当用户点击 URL 或调用 URL(可能是 HTML img 标签的 src 值)时,它会调用。在这里,我们获取在 create 控制器上传图片时生成的并分配给图片的唯一代码。
此代码位于 URI 的第一个值中。通常它将占用第三个参数——第一个和第二个参数通常用于分别指定控制器和控制器函数。然而,我们使用 CodeIgniter 路由改变了这种行为。这在本章的 调整 routes.php 文件 部分有详细解释。
一旦我们有了唯一的代码,我们就将其传递给 Image_model 的 fetch_image() 函数:
$image_code = $this->uri->segment(1);
$this->load->model('Image_model');
$query = $this->Image_model->fetch_image($image_code);
我们检查返回的内容。我们询问返回的行数是否正好等于 1。如果不是,我们将重定向到 create 控制器。
可能你不想这样做。如果返回的行数不等于 1,可能你什么也不做。例如,如果请求的图片在 HTML img 标签中,那么如果找不到图片,重定向可能会将某人从他们正在查看的网站发送到本项目的上传页面——这可能是你不希望发生的事情。如果你想移除此功能,请从代码摘录中删除以下加粗行:
....
$img_dir_name = $row->img_dir_name;
}
$url_address = base_url() . 'upload/' . $img_dir_name .'/' . $img_image_name;
redirect (prep_url($url_address));
} else {
redirect('create');
}
}
}
}
....
不管怎样,如果返回的值正好是 1,那么我们将遍历返回的数据库对象,找到 img_image_name 和 img_dir_name,这些是我们需要在磁盘上的 upload 文件夹中定位图片所需的。这可以按以下方式完成:
foreach ($query->result() as $row) {
$img_image_name = $row->img_image_name;
$img_dir_name = $row->img_dir_name;
}
我们构建图片文件的地址,并将浏览器重定向到该地址,如下所示:
$url_address = base_url() . 'upload/' . $img_dir_name .'/' . $img_image_name;
redirect (prep_url($url_address));
创建语言文件
与本书中的所有项目一样,我们正在使用语言文件向用户提供文本。这样,您可以启用多区域/多语言支持。
创建 /path/to/codeigniter/application/language/english/en_admin_lang.php 文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "Image Share";
// Upload
$lang['encode_instruction_1'] = "Upload your image to share it";
$lang['encode_upload_now'] = "Share Now";
$lang['encode_upload_now_success'] = "Your image was uploaded, you can share it with this URL";
$lang['encode_upload_url'] = "Hey look at this, here's your image:";
$lang['encode_upload_mkdir_error'] = "Cannot make temp folder";
$lang['encode_upload_general_error'] = "The Image cannot be saved at this time";
将所有内容整合在一起
让我们看看用户是如何上传图片的。以下是事件发生的顺序:
-
CodeIgniter 在
routes.php配置文件中查找,并找到以下行:$route['create'] = "create/index";它将请求重定向到
create控制器的index()函数。 -
index()函数加载create/create.php视图文件,向用户显示上传表单。 -
用户点击选择文件按钮,导航到他们想要上传的图片文件,并选择它。
-
用户点击上传按钮,表单提交到
create控制器的index()函数。 -
index()函数在主upload目录中创建一个文件夹以存储图片,然后执行实际的上传。 -
在上传成功后,
index()将上传的详细信息(新的文件夹名称和图像名称)发送到save_image()模型函数。 -
save_model()函数还创建一个唯一的代码,并将其与通过create控制器传递给它的文件夹名称和图像名称一起保存在images表中。 -
在数据库插入期间生成的唯一代码随后返回到控制器,并传递给结果视图,它将形成用户成功消息的一部分。
现在,让我们看看如何查看(或检索)图片。以下是事件发生的顺序:
-
一个具有
www.domain.com/226KgfYH语法格式的 URL 进入应用程序——无论是有人点击链接还是其他调用(<img src="img/li>) -
php` CodeIgniter looks in the `routes.php` config file and finds the following line:`$route['(:any)'] = "go/index"; ```php ```` -
由于传入的请求不匹配其他两个路由,因此前面的路由是 CodeIgniter 应用到此请求的路由。
-
``调用
go控制器,并将226KgfYH代码作为uri的第一个段传递给它。``` -
go`控制器将此传递给`Image_model.php`文件中的`fetch_image()`函数。`fetch_image()`函数将尝试在数据库中找到匹配的记录。如果找到,它将返回文件夹名称,标记图像的保存位置及其文件名。 -
``返回此信息,并构建该图像的路径。CodeIgniter 随后将用户重定向到该图像,即向请求该图像的用户提供该图像资源。```
``# 摘要 因此,我们有一个基本的图像共享应用程序。它能够接受各种图像,并将它们分配到数据库中的记录和文件系统中的唯一文件夹中。这很有趣,因为它为你留下了改进的空间。例如,你可以做以下事情: * 你可以添加查看限制。由于图像记录存储在数据库中,你可以调整数据库。添加名为img_count和img_count_limit的两列,你可以允许用户为每张图像设置查看次数的限制,并在达到该限制时停止提供该图像。 * 你可以根据日期限制查看次数。类似于前面的点,但你可以将图像查看次数限制在特定日期。 * 你可以为不同尺寸提供不同的 URL。你可以添加功能,根据初始上传创建多个尺寸的图像,为不同的图像尺寸提供多个不同的 URL。 * 你可以报告滥用。你可以添加一个选项,允许图像查看者报告可能被上传的不当图像。 * 你可以设置服务条款。如果你计划提供此类应用程序作为公众可以使用的实际网络服务,那么我强烈建议你添加一份服务条款文档,甚至可能要求人们在上传图像之前同意条款。在那些条款中,你将想要提到,为了使用该服务,人们首先必须同意他们不会上传和分享任何可能被视为非法的图像。你还应提到,如果需要,你将配合任何法院。你真的不希望因为拥有或运营一个存储令人不快的图像的网络服务而陷入麻烦;尽可能明确地说明你的责任限制,并强调是上传者提供了图像。在下一章中,我们将创建一个新闻通讯注册系统。你将能够让人们注册,并将他们的详细信息存储在数据库中。人们将被允许取消订阅和选择不同的设置。```
第五章:创建通讯订阅
通讯订阅是一个相当方便的应用程序;你可以很容易地适应它以适应大多数应用程序,而无需太多麻烦。它使你能够拥有一个订阅者数据库,并管理他们,编辑他们的设置,如果他们选择取消订阅,则从数据库中删除他们。
在本章中,我们将涵盖:
-
设计和线框图
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
整合所有内容
简介
在这个项目中,我们将创建一个应用程序,允许用户注册订阅通讯。将显示一个表单,邀请用户输入他们的电子邮件地址,然后定义一些设置以提交该表单。它还将允许订阅者更改他们的设置,甚至完全取消订阅。
要创建此应用程序,我们将创建一个控制器。这将处理项目的所有部分:订阅、编辑设置和取消订阅。
我们将创建一个语言文件来存储文本,以便在需要时提供多语言支持。
我们将创建所有必要的视图文件和一个与数据库交互的模型。
然而,这个应用程序,以及本书中的所有其他应用程序,都依赖于我们在第一章中做的基本设置,简介和共享项目资源。虽然你可以将大量代码复制并粘贴到几乎任何你已有的应用程序中,但请记住,第一章中做的设置是这个章节的基础。
因此,无需多言,让我们开始吧。
设计和线框图
正如往常一样,在我们开始构建之前,我们应该看看我们计划构建的内容。
首先,让我们简要了解一下我们的意图:我们计划构建一个应用程序,允许人们注册一个联系人数据库,该数据库将用作通讯订阅数据库。我们将通过注册他们的电子邮件地址和一些选项来启用用户订阅。这些信息将被保存在数据库中。
我们还将允许人们修改他们的设置,如果他们希望的话,甚至可以取消订阅。
总之,为了更好地了解正在发生的事情,让我们看一下以下网站地图:

所以,这就是网站地图;首先要注意的是这个网站是多么简单。在这个项目中只有三个主要区域。让我们逐一了解每个项目,并简要了解它们的功能:
-
首页:这是初始着陆区域。
index()函数负责向用户显示一个表单,邀请他们订阅。 -
注册:这个处理表单输入的验证,并将验证成功的数据(如果验证成功)传递给
add()模型函数。 -
设置/取消订阅:它接受用户的电子邮件地址作为第三个和第四个
uri参数,并向订阅者显示一个表单。此表单包含分配给提供的电子邮件地址的设置。用户可以修改这些设置,如果他们希望的话,还可以取消订阅。
现在我们对网站的结构和形式有了相当好的了解,让我们来看看每个页面的线框。
主页 – index() 和注册 – index() 项目
以下截图显示了前图中第1点(主页(index())项目)和第 2 点(注册(index())项目)的线框。用户在 HTML 中看到一个名为signup_email的文本框,以及两个名为signup_opt1和signup_opt2的复选框。
小贴士
这些选项只是一个例子;如果你愿意,它们可以被删除或修改。它们旨在作为新闻通讯的过滤器。例如,你可以包括频率选项,提供每周、每月或每季度的选项。当你发送新闻通讯时,你只会根据这些选项向订阅者发送新闻通讯——就像我说的,你可以更改它们,添加更多,或者如果你愿意,可以不添加任何选项。
用户可以像以下截图所示输入他们的电子邮件地址,应用他们可能希望添加的任何选项,并提交表单。表单将被提交到signup控制器的index()函数,该函数将验证这些数据。通过验证后,Signup_model的add()函数将在signups数据库表中创建记录。

设置/取消订阅 – settings()
以下截图显示了网站地图图中第3点(设置/取消订阅(settings())项目)的线框。用户会看到一个预先填写了他们设置的表单。

我们能够获取正确的详细信息是因为 URL。用户的电子邮件地址作为 URL 的第三和第四部分出现在其中。
当用户点击取消订阅链接时,页面会被加载——可能是在一封电子邮件中。这个链接的 URL 将采用http://www.domain.com/signup/settings/name/domain.com格式。
你会注意到我们并没有使用http://www.domain.com/signup/settings/name@domain.com格式。
在第二个 URL 中,你可以看到@字符;在第一个中,你可以看到该字符被斜杠替换。实际上,我们已经将电子邮件地址的第一部分(@之前的所有内容)转换成了第三个uri参数,而uri的第四个参数来自电子邮件地址的第二部分(@之后的所有内容)。
由于安全原因,我们无法在 URL 中使用@字符,因此不能有http://www.domain.com/signup/settings/name@domain.com这样的 URL。这是 CodeIgniter 的默认行为:出于减少恶意脚本或命令运行机会的考虑,某些字符不允许出现在 URL 中。
文件概览
这是一个相对较小的项目,总的来说,我们只需要创建六个文件。具体如下:
-
/path/to/codeigniter/application/models/signup_model.php:这提供对数据库的读写访问。 -
/path/to/codeigniter/application/views/signup/signup.php:这向用户显示一个小表单,邀请他们输入他们的电子邮件地址并检查两个复选框:选项 1和选项 2。你可以修改这些选项,添加更多或完全删除它们。这些选项旨在帮助注册的人定义他们希望从应用程序中获得哪些信息。 -
/path/to/codeigniter/application/views/signup/settings.php:这向用户显示一个小表单,显示他们当前的应用程序设置。 -
/path/to/codeigniter/application/views/nav/top_nav.php:这为页面顶部提供导航栏。 -
/path/to/codeigniter/application/controllers/signup.php:这个文件包含所有必要的函数,用于注册新订阅者并修改他们的账户详情。此控制器还处理任何退订请求。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php:这为应用程序提供语言支持。
前六个文件的文件结构如下:
application/
├── controllers/
│ ├── signup.php
├── models/
│ ├── signup_model.php
├── views/signup/
│ ├── signup.php
│ ├── settings.php
├── views/nav/
│ ├── top_nav.php
├── language/english/
│ ├── en_admin_lang.php
创建数据库
好的,你应该已经按照第一章中描述的设置了 CodeIgniter 和 Bootstrap,简介和共享项目资源。如果没有,那么你应该知道,本章中的代码是专门针对第一章中的设置编写的。然而,如果你没有这样做,也不是世界末日;代码可以轻松地应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到你的数据库中:
CREATE DATABASE `signupdb`;
USE DATABASE `signupdb`;
CREATE TABLE `signups` (
`signup_id` int(11) NOT NULL AUTO_INCREMENT,
`signup_email` varchar(255) NOT NULL,
`signup_opt1` int(1) NOT NULL,
`signup_opt2` int(1) NOT NULL,
`signup_active` int(1) NOT NULL,
`signup_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`signup_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
对,让我们看一下表格中的每一项,看看它代表什么:
| 表格:注册信息 |
|---|
| 元素 |
signup_id |
signup_email |
signup_opt1 |
signup_opt2 |
signup_active |
signup_created_at |
我们还需要对config/database.php文件进行修改,即设置数据库访问详情、用户名、密码等:
-
打开
config/database.php文件,找到以下行:$db['default']['hostname'] = 'localhost'; $db['default']['username'] = 'your username'; $db['default']['password'] = 'your password'; $db['default']['database'] = 'signupdb'; -
编辑前面的值,确保用更具体于您设置和情况的值替换这些值——因此输入您的用户名、密码等。
调整 routes.php 文件
我们希望将用户重定向到signup控制器而不是默认的 CodeIgniter welcome控制器。为此,我们需要修改routes.php文件中的默认控制器设置。
打开config/routes.php文件进行编辑,并找到以下行(文件底部附近):
$route['default_controller'] = "welcome";
$route['404_override'] = '';
首先,我们需要更改默认控制器。最初,在 CodeIgniter 应用程序中,默认控制器设置为welcome。然而,我们不需要这个,我们希望默认控制器是signup。所以,找到以下行:
$route['default_controller'] = "welcome";
将前面的行替换为以下内容:
$route['default_controller'] = "signup";
$route['404_override'] = '';
创建模型
在此项目中只有一个模型——signup_model.php,它包含将订阅者添加到数据库、修改其设置以及处理订阅者取消订阅时移除订阅者的特定功能。
这是本项目唯一的模型。让我们简要地回顾一下其中的每个函数,以了解其一般功能,然后我们将更详细地讨论代码。
在此模型中有四个主要功能,具体如下:
-
add(): 这个函数接受一个参数:当用户在views/signup/signup.php中的表单提交成功时,由signup控制器的index()函数发送的$data数组。add()函数接收数组,并使用$this->db->insert()CodeIgniter Active Record 函数,将用户的注册数据插入到signups表中。 -
edit(): 这个函数接受一个参数:由signup控制器的settings()函数发送的$data数组。此函数仅在用户编辑设置而不是取消订阅时调用。edit()函数将更新用户的个人资料。 -
delete(): 这个函数接受一个参数:由signup控制器的settings()函数发送的$data数组。此函数仅在用户取消订阅而不是编辑设置时调用。如果删除成功,函数将返回true,如果不成功,则返回false。 -
get_settings(): 这个函数接受一个参数:由signup控制器的settings()函数发送的$data数组。设置表单需要用正确的数据填充所需的电子邮件地址,而get_settings()提供这些信息。
这只是一个简要概述,所以让我们创建模型并讨论其功能。
创建/path/to/codeigniter/application/models/signup_model.php文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Signup_model extends CI_Model {
function __construct() {
parent::__construct();
}
以下代码片段使用 $this->db->insert() CodeIgniter Active Record 函数将订阅者添加到数据库中。这个函数由 signup 控制器的 index() 函数调用。它接受一个名为 $data 的数组;这个数组是用户在 views/signup/signup.php 表单中提交的经过验证的表单输入。在成功写入数据库的情况下,它将返回 true;如果发生错误,它将返回 false:
public function add($data) {
if ($this->db->insert('signups', $data)) {
return true;
} else {
return false;
}
}
以下代码片段使用 $this->db->update() CodeIgniter Active Record 函数在 signups 数据库表上执行更新操作。它接受一个名为 $data 的数组。这个数组是用户在 views/signup/settings.php 表单中提交的经过验证的表单输入。在成功更新的情况下,它将返回 true;如果发生错误,它将返回 false:
public function edit($data) {
$this->db->where('signup_email', $data['signup_email']);
if ($this->db->update('signups', $data)) {
return true;
} else {
return false;
}
}
以下代码片段使用 $this->db->delete() CodeIgniter Active Record 函数在 signups 数据库表上执行删除操作。它接受一个名为 $data 的数组。这个数组是用户在 views/signup/settings.php 表单中提交的经过验证的表单输入,只包含订阅者的电子邮件地址。在成功删除的情况下,它将返回 true;如果发生错误,它将返回 false:
public function delete($data) {
$this->db->where('signup_email', $data['signup_email']);
if ($this->db->delete('signups')) {
return true;
} else {
return false;
}
}
以下代码片段使用 $this->db->get() CodeIgniter Active Record 函数在 signups 数据库表上执行选择操作。它接受一个名为 $email 的变量。这是订阅者的格式化电子邮件地址。此函数返回一个订阅者的数据库记录。这是 signup 控制器的 settings() 函数所必需的,以便预先填充表单项。在成功选择的情况下,它将返回一个数据库结果对象;如果发生错误,它将返回 false:
public function get_settings($email) {
$this->db->where('signup_email', $email);
$query = $this->db->get('signups');
if ($query) {
return $query;
} else {
return false;
}
}
}
如您所见,模型相当直接且简洁,因此现在让我们看看视图。
创建视图
在这个项目中,有三个视图,具体如下:
-
/path/to/codeigniter/application/views/signup/signup.php:此文件向用户显示一个表单,允许他们将自己的电子邮件地址注册到项目中。 -
/path/to/codeigniter/application/views/signup/settings.php:此文件向用户显示一个表单,允许他们修改偏好设置,如果他们愿意,还可以取消订阅。 -
/path/to/codeigniter/application/views/nav/top_nav.php:此文件显示顶层菜单。在这个项目中,这个文件非常简单,因此它只包含一个链接,用于返回到index()函数。
这是对观点的良好概述。现在让我们逐一过目,构建代码,并讨论它们的功能:
-
创建
/path/to/codeigniter/application/views/signup/signup.php文件,并向其中添加以下代码:<div class="row row-offcanvas row-offcanvas-right"> <div class="col-xs-12 col-sm-9"> <div class="row"> <?php echo validation_errors(); ?> <?php echo form_open('/signup') ; ?> <?php echo form_input($signup_email); ?><br /> <?php echo form_checkbox($signup_opt1) . $this->lang->line('signup_opt1'); ?><br /> <?php echo form_checkbox($signup_opt2) . $this->lang->line('signup_opt2'); ?><br /> <?php echo form_submit('', $this->lang->line('common_form_elements_go'), 'class="btn btn-success"') ; ?><br /> <?php echo form_close() ; ?> </div> </div> </div>前面的 HTML 包含一个表单,允许用户注册到应用程序。表单还显示了任何验证错误。
-
创建
/path/to/codeigniter/application/views/signup/settings.php文件,并向其中添加以下代码:<div class="row row-offcanvas row-offcanvas-right"> <div class="col-xs-12 col-sm-9"> <div class="row"> <?php echo validation_errors(); ?> <?php echo form_open('/signup/settings') ; ?> <?php echo form_input($signup_email); ?><br /> <?php echo form_checkbox($signup_opt1) . $this->lang->line('signup_opt1'); ?><br /> <?php echo form_checkbox($signup_opt2) . $this->lang->line('signup_opt2'); ?><br /> <?php echo form_checkbox($signup_unsub) . $this->lang->line('signup_unsub'); ?><br /> <?php echo form_submit('', $this->lang->line('common_form_elements_go'), 'class="btn btn-success"') ; ?><br /> <?php echo form_close() ; ?> </div> </div> </div>前面的 HTML 包含一个表单,允许订阅者编辑他们的设置或完全取消订阅。表单的数据是通过
signup_model的get_settings()函数获取的。 -
创建
/path/to/codeigniter/application/views/nav/top_nav.php文件,并向其中添加以下代码:<!-- Fixed navbar --> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="<?php echo base_url() ; ?>"><?php echo $this->lang->line('system_system_name'); ?></a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li class="active"><?php echo anchor('signup', $this->lang->line('nav_home')) ; ?></li> </ul> </div><!--/.nav-collapse --> </div> </div> <div class="container theme-showcase" role="main">
创建控制器
我们将在本项目创建仅一个控制器,即 /path/to/codeigniter/application/controllers/signup.php。
让我们现在来审查这个控制器,看看代码,并讨论它是如何工作的。
创建 /path/to/codeigniter/application/controllers/signup.php 文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Signup extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('form');
$this->load->helper('url');
$this->load->model('Signup_model');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
此函数在数据库中创建一个订阅者,因此我们首先需要做的是设置表单验证规则:
// Set validation rules
$this->form_validation->set_rules('signup_email', $this->lang->line('signup_emailemail'), 'required|valid_email|min_length[1]|max_length[125]|is_unique[signups.signup_email]');
$this->form_validation->set_rules('signup_emailopt1', $this->lang->line('signup_emailopt1'), 'min_length[1]|max_length[1]');
$this->form_validation->set_rules('signup_emailopt2', $this->lang->line('signup_emailopt2'), 'min_length[1]|max_length[1]');
// Begin validation
if ($this->form_validation->run() == FALSE) {
如果表单提交时出现错误,或者这是函数的第一次加载实例,那么我们将到达以下代码。我们在 views/signup/signup.php 文件中为表单元素定义以下设置:
$data['signup_email'] = array('name' => 'signup_email', 'class' => 'form-control', 'id' => 'signup_email', 'value' => set_value('signup_email', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signup_email'));
$data['signup_opt1'] = array('name' => 'signup_opt1', 'id' => 'signup_opt1', 'value' => '1', 'checked' => FALSE, 'style' => 'margin:10px');
$data['signup_opt2'] = array('name' => 'signup_opt2', 'id' => 'signup_opt2', 'value' => '1', 'checked' => FALSE, 'style' => 'margin:10px');
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
$this->load->view('signup/signup', $data);
$this->load->view('common/footer');
} else {
然而,如果没有验证错误,我们将到达以下代码。我们将表单元素的从数据打包到一个名为 $data 的数组中,并将其发送到 signup_model 的 add() 函数。这将执行将订阅者写入数据库的任务:
$data = array('signup_email' => $this->input->post('signup_email'),
'signup_opt1' => $this->input->post('signup_opt1'),
'signup_opt2' => $this->input->post('signup_opt2'),
'signup_active' => 1);
if ($this->Signup_model->add($data)) {
echo $this->lang->line('signup_success');
} else {
echo $this->lang->line('signup_error');
}
}
}
以下函数负责更新订阅者的设置或处理取消订阅请求。在它能够执行这两者中的任何一项之前,它需要用户的电子邮件地址。电子邮件地址是在订阅者点击链接(例如电子邮件中的取消订阅链接)时提供的:
public function settings() {
// Set validation rules
$this->form_validation->set_rules('signup_email', $this->lang->line('signup_email'), 'required|valid_email|min_length[1]|max_length[125]');
$this->form_validation->set_rules('signup_opt1', $this->lang->line('signup_opt1'), 'min_length[1]|max_length[1]');
$this->form_validation->set_rules('signup_opt2', $this->lang->line('signup_opt2'), 'min_length[1]|max_length[1]');
$this->form_validation->set_rules('signup_unsub', $this->lang->line('signup_unsub'), 'min_length[1]|max_length[1]');
// Begin validation
if ($this->form_validation->run() == FALSE) {
如果验证失败,或者表单是第一次被访问,那么我们将到达以下代码。我们首先尝试获取订阅者的详细信息,以便我们可以在表单中显示正确的设置。我们将 uri 段落的第三个和第四个参数传递给 signup_model 的 get_settings() 函数。我们通过在两个 uri 段落之间写入 @ 符号来连接它们,记住出于安全原因我们不能在 URL 中接受 @ 符号。这可以按以下方式完成:
$query = $this->Signup_model->get_settings($this->uri->segment(3) . '@' . $this->uri->segment(4));
if ($query->num_rows() == 1) {
foreach ($query->result() as $row) {
$signup_opt1 = $row->signup_opt1;
$signup_opt2 = $row->signup_opt2;
}
} else {
redirect('signup');
}
signup_model 的 get_settings() 函数将在 signups 表中查找并返回一个结果对象。
首先,我们测试找到的记录数是否正好是 1。如果不是这样,就存在问题:要么数据库中存在属于同一电子邮件地址的多个记录,要么根本找不到电子邮件地址,在这种情况下,我们将用户重定向到 index() 函数。
无论如何,如果正好找到一条记录,我们然后使用 foreach 循环遍历结果对象,并将我们将用于填充表单选项的值放入局部变量中:$signup_opt1 和 $signup_opt2。
然后,我们为我们的表单元素定义设置,将 $signup_email、$signup_opt1 和 $signup_opt2 以及取消订阅复选框的设置传递给它们:
$data['signup_email'] = array('name' => 'signup_email', 'class' => 'form-control', 'id' => 'signup_email', 'value' => set_value('signup_email', $this->uri->segment(3) . '@' . $this->uri->segment(4)), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signup_email'));
$data['signup_opt1'] = array('name' => 'signup_opt1', 'id' => 'signup_opt1', 'value' => '1', 'checked' => ($signup_opt1 == 1) ? TRUE : FALSE, 'style' => 'margin:10px');
$data['signup_opt2'] = array('name' => 'signup_opt2', 'id' => 'signup_opt2', 'value' => '1', 'checked' => ($signup_opt2 == 1) ? TRUE : FALSE, 'style' => 'margin:10px');
$data['signup_unsub'] = array('name' => 'signup_unsub', 'id' => 'signup_unsub', 'value' => '1', 'checked' => FALSE, 'style' => 'margin:10px');
这些表单元素设置随后被发送到views/signup/settings.php文件:
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
$this->load->view('signup/settings', $data);
$this->load->view('common/footer');
} else {
如果表单提交没有错误,那么我们将到达以下代码。我们首先做的是确定用户是否表示他们希望取消订阅。这是通过查找signup_unsub表单复选框的值来完成的。如果用户已经勾选了这个复选框,那么就没有必要更新他们的设置。相反,我们通过调用signup_model的delete()函数来删除用户:
if ($this->input->post('signup_unsub') == 1) {
$data = array('signup_email' => $this->input->post('signup_email'));
if ($this->Signup_model->delete($data)) {
echo $this->lang->line('unsub_success');
} else {
echo $this->lang->line('unsub_error');
}
} else {
然而,如果他们没有通过勾选名为signup_unsub的表单复选框来表示他们想要取消订阅,那么我们就会想要更新他们的详细信息。我们将表单输入的值打包到一个名为$data的数组中,并使用signup_model的edit()函数将其准备好写入数据库:
$data = array('signup_email' => $this->input->post('signup_email'),
'signup_opt1' => $this->input->post('signup_opt1'),
'signup_opt2' => $this->input->post('signup_opt2'));
if ($this->Signup_model->edit($data)) {
echo $this->lang->line('setting_success');
} else {
echo $this->lang->line('setting_error');
}
}
}
}
}
因此,这就是signup控制器。正如你所看到的,它是一个小巧、简洁的脚本,我相信你将能够按照你的意愿对其进行修改和扩展。
创建语言文件
与本书中的所有项目一样,我们正在使用语言文件来向用户提供文本。这样,你可以启用多区域/多语言支持。让我们创建语言文件。
创建/path/to/codeigniter/application/language/english/en_admin_lang.php文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
s// General
$lang['system_system_name'] = "Signup";
// nav
$lang['nav_home'] = "Home";
// index()
$lang['singup_instruction'] = "";
$lang['signup_email'] = "Your Email";
$lang['signup_opt1'] = "Option 1";
$lang['signup_opt2'] = "Option 2";
$lang['signup_unsub'] = "Unsubscribe";
$lang['signup_success'] = "You have signed up";
$lang['signup_error'] = "There was an error in signing up";
$lang['setting_success'] = "Your settings have been amended";
$lang['setting_error'] = "There was an error in amending your settings";
$lang['unsub_success'] = "You have been unsubscribed";
$lang['unsub_error'] = "There was an error in unsubscribing you";
将所有这些放在一起
好的,这里有一些示例,将有助于将所有这些放在一起。
用户订阅
用户订阅时发生的事件顺序如下:
-
用户访问应用程序,CodeIgniter 将他们路由到
signup控制器。 -
signup控制器中的index()函数显示views/signup/signup.php视图文件。 -
用户在浏览器中查看表单,输入他们的电子邮件地址,并且没有验证错误地提交表单。
-
index()函数将用户的输入打包到一个名为$data的数组中,并将其传递给Signup_model的add()函数。 -
add()函数执行 Active Record 插入,将用户的订阅写入signups数据库表。
用户更新他们的设置
当用户想要更新设置时,以下事件会发生:
-
用户点击他们收到的电子邮件中的链接。
-
URL 将它们路由到
signup控制器的settings()函数。 -
settings()函数将 URL 的第三和第四个参数合并,使用@字符连接第三和第四部分,并将这个“重建”的电子邮件地址传递给signup控制器的get_settings()函数。 -
get_settings()函数在数据库中查找匹配的记录,如果找到正好一条记录,它将作为数据库结果对象返回给settings()函数。 -
现在
settings()函数有了匹配的记录,它从结果对象中取出各种项目,并将它们分配给局部变量。 -
这些随后被用来预填充
views/signup/settings.php文件中的表单项。 -
用户看到显示的表单,其中包含记录中填写的任何设置。
-
用户希望勾选选项 1但保持选项 2未勾选。用户点击了选项 1的复选框。
-
用户提交表单,表单提交到
signup控制器的settings()函数,并且没有错误地成功验证。 -
由于没有错误,验证测试的第二部分(其余部分)被运行。
-
检查表单元素
signup_unsub的值。由于用户没有取消订阅,这不会等于1。 -
由于
signup_unsub不等于1,signup_model的edit()函数接收一个名为$data的数组。这个$data数组包含已提交表单的数据内容。 -
然后
edit()函数对$data数组执行 CodeIgniter Active Record 更新操作。
用户取消订阅
当用户想要取消订阅时,以下事件发生:
-
用户点击他们收到的电子邮件中的一个链接。
-
URL 将他们路由到
signup控制器的settings()函数。 -
settings()函数获取 URL 的第三和第四个参数,使用@字符连接第三和第四个段,并将这个“重建”的电子邮件地址传递给signup控制器的get_settings()函数。 -
get_settings()函数在数据库中查找匹配的记录,如果找到正好一条记录,它将作为数据库结果对象返回给settings()函数。 -
现在
settings()函数有一个匹配的记录,它从结果对象中取出各种项目并将它们分配给局部变量。 -
这些随后被用于在
views/signup/settings.php文件中预填充表单项。 -
用户看到显示的表单,其中包含记录中填写的任何设置。
-
用户希望取消该服务的订阅。
-
用户勾选取消订阅并提交表单。表单提交到
signup控制器的settings()函数,并且没有错误地成功验证。 -
由于没有错误,验证测试的第二部分(其余部分)被运行。
-
检查表单元素
signup_unsub的值。由于用户正在取消订阅,这等于1。 -
由于
signup_unsub等于1,signup_model的delete()函数接收一个名为$data的数组。这个$data数组包含订阅者的电子邮件地址。 -
然后
delete()函数对$data数组执行 CodeIgniter Active Record 删除操作。
摘要
在这个项目中,你将拥有一个有用的注册应用程序的基础。像往常一样,有一些事情你可以做来扩展功能,具体如下:
-
添加更多用户可能应用于其订阅的选项
-
添加 HTML/纯文本设置(并且只发送他们所请求的内容)
-
添加一个注册日落条款:允许某人在特定时间内注册,一旦到达该时间,就停止向他们发送新闻通讯。
第六章:创建认证系统
CodeIgniter 没有自带用户认证系统(呃,那个短语),但无论如何,它没有。如果您想管理用户和会话,您有多种选择。您可以安装 auth Spark,或者您可以开发自己的解决方案——这正是我们将在这里做的。
我对其他“第三方”插件(无论其用途如何)的烦恼之一是代码几乎总是难以理解,这使得维护和集成变得困难。这个认证系统是我能使其尽可能简单,希望它将很容易为您适应和扩展。
本章提供的认证系统将允许您创建和管理用户、密码重置、用户电子邮件通知、用户登录等。
在本章中,我们将涵盖以下主题:
-
设计和线框
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
整合所有内容
简介
要创建此应用程序,我们将创建五个控制器:一个用于处理会话登录,一个用于处理管理功能(CRUD 操作),一个用于用户密码管理,一个允许用户注册,以及一个在用户登录后提供功能的控制器。
我们还将创建一个语言文件来存储文本,以便在需要时支持多种语言。
我们将对config.php文件进行修改,以允许对会话和密码支持所需的加密支持。
我们将创建所有必要的视图文件,甚至一个 CSS 文件来帮助 Bootstrap 处理一些视图。
然而,这个应用程序以及本书中的其他应用程序,都依赖于我们在第一章中完成的基本设置,简介和共享项目资源;尽管你可以将大量代码复制并粘贴到几乎任何你已有的应用程序中,但请记住,第一章中完成的基本设置是本章的基础。
所以,无需多言,让我们开始吧。
设计和线框
和往常一样,在我们开始构建之前,我们应该先看看我们打算构建什么。
首先,简要描述我们的意图:我们计划构建一个应用程序,它将提供以下功能:
-
管理员可以管理系统中的所有用户,并允许个别用户编辑和更新自己的数据。
-
如果用户忘记了密码,他们可以重置密码;确认此操作的电子邮件将发送给这些用户
-
新用户可以注册并成为系统的一部分;密码将被生成并通过电子邮件发送给他们
我们还将探讨如何实现代码来检查用户的访问级别。您可以在您的项目中使用此代码来限制用户访问特定的控制器和控制器函数。
为了更好地了解正在发生的事情,让我们看看以下网站地图:

因此,这是网站地图;现在,让我们逐一查看每个项目,并简要了解它们的功能:
-
登录:想象这是一个起点。用户将能够在此处登录。根据
users.usr_access_level的值,他们将被导向me或users控制器。me控制器是普通用户编辑和更新他们详情的地方,而users控制器提供了一个管理员管理所有用户的地方。 -
我的信息:目前,这向用户显示一个表单;然而,请将此区域视为非管理员用户的仪表板区域。管理员将
users.usr_access_level值设置为1。目前,me控制器将加载index函数,允许用户编辑他们的详情——说到这里,让我们看看下一个块。 -
编辑我的详细信息:这将向当前用户显示一个表单。该表单允许用户更改并保存他们的联系数据。
-
用户:
users控制器处理管理员功能,例如所有用户的 CRUD 操作、密码重置和密码混淆(针对所有用户)。 -
查看所有:此列表显示所有用户及其在数据库中的当前状态。用户以表格形式显示。那些活跃的用户(
users.usr_is_active = 1)的行没有背景色,而不活跃的用户(users.usr_is_active = 0)的行有橙色背景色。 -
创建:正如其名所示,这将显示包含表单的
users/new_user视图,允许管理员在系统中创建用户。 -
编辑:这显示了一个与上一个类似的表单,但它是预先填充了当前登录用户的详细信息。当管理员在查看所有页面中点击编辑链接时,将加载此表单。
-
删除:这显示了一个确认页面,要求管理员确认他们是否希望删除用户。当管理员在查看所有页面中点击编辑链接时,将加载此页面。
-
忘记密码:这向用户显示一个表单。用户被邀请在表单文本字段中输入他们的电子邮件地址并点击提交。如果电子邮件地址存在于数据库中,则将发送一封包含在正文中的 URL 的电子邮件到该电子邮件地址。此 URL 是该认证系统的重置 URL。附加到 URL 的是一个系统用于验证密码重置请求是否真实的唯一代码。
-
注册:这向用户显示一个表单,邀请他们输入他们的名字、姓氏和电子邮件地址。一旦成功提交(没有验证错误),新用户将被添加到系统中,并向新用户发送一封电子邮件,告知他们密码;他们的密码是在注册时由系统自动生成的。
现在我们对网站的结构和形式有了相当好的了解,让我们看看每个页面的线框图。
我 – 编辑详情
以下截图显示了从网站地图中讨论的“编辑我的详情”项的线框图。普通用户(非管理员用户)可以在 HTML 表单中查看他们的详细信息,并通过点击保存,他们可以更新这些详细信息。

查看所有用户
以下截图显示了从网站地图中的“查看所有”项的线框图。管理员用户能够以表格网格的形式看到系统中的所有用户。用户被列出,并具有编辑和删除选项,管理员用户可以使用这些选项。

创建用户
以下截图显示了从网站地图中的第6点(创建用户项)的线框图。它显示了一个包含文本字段的表单,允许管理员输入用户的详细信息。请注意,用户访问级别可以在此设置;级别1在系统中被视为管理员,因此用户将能够获得分配给他们的管理员权限,而更高的数字是普通用户。目前,系统只理解级别2(作为普通用户);下拉菜单有五个级别——您可以根据需要应用这些级别或甚至添加更多。将用户设置为活动状态(users.usr_is_active = 1)或非活动状态(users.usr_is_active = 0)将在登录时限制用户。活动用户将会有他们的登录请求由signin脚本处理,而非活动用户则不会。

编辑用户详情
编辑用户详情的表单与上一节中讨论的新用户功能类似。当管理员用户点击名称旁边的编辑链接(在/views/users/view_all_users.php视图文件中)时,通过网站地图的第5点(查看用户功能)访问。这里有趣的不同之处在于带有重置密码电子邮件选项的其他选项面板。这将重置用户的密码,并发送电子邮件通知他们新的密码。

删除用户
这是一个最终确认页面,要求允许删除用户。它通过网站地图中的第5点(查看用户功能)访问。管理员点击编辑链接以查看编辑用户页面。点击删除将从users表中删除用户,而点击“取消”将使管理员返回到第5点(查看用户项)。

登录
以下截图显示了登录页面的布局。用户可以输入他们的用户名和密码,然后点击登录按钮。验证错误将显示在表单上方(然而,在以下截图中没有显示验证错误)。还有一个链接,供某人启动重置密码的过程。忘记密码链接将显示一个新表单,允许该人输入电子邮件地址。

注册
注册功能允许非用户注册到系统中。潜在用户被提示输入他们的名字、姓氏和电子邮件地址。我们使用他们的名字和姓氏在欢迎电子邮件中使用,该电子邮件将发送到本阶段输入的电子邮件地址。

文件概览
我们将为这个项目创建很多文件,总共 23 个文件,具体如下:
-
/path/to/codeigniter/application/core/MY_Controller.php: 这作为users.php控制器等子类的一个父类。它提供了常用助手、库和错误分隔符等常用资源。 -
/path/to/codeigniter/application/models/password_model.php: 这提供了对数据库的读写访问——特别是在users表周围——专注于密码特定操作。 -
/path/to/codeigniter/application/models/signin_model.php: 这提供了特定于登录过程的函数。 -
/path/to/codeigniter/application/models/users_model.php: 这提供了特定于users表的函数。 -
/path/to/codeigniter/application/model/register_model.php: 这提供了帮助用户被添加到users表中的方法,而无需管理员首先创建它们。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 这在页面顶部提供了一个导航栏。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php: 这为应用程序提供语言支持。 -
/path/to/codeigniter/application/views/users/new_user.php: 这允许管理员创建新用户。用户被保存在users表中。 -
/path/to/codeigniter/application/views/users/view_all_users.php: 这允许管理员查看users表中的所有用户列表。 -
/path/to/codeigniter/application/views/users/delete_user.php: 这允许管理员删除用户。 -
/path/to/codeigniter/application/views/users/edit_user.php: 这允许管理员编辑用户的详细信息。 -
/path/to/codeigniter/application/views/users/forgot_password.php: 这允许未登录的用户重置他们的密码。此视图包含一个简单的表单,要求用户输入他们的电子邮件地址。将带有唯一代码的电子邮件发送到该地址。此代码用于确保更改密码请求是真实的。 -
/path/to/codeigniter/application/views/users/me.php: 这允许非管理员用户编辑他们的详细信息。 -
/path/to/codeigniter/application/views/users/new_password.php: 这允许未登录的用户输入新密码。 -
/path/to/codeigniter/application/views/users/register.php: 这允许尚未成为用户(users表中的记录)的人登录并生成users表中的新行。 -
/path/to/codeigniter/application/views/users/signin.php: 这显示了一个简单的登录表单。 -
/path/to/codeigniter/application/views/users/change_password.php:这允许已登录的用户重置他们的密码。 -
/path/to/codeigniter/application/views/email_scripts/welcome.txt:这个文件包含简单的欢迎文本。 -
/path/to/codeigniter/application/views/email_scripts/new_password.txt:这个文件包含一个简单的说明,指导用户点击链接以打开password/new_password控制器函数。 -
/path/to/codeigniter/application/views/email_scripts/reset_password.txt:这个文件包含一个简单的消息,通知用户他们的密码已被更改。
上述 23 个文件的文件结构如下所示:
application/
├── core/
│ ├── MY_Controller.php
├── controllers/
│ ├── me.php
│ ├── password.php
│ ├── register.php
│ ├── signin.php
│ ├── users.php
├── models/
│ ├── password_model.php
│ ├── register_model.php
│ ├── signin_model.php
│ ├── users_model.php
├── views/users/
│ ├── new_user.php
│ ├── view_all_users.php
│ ├── delete_user.php
│ ├── edit_user.php
│ ├── forgot_password.php
│ ├── me.php
│ ├── new_password.php
│ ├── register.php
│ ├── signin.php
│ ├── change_password.php
├── views/email_scripts/
│ ├── welcome.txt
│ ├── new_password.txt
│ ├── reset_password.txt
├── views/nav/
│ ├── top_nav.php
├── views/common/
│ ├── login_header.php
├── language/english/
│ ├── en_admin_lang.php
bootstrap/
├── css/
├── signin.css
注意列表中的最后一项,即 signin.css。这个文件位于 bootstrap/css/ 文件夹中,与 CodeIgniter 的 application 文件夹处于同一级别。我们在 第一章,简介和共享项目资源 中安装了 Twitter Bootstrap。在本章中,我们将介绍如何将 bootstrap 文件夹放置在正确的文件夹级别和位置。
创建数据库
好吧,你应该已经按照 第一章,简介和共享项目资源 中描述的方式设置了 CodeIgniter 和 Bootstrap。如果没有,那么你应该知道,本章中的代码是专门针对 第一章,简介和共享项目资源 中的设置编写的。然而,如果你还没有这样做,也不是世界末日——代码可以轻松地应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到你的数据库中:
CREATE DATABASE `user_auth`;
USE `user_auth`;
CREATE TABLE `ci_sessions` (
`session_id` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '0',
`ip_address` varchar(16) COLLATE utf8_bin NOT NULL DEFAULT '0',
`user_agent` varchar(120) COLLATE utf8_bin DEFAULT NULL,
`last_activity` int(10) unsigned NOT NULL DEFAULT '0',
`user_data` text COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`session_id`),
KEY `last_activity_idx` (`last_activity`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `users` (
`usr_id` int(11) NOT NULL AUTO_INCREMENT,
`acc_id` int(11) NOT NULL COMMENT 'account id',
`usr_fname` varchar(125) NOT NULL,
`usr_lname` varchar(125) NOT NULL,
`usr_uname` varchar(50) NOT NULL,
`usr_email` varchar(255) NOT NULL,
`usr_hash` varchar(255) NOT NULL,
`usr_add1` varchar(255) NOT NULL,
`usr_add2` varchar(255) NOT NULL,
`usr_add3` varchar(255) NOT NULL,
`usr_town_city` varchar(255) NOT NULL,
`usr_zip_pcode` varchar(10) NOT NULL,
`usr_access_level` int(2) NOT NULL COMMENT 'up to 99',
`usr_is_active` int(1) NOT NULL COMMENT '1 (active) or 0 (inactive)',
`usr_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`usr_pwd_change_code` varchar(50) NOT NULL,
PRIMARY KEY (`usr_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
你会看到我们首先创建的表是 ci_sessions。我们需要这个表来允许 CodeIgniter 管理会话,特别是登录用户。然而,它只是从 CodeIgniter 用户指南 中可用的标准会话表,所以我不包括该表的描述,因为它在技术上并不特定于这个应用程序。不过,如果你感兴趣,可以在 ellislab.com/codeigniter/user-guide/libraries/sessions.html 找到描述。
好的,让我们逐个查看每个表中的每个条目,看看它的含义:
| 表:users |
|---|
| 元素 |
usr_id |
acc_id |
usr_fname |
usr_lname |
usr_uname |
usr_email |
| usr_hash | 这是用户密码的散列。users.usr_hash中的值以两种方式生成。第一种是当有人手动更改密码时(可能是通过“忘记密码”过程)。$this->encrypt->sha1($this->input->post('usr_password1')); CodeIgniter 函数包含用户的新密码。第二种创建密码的方式是当系统生成密码并发送给用户时,例如,当管理员手动创建新用户时。这种方式下,管理员不知道新用户的密码。为了实现这一点,CodeIgniter 使用相同的sha1()加密函数;然而,与通过$POST从用户那里提供的密码不同,它是通过创建一个随机字符串并将其传递给sha1()来生成的,如下所示:
$password = random_string('alnum', 8);
$hash = $this->encrypt->sha1($password);
|
usr_add1 |
这是个人地址的第一行。 |
|---|---|
usr_add2 |
这是个人地址的第二行。 |
usr_add3 |
这是个人地址的第三行。 |
usr_town_city |
这是个人地址的城镇或城市。 |
usr_zip_pcode |
这是个人地址的邮政编码或邮编。 |
usr_access_level |
这表示用户的权限级别。权限级别可以控制用户允许执行的操作。 |
usr_is_active |
这表示用户是否活跃(1)或非活跃(0)——非活跃意味着用户无法登录。 |
usr_created_at |
这是记录创建时创建的 MySQL 时间戳。 |
usr_pwd_change_code |
这是一个当某人希望更改密码时生成的唯一代码。这个唯一代码会生成并发送给希望更改密码的用户。该代码附加在电子邮件正文的 URL 中。用户点击此链接将被重定向到认证系统。系统查看该代码以检查其是否有效并匹配电子邮件。如果匹配,用户可以按照屏幕上的说明创建一个新的密码。 |
我们还需要对config/database.php文件进行修改,即设置数据库访问详情、用户名密码等。
打开config/database.php文件并找到以下行:
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'your username';
$db['default']['password'] = 'your password';
$db['default']['database'] = 'user_auth';
编辑前述行中的值,确保用更具体于您设置和情况的值替换这些值;因此,输入您的用户名、密码等。
调整 config.php 文件
在此文件中,有一些设置我们需要配置以支持会话和加密。因此,打开config/config.php文件并做出以下更改:
-
我们需要设置一个加密密钥;会话和 CodeIgniter 的加密功能都需要在
$config数组中设置一个加密密钥,因此找到以下行:$config['encryption_key'] = '';然后,将其更改为以下内容:
$config['encryption_key'] = 'a-random-string-of-alphanum-characters';小贴士
现在,显然实际上不要将此值更改为一个随机的字母数字字符序列,而是改为,嗯,一个随机的字母数字字符序列——如果这说得通?是的,你知道我的意思。
-
接下来,找到以下行:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; $config['sess_encrypt_cookie'] = FALSE; $config['sess_use_database'] = FALSE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;然后,将其更改为以下内容:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = TRUE; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;
调整 routes.php 文件
我们希望将用户重定向到 signin 控制器而不是默认的 CodeIgniter welcome 控制器。我们需要修改 routes.php 文件中的默认控制器设置以反映这一点:
-
打开
config/routes.php文件进行编辑,并找到以下行(在文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。最初在 CodeIgniter 应用程序中,默认控制器设置为
welcome;然而,我们不需要这个;相反,我们希望默认控制器是signin。所以,找到以下行:$route['default_controller'] = "welcome";用以下内容替换它:
$route['default_controller'] = "signin";
创建模型
本项目中有四个模型,如下所示:
-
models/password_model.php:此文件包含创建和重置密码的特定函数。 -
models/register_model.php:此文件包含特定于用户注册的函数。 -
models/signin_model.php:此文件包含特定于用户登录系统的函数。 -
models/users_model.php:此文件包含本项目的模型函数的主体,特别是对用户执行 CRUD 操作以及各种其他管理功能。
因此,这是本项目的模型概述;现在,让我们去创建每个模型。
创建 /path/to/codeigniter/application/models/password_model.php 文件,并将其中的以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Password_model extends CI_Model {
function __construct() {
parent::__construct();
}
does_code_match() 函数将检查在 URL 中提供的代码是否与数据库中的代码匹配。如果匹配,它返回 true;如果不匹配,它返回 false。这在此处显示:
function does_code_match($code, $email) {
$query = "SELECT COUNT(*) AS `count`
FROM `users`
WHERE `usr_pwd_change_code` = ?
AND `usr_email` = ? ";
$res = $this->db->query($query, array($code, $email));
foreach ($res->result() as $row) {
$count = $row->count;
}
if ($count == 1) {
return true;
} else {
return false;
}
}
}
创建 /path/to/codeigniter/application/models/register_model.php 模型文件,并将其中的以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Register_model extends CI_Model {
function __construct() {
parent::__construct();
}
public function register_user($data) {
if ($this->db->insert('users', $data)) {
return true;
} else {
return false;
}
}
}
register 模型只包含一个函数,即 register_user()。它简单地使用 CodeIgniter Active Record insert() 类将 $data 数组的内 容插入到 users 表中。
创建 /path/to/codeigniter/application/models/users_model.php 模型文件,并将其中的以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Users_model extends CI_Model {
function __construct() {
parent::__construct();
}
function get_all_users() {
return $this->db->get('users');
}
function process_create_user($data) {
if ($this->db->insert('users', $data)) {
return $this->db->insert_id();
} else {
return false;
}
}
function process_update_user($id, $data) {
$this->db->where('usr_id', $id);
if ($this->db->update('users', $data)) {
return true;
} else {
return false;
}
}
function get_user_details($id) {
$this->db->where('usr_id', $id);
$result = $this->db->get('users');
if ($result) {
return $result;
} else {
return false;
}
}
function get_user_details_by_email($email) {
$this->db->where('usr_email', $email);
$result = $this->db->get('users');
if ($result) {
return $result;
} else {
return false;
}
}
function delete_user($id) {
if($this->db->delete('users', array('usr_id' => $id))) {
return true;
} else {
return false;
}
}
让我们看看 make_code() 函数。此函数创建一个唯一的代码并将其保存到用户的记录中。此代码在电子邮件的 URL 结尾处发送给用户。如果 URL 中的此代码与数据库中的代码匹配,那么它很可能是真正的密码更改,因为不太可能有人能准确猜出代码。
注意 PHP 构造 do…while 的样子:
do {
// something
} while ('…a condition is met');
因此,这意味着在满足条件时执行某些操作。考虑到这一点,思考我们的问题;我们必须将users.usr_pwd_change_code分配一个在数据库中不存在的值。该代码应该是一个唯一的值,以确保其他人不会错误地更改其密码。
我们使用do…while结构作为创建代码的手段,首先创建代码,然后通过遍历users表来查找该代码的另一个实例。如果找到,则返回的行数将大于或等于一。然后,生成另一个代码,并在users表中再次进行搜索。
这将重复进行,直到生成一个在users表中找不到的代码。然后,这个唯一的代码作为$url_code返回:
function make_code() {
do {
$url_code = random_string('alnum', 8);
$this->db->where('usr_pwd_change_code = ', $url_code);
$this->db->from('users');
$num = $this->db->count_all_results();
} while ($num >= 1);
return $url_code;
}
function count_results($email) {
$this->db->where('usr_email', $email);
$this->db->from('users');
return $this->db->count_all_results();
}
以下update_user_password()函数接受包含用户主键和新密码的数据数组。该数组由password_model的new_password()函数提供。用户的 ID(users.usr_id)来自会话(因为他们已登录),新密码来自new_password()加载的表单(views/users/new_password.php):
function update_user_password($data) {
$this->db->where('usr_id', $data['usr_id']);
if ($this->db->update('users', $data)) {
return true;
} else {
return false;
}
}
function does_code_match($data, $email) {
$query = "SELECT COUNT(*) AS `count`
FROM `users`
WHERE `usr_pwd_change_code` = ?
AND `usr_email` = ? ";
$res = $this->db->query($query, array($data['code'], $email));
foreach ($res->result() as $row) {
$count = $row->count;
}
if ($count == 1) {
return true;
} else {
return false;
}
}
function update_user_code($data) {
$this->db->where('usr_email', $data['usr_email']);
if ($this->db->update('users', $data)) {
return true;
} else {
return false;
}
}
}
创建/path/to/codeigniter/application/models/signin_model.php模型文件,并将其中的以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Signin_model extends CI_Model {
function __construct() {
parent::__construct();
}
public function does_user_exist($email) {
$this->db->where('usr_email', $email);
$query = $this->db->get('users');
return $query;
}
}
此模型除了__construct()函数外,还包含一个函数,即does_user_exist($email)。此函数接受用户从登录视图提交的电子邮件地址,并返回活动记录查询。
查询在signin控制器中使用 CodeIgniter 数据库函数num_rows()进行评估:
$query = $this->Signin_model->does_user_exist($usr_email);
if ($query->num_rows() == 1) {
...
如果只有一个匹配项,则signin控制器会遍历活动记录结果,并尝试登录用户。
创建视图
在此项目中,有相当多的视图文件和电子邮件模板文件——实际上,我们将创建 10 个视图文件,3 个电子邮件脚本,以及一个特定于登录和修改导航文件的头部文件。好吧,让我们开始吧。
以下是在此项目中使用的标准视图文件:
-
path/to/codeigniter/application/views/users/new_user.php: 这向管理员用户显示一个表单,允许他们创建用户。新用户会收到一封电子邮件,欢迎他们加入系统,并告知他们密码。电子邮件脚本位于/views/email_scripts/welcome.txt。 -
path/to/codeigniter/application/views/users/view_all_users.php: 这显示系统中当前的用户列表。管理员用户能够编辑或删除用户。 -
path/to/codeigniter/application/views/users/delete_user.php: 这向管理员用户显示一个确认页面。如果管理员用户在view_all_users/php视图中按下删除键,则会显示此页面。确认页面询问管理员用户是否真的希望删除所选用户。 -
path/to/codeigniter/application/views/users/edit_user.php: 这将向管理员用户显示一个表单。如果管理员用户在view_all_users.php视图中按下编辑,则会显示此表单。该表单类似于new_user.php文件,除了有一个面板,管理员用户可以发送电子邮件给用户以重置他们的密码。 -
path/to/codeigniter/application/views/users/forgot_password.php: 这将向任何请求电子邮件地址的人显示一个表单。如果此电子邮件地址在系统中,将向他们发送一封电子邮件,其中包含重置密码的说明。 -
path/to/codeigniter/application/views/users/me.php: 这将向当前登录用户显示一个表单。该表单类似于edit_user.php。它允许当前登录用户编辑和修改他们的账户详情。 -
path/to/codeigniter/application/views/users/new_password.php: 这将向任何人显示一个表单,邀请他们输入他们的电子邮件地址——从忘记密码过程中生成的代码已经是隐藏的表单元素。代码和电子邮件地址将被比较,如果代码匹配,将为用户生成一个新的密码。 -
path/to/codeigniter/application/views/users/register.php: 这将向用户显示一个表单,允许他们输入他们的姓名和电子邮件地址。然后他们将被添加到数据库中,并为他们生成一个密码。此密码将通过电子邮件发送给他们,并附有欢迎信息。此电子邮件的文本位于/views/email_scripts/welcome.txt。 -
path/to/codeigniter/application/views/users/signin.php: 这将显示一个表单。该表单允许用户(普通或管理员)使用用户名和密码登录系统;请记住,他们的密码没有存储在users表中,只存储了密码的散列。为了支持这种散列,我们需要在配置文件中更改加密密钥。我们在本章的 调整 config.php 文件 部分讨论了这一点。 -
path/to/codeigniter/application/views/users/change_password.php: 这将向任何登录用户显示一个表单。该表单允许用户(普通或管理员)更改他们的密码。
以下是在此应用程序中使用的电子邮件脚本:
-
path/to/codeigniter/application/views/email_scripts/welcome.txt: 此文件包含当新用户被管理员从new_user.php表单添加或当他们使用register.php视图中的表单自行创建账户时发送的欢迎电子邮件的文本。 -
path/to/codeigniter/application/views/email_scripts/new_password.txt: 此文件包含通知用户更改密码的文本。 -
path/to/codeigniter/application/views/email_scripts/reset_password.txt: 此文件包含一个用户可以点击以开始重置密码过程的 URL。该 URL 包含一个系统用于确保这是一个真实的密码更改请求的唯一代码。
以下是在此应用程序中使用的登录页眉和导航视图:
-
path/to/codeigniter/application/views/common/login_header.php:登录表单的 CSS 要求与标准/views/common/header.php视图不同。具体来说,它需要signin.css文件。 -
path/to/codeigniter/application/views/nav/top_nav.php:它包含允许管理员和普通用户打开各种页面的导航选项,并且还包含一个注销链接,允许用户终止他们的会话。
正确,这些是视图文件,包括标准 HTML 和 TXT 文件用于电子邮件等。让我们逐一查看每个文件并创建它们。
创建/path/to/codeigniter/application/views/users/register.php文件,并将其中的以下代码添加到该文件中:
<div class="container">
<?php echo validation_errors(); ?>
<?php echo form_open('register/index', 'role="form" class="form-signin"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('register_page_title'); ?></h2>
<input type="text" class="form-control" name="usr_fname" placeholder="<?php echo $this->lang->line('register_first_name'); ?>" autofocus>
<input type="text" class="form-control" name="usr_lname" placeholder="<?php echo $this->lang->line('register_last_name'); ?>" >
<input type="email" class="form-control" name="usr_email" placeholder="<?php echo $this->lang->line('register_email'); ?>" >
<?php echo form_submit('submit', 'Register', 'class="btn btn-lg btn-primary btn-block"'); ?>
</form>
</div>
这向系统中的潜在用户显示一个表单。它要求用户输入名字、姓氏和电子邮件地址。表单提交到register/index,该表单将验证用户输入的数据。如果没有错误,则用户将被添加到users表中,为他们生成一个密码,并生成一个哈希值存储为users.usr_hash并发送给他们。电子邮件模板是welcome.txt,如下所示。
创建/path/to/codeigniter/application/views/email_scripts/welcome.txt文件,并将其中的以下代码添加到该文件中:
Dear %usr_fname% %usr_lname%,
Welcome to the site. Your password is:
%password%
Regards,
The Team
这是当用户注册时发送给用户的欢迎电子邮件的文本。请注意,有三个文本字符串被百分号(%)包围。这些是将在注册过程中被识别并使用str_replace() PHP 函数替换其真实值的文本字符串。例如,假设我注册了该网站。我的名字是 Robert Foster,我的电子邮件可能是rob-foster@domain.com。发送到rob-foster@domain.com的电子邮件看起来如下:
Dear Robert Foster,
Welcome to the site. Your password is:
<this-is-the-password>
Regards,
The Team
创建/path/to/codeigniter/application/views/users/forgot_password.php文件,并将其中的以下代码添加到该文件中:
<?php if (isset($login_fail)) : ?>
<div class="alert alert-danger"><?php echo $this->lang->line('admin_login_error') ; ?></div>
<?php endif ; ?>
<?php echo validation_errors(); ?>
<?php echo form_open('password/forgot_password', 'class="form-signin" role="form"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('forgot_pwd_header') ; ?></h2>
<p class="lead"><?php echo $this->lang->line('forgot_pwd_instruction') ;?></p>
<?php echo form_input(array('name' => 'usr_email', 'class' => 'form-control', 'placeholder' => $this->lang->line('admin_login_email'),'id' => 'email', 'value' => set_value('email', ''), 'maxlength' => '100', 'size' => '50', 'style' => 'width:100%')); ?>
<br />
<button class="btn btn-lg btn-primary btn-block" type="submit"><?php echo $this->lang->line('common_form_elements_go') ; ?></button>
<br />
<?php echo form_close() ; ?>
</div>
forgot_password.php视图文件为任何用户提供一个简短的表单,以开始重置密码的过程。用户可以输入他们的电子邮件地址并按下“Go”按钮。表单提交到password控制器的forgot_password()函数,在那里进行验证。
如果电子邮件地址通过验证,则forgot_password()函数检查电子邮件地址是否存在于users表中。如果存在,则生成一个唯一的代码并存储在users.usr_pwd_change_code中。如果代码不存在,则用户将被重定向到forgot_password()函数再次尝试。
此代码也被附加到 URL 中,并在电子邮件正文中发送给用户。用户被指示点击电子邮件中的链接,该链接将引导他们到password控制器的new_password()函数。new_password()函数将加载users/new_password.php视图文件,该文件将要求用户输入他们的电子邮件地址。
此电子邮件地址经过验证,new_password()将检查users表中是否存在该电子邮件地址。如果存在,它将检查 URL 中的代码值是否与存储在users.usr_pwd_change_code中的值匹配。如果匹配,则很可能是真实的,并将生成一个新的密码。此密码将通过电子邮件发送给用户。使用密码创建一个哈希值并存储在users.usr_hash中。
创建/path/to/codeigniter/application/views/users/signin.php文件,并将以下代码添加到其中:
<?php if (isset($login_fail)) : ?>
<div class="alert alert-danger"><?php echo $this->lang->line('admin_login_error') ; ?></div>
<?php endif ; ?>
<?php echo validation_errors(); ?>
<?php echo form_open('signin/index', 'class="form-signin" role="form"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('admin_login_header') ; ?></h2>
<input type="email" name="usr_email" class="form-control" placeholder="<?php echo $this->lang->line('admin_login_email') ; ?>" required autofocus>
<input type="password" name="usr_password" class="form-control" placeholder="<?php echo $this->lang->line('admin_login_password') ; ?>" required>
<button class="btn btn-lg btn-primary btn-block" type="submit"><?php echo $this->lang->line('admin_login_signin') ; ?></button>
<br />
<?php echo anchor('password',$this->lang->line('signin_forgot_password')); ?>
<?php echo form_close() ; ?>
</div>
signin视图相当简单:一个标准的登录界面。用户可以输入他们的电子邮件地址和密码进行登录。如果有任何错误,验证错误会显示在表单上方,并且一个忘记密码链接允许用户使用一种方法开始重置密码的过程。
错误消息包含在一个具有alert alert-dangerBootstrap 类的div元素中;我更喜欢一个大的红色错误消息而不是那些软绵绵的橙色东西;然而,你可以将其更改为更柔和的样式,例如alert alert-warning。
创建/path/to/codeigniter/application/views/users/view_all_users.php文件,并将以下代码添加到其中:
<h2><?php echo $page_heading ; ?></h2>
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<td>Actions</td>
</tr>
</thead>
<tbody>
<?php if ($query->num_rows() > 0) : ?>
<?php foreach ($query->result() as $row) : ?>
<tr>
<td><?php echo $row->usr_id ; ?></td>
<td><?php echo $row->usr_fname ; ?></td>
<td><?php echo $row->usr_lname ; ?></td>
<td><?php echo $row->usr_email ; ?></td>
<td><?php echo anchor('users/edit_user/'.
$row->usr_id,$this->lang->line('common_form_elements_action_edit')) . ' ' . anchor('users/delete_user/'.
$row->usr_id,$this->lang->line('common_form_elements_action_delete')) ; ?>
</td>
</tr>
<?php endforeach ; ?>
<?php else : ?>
<tr>
<td colspan="5" class="info">No users here!</td>
</tr>
<?php endif; ?>
</tbody>
</table>
view_all_users.php视图文件在任何时候都会以表格形式显示系统中的所有用户。只有管理员用户能够看到这个列表。
表格提供了编辑和删除选项,允许用户编辑一个用户(加载users控制器的edit_user()函数)和删除一个用户(加载users控制器的delete_user()函数)。
创建/path/to/codeigniter/application/views/users/new_user.php文件,并将以下代码添加到其中:
<?php echo validation_errors() ; ?>
<div class="page-header">
<h1><?php echo $page_heading ; ?></h1>
</div>
<p class="lead"><?php echo $this->lang->line('usr_form_instruction_edit');?></p>
<div class="span8">
<?php echo form_open('users/new_user','role="form" class="form"') ; ?>
<div class="form-group">
<?php echo form_error('usr_fname'); ?>
<label for="usr_fname"><?php echo $this->lang->line('usr_fname');?></label>
<?php echo form_input($usr_fname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_lname'); ?>
<label for="usr_lname"><?php echo $this->lang->line('usr_lname');?></label>
<?php echo form_input($usr_lname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_uname'); ?>
<label for="usr_uname"><?php echo $this->lang->line('usr_uname');?></label>
<?php echo form_input($usr_uname); ?>
</div>
<div class="form-group">
<label for="usr_email"><?php echo $this->lang->line('usr_email');?></label>
<?php echo form_input($usr_email); ?>
</div>
<div class="form-group">
<label for="usr_confirm_email"><?php echo $this->lang->line('usr_confirm_email');?></label>
<?php echo form_input($usr_confirm_email); ?>
</div>
<div class="form-group">
<label for="usr_add1"><?php echo $this->lang->line('usr_add1');?></label>
<?php echo form_input($usr_add1); ?>
</div>
<div class="form-group">
<label for="usr_add2"><?php echo $this->lang->line('usr_add2');?></label>
<?php echo form_input($usr_add2); ?>
</div>
<div class="form-group">
<label for="usr_add3"><?php echo $this->lang->line('usr_add3');?></label>
<?php echo form_input($usr_add3); ?>
</div>
<div class="form-group">
<label for="usr_town_city"><?php echo $this->lang->line('usr_town_city');?></label>
<?php echo form_input($usr_town_city); ?>
</div>
<div class="form-group">
<label for="usr_zip_pcode"><?php echo $this->lang->line('usr_zip_pcode');?></label>
<?php echo form_input($usr_zip_pcode); ?>
</div>
<div class="form-group">
<label for="usr_access_level"><?php echo $this->lang->line('usr_access_level');?></label>
<?php echo form_dropdown('usr_access_level', $usr_access_level, 'large'); ?>
</div>
<div class="form-group">
<label for="usr_is_active"><?php echo $this->lang->line('usr_is_active');?></label>
<input type="radio" name="usr_is_active" value="<?php echo set_value('usr_is_active') ; ?>" /> Active
<input type="radio" name="usr_is_active" value="<?php echo set_value('usr_is_active') ; ?>" /> Inactive
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button> or <? echo anchor('users',$this->lang->line('common_form_elements_cancel'));?>
</div>
<?php echo form_close() ; ?>
</div>
</div>
new_user.php视图文件向管理员用户显示一个表单,允许他们在系统中创建用户。表单提交到users控制器的new_user()函数。验证错误会在表单上方显示。在成功提交(没有验证错误)的情况下,new_user()函数将为用户创建一个密码和一个基于密码的哈希值。密码将通过电子邮件发送给用户。此电子邮件的文本位于/views/email_scripts/welcome.txt文件中。
创建/path/to/codeigniter/application/views/users/edit_user.php文件,并将以下代码添加到其中:
<div class="page-header">
<h1><?php echo $page_heading ; ?></h1>
</div>
<p class="lead"><?php echo $this->lang->line('usr_form_instruction_edit');?></p>
<div class="span8">
<?php echo form_open('users/edit_user','role="form" class="form"') ; ?>
<div class="form-group">
<?php echo form_error('usr_fname'); ?>
<label for="usr_fname"><?php echo $this->lang->line('usr_fname');?></label>
<?php echo form_input($usr_fname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_lname'); ?>
<label for="usr_lname"><?php echo $this->lang->line('usr_lname');?></label>
<?php echo form_input($usr_lname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_uname'); ?>
<label for="usr_uname"><?php echo $this->lang->line('usr_uname');?></label>
<?php echo form_input($usr_uname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_email'); ?>
<label for="usr_email"><?php echo $this->lang->line('usr_email');?></label>
<?php echo form_input($usr_email); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_confirm_email'); ?>
<label for="usr_confirm_email"><?php echo $this->lang->line('usr_confirm_email');?></label>
<?php echo form_input($usr_confirm_email); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_add1'); ?>
<label for="usr_add1"><?php echo $this->lang->line('usr_add1');?></label>
<?php echo form_input($usr_add1); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_add2'); ?>
<label for="usr_add2"><?php echo $this->lang->line('usr_add2');?></label>
<?php echo form_input($usr_add2); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_add3'); ?>
<label for="usr_add3"><?php echo $this->lang->line('usr_add3');?></label>
<?php echo form_input($usr_add3); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_town_city'); ?>
<label for="usr_town_city"><?php echo $this->lang->line('usr_town_city');?></label>
<?php echo form_input($usr_town_city); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_zip_pcode'); ?>
<label for="usr_zip_pcode"><?php echo $this->lang->line('usr_zip_pcode');?></label>
<?php echo form_input($usr_zip_pcode); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_access_level'); ?>
<label id="usr_access_level" for="usr_access_level"><?php echo $this->lang->line('usr_access_level');?></label>
<?php echo form_dropdown('usr_access_level', $usr_access_level_options, $usr_access_level); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_is_active'); ?>
<label for="usr_is_active"><?php echo $this->lang->line('usr_is_active');?></label>
<input type="radio" name="usr_is_active" <?php if ($usr_is_active == 1) { echo 'checked' ;} ?> /> Active
<input type="radio" name="usr_is_active" <?php if ($usr_is_active == 0) { echo 'checked' ;} ?> /> Inactive
</div>
<?php echo form_hidden($id); ?>
<div class="form-group">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button> or <? echo anchor('users',$this->lang->line('common_form_elements_cancel'));?>
</div>
<?php echo form_close() ; ?>
</div>
<?php echo anchor('users/pwd_email/'.$id['usr_id'],'Send Password Reset Email') ; ?>
</div>
edit_user.php视图文件向管理员用户显示一个表单,允许他们编辑系统中的用户。当管理员用户从views/users/list_all_users.php视图文件中点击编辑时,会访问此表单。表单提交到users控制器的edit_user()函数。验证错误会在表单上方显示。
创建/path/to/codeigniter/application/views/users/me.php文件,并将以下代码添加到其中:
<?php echo validation_errors() ; ?>
<div class="page-header">
<h1><?php echo $page_heading ; ?></h1>
</div>
<p class="lead"><?php echo $this->lang->line('usr_form_instruction');?></p>
<div class="span8">
<?php echo form_open('me/index','role="form"') ; ?>
<div class="form-group">
<?php echo form_error('usr_fname'); ?>
<label for="usr_fname"><?php echo $this->lang->line('usr_fname');?></label>
<?php echo form_input($usr_fname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_lname'); ?>
<label for="usr_lname"><?php echo $this->lang->line('usr_lname');?></label>
<?php echo form_input($usr_lname); ?>
</div>
<div class="form-group">
<?php echo form_error('usr_uname'); ?>
<label for="usr_uname"><?php echo $this->lang->line('usr_uname');?></label>
<?php echo form_input($usr_uname); ?>
</div>
<div class="form-group">
<label for="usr_email"><?php echo $this->lang->line('usr_email');?></label>
<?php echo form_input($usr_email); ?>
</div>
<div class="form-group">
<label for="usr_confirm_email"><?php echo $this->lang->line('usr_confirm_email');?></label>
<?php echo form_input($usr_confirm_email); ?>
</div>
<div class="form-group">
<label for="usr_add1"><?php echo $this->lang->line('usr_add1');?></label>
<?php echo form_input($usr_add1); ?>
</div>
<div class="form-group">
<label for="usr_add2"><?php echo $this->lang->line('usr_add2');?></label>
<?php echo form_input($usr_add2); ?>
</div>
<div class="form-group">
<label for="usr_add3"><?php echo $this->lang->line('usr_add3');?></label>
<?php echo form_input($usr_add3); ?>
</div>
<div class="form-group">
<label for="usr_town_city"><?php echo $this->lang->line('usr_town_city');?></label>
<?php echo form_input($usr_town_city); ?>
</div>
<div class="form-group">
<label for="usr_zip_pcode"><?php echo $this->lang->line('usr_zip_pcode');?></label>
<?php echo form_input($usr_zip_pcode); ?>
</div>
<?php echo form_hidden($id); ?>
<div class="form-group">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button> or <? echo anchor('users',$this->lang->line('common_form_elements_cancel'));?>
</div>
<?php echo form_close() ; ?>
</div>
<?php echo anchor('me/pwd_email/'.$id,'Reset Email') ; ?>
同样,像new_user和edit_user视图中的表单一样,这个表单也是相似的;然而,它包括一个重置电子邮件链接,该链接将运行me控制器的pwd_email()函数来创建一个新的密码并将其发送给当前用户。密码不会存储在数据库中;只存储一个哈希值(users.usr_hash)。
创建/path/to/codeigniter/application/views/users/register.php文件,并将以下代码添加到其中:
<div class="container">
<?php echo validation_errors(); ?>
<?php echo form_open('register/index', 'role="form" class="form-signin"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('register_page_title'); ?></h2>
<input type="text" class="form-control" name="usr_fname" placeholder="<?php echo $this->lang->line('register_first_name'); ?>" required autofocus>
<input type="text" class="form-control" name="usr_lname" placeholder="<?php echo $this->lang->line('register_last_name'); ?>" required>
<input type="email" class="form-control" name="usr_email" placeholder="<?php echo $this->lang->line('register_email'); ?>" required>
<?php echo form_submit('submit', 'Register', 'class="btn btn-lg btn-primary btn-block"'); ?>
</form>
</div>
register.php视图文件向希望成为系统用户的个人显示一个表单。用户被邀请输入他们的名字、姓氏以及他们的电子邮件地址。然后他们点击注册按钮。
表单提交到register控制器的index()函数。index()函数将执行验证,并将任何错误显示在表单上方。
假设没有错误并且表单提交没有问题,index()函数将尝试将它们写入users表。生成一个密码并以电子邮件的形式发送给用户。电子邮件的内容存储在views/email_scripts/welcome.txt视图文件中。
创建/path/to/codeigniter/application/views/users/signin.php文件,并将以下代码添加到其中:
<?php if (isset($login_fail)) : ?>
<div class="alert alert-danger"><?php echo $this->lang->line('admin_login_error') ; ?></div>
<?php endif ; ?>
<?php echo validation_errors(); ?>
<?php echo form_open('signin/index', 'class="form-signin" role="form"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('admin_login_header') ; ?></h2>
<input type="email" name="usr_email" class="form-control" placeholder="<?php echo $this->lang->line('admin_login_email') ; ?>" required autofocus>
<input type="password" name="usr_password" class="form-control" placeholder="<?php echo $this->lang->line('admin_login_password') ; ?>" required>
<button class="btn btn-lg btn-primary btn-block" type="submit"><?php echo $this->lang->line('admin_login_signin') ; ?></button>
<br />
<?php echo anchor('password',$this->lang->line('signin_forgot_password')); ?>
<?php echo form_close() ; ?>
</div>
signin.php视图文件向用户显示一个表单。用户被邀请输入他们的电子邮件地址和密码。表单提交到signin控制器的index()函数,该函数将验证输入,并在没有错误的情况下尝试处理登录请求。
只有活跃用户才能登录(users.usr_is_active = 1)并且管理员用户(users.usr_accss_level = 1)将看到只有管理员才能使用的选项。普通用户(users.usr_access_level = 2)将被重定向到me控制器。
提示
当然,您可以将此行为适应到任何其他控制器。如何在确保正确访问部分讨论了如何进行此操作。
创建/path/to/codeigniter/application/views/users/change_password.php文件,并将以下代码添加到其中:
<?php if (isset($login_fail)) : ?>
<div class="alert alert-danger"><?php echo $this->lang->line('admin_login_error') ; ?></div>
<?php endif ; ?>
<?php echo validation_errors(); ?>
<?php echo form_open('me/change_password', 'class="form-signin" role="form"') ; ?>
<h2 class="form-signin-heading"><?php echo $this->lang->line('forgot_pwd_header') ; ?></h2>
<p class="lead"><?php echo $this->lang->line('forgot_pwd_instruction') ;?></p>
<table border="0">
<tr>
<td><?php $this->lang->line('signin_new_pwd_email') ; ?></td>
</tr>
<tr>
<td><?php echo form_input($usr_new_pwd_1); ?></td>
</tr>
<tr>
<td><?php echo form_input($usr_new_pwd_2); ?></td>
</tr>
</table>
<button class="btn btn-lg btn-primary btn-block" type="submit"><?php echo $this->lang->line('common_form_elements_go') ; ?></button>
<br />
<?php echo form_close() ; ?>
</div>
这个视图文件向用户显示一个 HTML 表单,允许他们为他们的账户输入两个新密码。表单提交到me控制器的change_password()函数,该函数验证提供的两个密码并检查它们是否匹配,除了其他各种验证检查。如果验证通过,则从提供的密码创建一个哈希值,并将该哈希值保存到用户的数据库记录中。
创建控制器
在这个项目中,有六个控制器,具体如下:
-
/core/MY_Controller.php:这是包含公共资源的父控制器类。 -
/controllers/password.php:这个文件包含允许用户请求新密码的功能。 -
/controllers/me.php:这为普通用户(即非管理员)提供了一个更改账户设置的位置:姓名、电子邮件等。 -
/controllers/register.php:此文件包含允许新用户注册并将他们的详细信息记录在users表中的功能。 -
/controllers/signin.php:此文件为用户提供登录账户并开始会话的方法。 -
/controllers/users.php:此文件为管理员提供管理已注册用户及其在users表中的记录的功能。
这些是我们六个控制器(一个用于扩展和五个用于扩展);让我们逐一过目并创建它们。
创建 /path/to/codeigniter/application/core/MY_Controller.php 控制器文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class MY_Controller extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->helper('form');
$this->load->helper('url');
$this->load->helper('security');
$this->load->helper('language');
$this->load->library('session');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-warning" role="alert">', '</div>');
$this->lang->load('en_admin', 'english');
}
}
core/MY_Controller.php 控制器作为所有需要在访问之前要求用户登录的控制器的主父控制器。
创建 /path/to/codeigniter/application/controllers/password.php 控制器文件,并将以下代码添加到其中。由于此控制器不需要被登录用户访问,我们不是通过 MY_Controller 扩展它,而是仅使用默认的 CI_Controller:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Password extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->library('session');
$this->load->helper('form');
$this->load->helper('file');
$this->load->helper('url');
$this->load->helper('security');
$this->load->model('Users_model');
$this->lang->load('en_admin', 'english');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="bs-callout bs-callout-error">', '</div>');
}
public function index() {
redirect('password/forgot_password');
}
重置密码页面为用户提供一个表单,允许他们输入他们的电子邮件地址。一旦用户提交了表单,就会生成一个代码并附加到 URL 链接的开头。此链接以电子邮件的形式发送到提供的电子邮件地址。URL 中的唯一代码由 password 控制器的下一个函数 new_password() 使用,但稍后我们会详细介绍。
首先,我们在 users/forgot_password.php 视图文件中定义表单的验证规则,如下所示:
public function forgot_password() {
$this->form_validation->set_rules('usr_email', $this->lang->line('signin_new_pwd_email'), 'required|min_length[5]|max_length[125]|valid_email');
如果表单是首次查看或已失败之前的验证规则,则 $this->form_validation() CodeIgniter 函数返回 FALSE,加载 users/forgot_password.php 视图文件:
if ($this->form_validation->run() == FALSE) {
$this->load->view('common/login_header');
$this->load->view('users/forgot_password');
$this->load->view('common/footer');
如果用户的电子邮件通过验证,那么我们将尝试生成一个唯一的代码并发送电子邮件给他们:
} else {
$email = $this->input->post('usr_email');
$num_res = $this->Users_model->count_results($email);
首先,我们查看在表单中提供的电子邮件地址是否实际存在于数据库中。如果不是,则 $num_res 将不等于 1。如果是这种情况,则将用户重定向到 forgot_password() 函数。然而,如果它存在,则我们使用 if 语句继续处理请求:
if ($num_res == 1) {
我们调用 Users_model 的 make_code() 函数,该函数将为我们生成一个唯一的代码,并将其作为 $code 变量返回。此 $code 变量被添加到 $data 数组中,并发送到 Users_model 的 update_user_code() 函数,该函数将刚刚生成的唯一代码写入 users.usr_pwd_change_code,为这里显示的 new_password() 函数做准备(new_password() 在用户点击我们即将发送给他们的电子邮件中的 URL 时运行):
$code = $this->Users_model->make_code();
$data = array(
'usr_pwd_change_code' => $code,
'usr_email' => $email
);
if ($this->Users_model->update_user_code($data)) { // Update okay, so send email
$result = $this->Users_model->get_user_details_by_email($email);
foreach ($result->result() as $row) {
$usr_fname = $row->usr_fname;
$usr_lname = $row->usr_lname;
}
好的,代码已经创建并保存到数据库中正确的账户,我们现在可以开始发送电子邮件。让我们定义电子邮件中将包含的链接。在这个例子中,它是http://www.domain.com/password/new_password/UNIQUE-CODE-HERE;然而,您需要将其更改为反映您服务器上的路径和域名:
$link = "http://www.domain.com/password/new_password/".$code;
现在我们需要加载reset_password.txt文件。此文件包含我们将发送的电子邮件正文的模板文本。同样,您需要将此文件的文件路径更改为系统上的路径。我们将文件名传递给read_file() CodeIgniter 函数,该函数将打开文件并返回其内容。该文件的内容,即文件中的文本,存储在$file变量中作为一个字符串:
$path = '/path/to/codeigniter/application/views/email_scripts/reset_password.txt';
$file = read_file($path);
使用str_replace() PHP 函数,我们将用正确的值替换$file变量中的变量:
$file = str_replace('%usr_fname%', $usr_fname, $file);
$file = str_replace('%usr_lname%', $usr_lname, $file);
echo $file = str_replace('%link%', $link, $file);
现在,我们已经准备好向用户发送电子邮件。我们使用 PHP 的mail()函数来发送电子邮件。如果电子邮件已发送,则将用户重定向到登录页面。如果没有,则只是重新加载函数:
if (mail ($email, $this->lang->line('email_subject_reset_password'),$file, 'From: me@domain.com')) {
redirect('signin');
}
} else {
// Some sort of error happened, redirect user back to form
redirect('password/forgot_password');
}
} else { // Some sort of error happened, redirect user back to form
redirect('password/forgot_password');
}
}
}
当用户点击在执行上一个函数forgot_password()期间发送给他们的电子邮件中的 URL 时,会访问new_password()函数。它向用户显示一个表单,允许他们输入新密码。
首先,我们在users/new_password.php视图文件中定义表单的验证规则:
public function new_password() {
$this->form_validation->set_rules('code', $this->lang->line('signin_new_pwd_code'), 'required|min_length[4]|max_length[8]');
$this->form_validation->set_rules('usr_email', $this->lang->line('signin_new_pwd_email'), 'required|min_length[5]|max_length[125]');
$this->form_validation->set_rules('usr_password1', $this->lang->line('signin_new_pwd_email'), 'required|min_length[5]|max_length[125]');
$this->form_validation->set_rules('usr_password2', $this->lang->line('signin_new_pwd_email'), 'required|min_length[5]|max_length[125]|matches[usr_password1]');
if ($this->input->post()) {
$data['code'] = xss_clean($this->input->post('code'));
} else {
$data['code'] = xss_clean($this->uri->segment(3));
}
如果表单是第一次查看或未通过前面的验证规则,则$this->form_validation() CodeIgniter 函数返回FALSE,加载users/new_password.php视图文件。该视图文件包含三个表单元素:一个用于用户的电子邮件地址和两个用于他们的新密码:
if ($this->form_validation->run() == FALSE) {
$data['usr_email'] = array('name' => 'usr_email', 'class' => 'form-control', 'id' => 'usr_email', 'type' => 'text', 'value' => set_value('usr_email', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signin_new_pwd_email'));
$data['usr_password1'] = array('name' => 'usr_password1', 'class' => 'form-control', 'id' => 'usr_password1', 'type' => 'password', 'value' => set_value('usr_password1', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signin_new_pwd_pwd'));
$data['usr_password2'] = array('name' => 'usr_password2', 'class' => 'form-control', 'id' => 'usr_password2', 'type' => 'password', 'value' => set_value('usr_password2', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signin_new_pwd_confirm'));
$this->load->view('common/login_header', $data);
$this->load->view('users/new_password', $data);
$this->load->view('common/footer', $data);
} else {
如果表单通过了验证,那么我们将尝试将 URL 中的代码与使用电子邮件地址作为搜索词的账户进行匹配:
// Does code from input match the code against the email
$email = xss_clean($this->input->post('usr_email'));
如果Users_model的does_code_match()函数返回一个假值,那么数据库中没有记录包含与表单中提供的电子邮件地址和 URL 中的代码匹配的电子邮件地址和代码。如果是这种情况,我们将他们重定向到forgot_password()函数以重新开始这个过程。然而,如果它匹配,那么这显然是一个真实的请求:
if (!$this->Users_model->does_code_match($data, $email)) { // Code doesn't match
redirect ('users/forgot_password');
} else { // Code does match
由于这很可能是真实的请求,并且电子邮件和唯一代码已匹配,让我们从提供的密码中创建一个哈希值:
$hash = $this->encrypt->sha1($this->input->post('usr_password1'));
我们可以将这个哈希存储在$data数组中,同时包含提供的电子邮件:
$data = array(
'usr_hash' => $hash,
'usr_email' => $email
);
现在,让我们将这封电子邮件和哈希传递给Users_model的update_user_password()函数:
if ($this->Users_model->update_user_password($data)) {
现在用户已经更新了他们的密码,让我们发送一封电子邮件来确认这一点:
$link = 'http://www.domain.com/signin';
$result = $this->Users_model->get_user_details_by_email($email);
foreach ($result->result() as $row) {
$usr_fname = $row->usr_fname;
$usr_lname = $row->usr_lname;
}
我们需要加载new_password.txt文件。此文件包含我们将发送的电子邮件正文的模板文本。同样,您需要将此文件的文件路径更改为您系统上的路径。我们将文件名传递给read_file() CodeIgniter 函数,该函数将打开文件并返回其内容。该文件的内容,即文件中的文本,被存储在$file变量中作为一个字符串:
$path = '/ path/to/codeigniter/application/views/email_scripts/new_password.txt';
$file = read_file($path);
使用str_replace() PHP 函数,我们将$file变量中的变量替换为正确的值。一旦发送了此电子邮件,我们将他们重定向到signin控制器,在那里他们可以使用新密码登录:
$file = str_replace('%usr_fname%', $usr_fname, $file);
$file = str_replace('%usr_lname%', $usr_lname, $file);
$file = str_replace('%password%', $password, $file);
$file = str_replace('%link%', $link, $file);
if (mail ($email, $this->lang->line('email_subject_new_password'),$file, 'From: me@domain.com') ) {
redirect ('signin');
}
}
}
}
}
}
创建/path/to/codeigniter/application/controllers/me.php控制器文件,并向其中添加以下代码:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Me extends CI_Controller {
me控制器应由非管理员用户使用——换句话说,users.usr_access_level的值设置为2或以上的用户。
此项目允许用户更改他们的详细信息、姓名、电子邮件地址等。然而,您可以调整me控制器以显示任意数量的内容。或者,您可以在另一个控制器的__construct()函数中使用以下代码提供特定级别用户的函数:
if ( ($this->session->userdata('logged_in') == FALSE) ||
(!$this->session->userdata('usr_access_level') >= 2) ) {
redirect('signin');
}
我们将在本章的“整合一切”部分更详细地介绍这一点;然而,我们还是在这里简要提及。前面的代码检查用户是否已登录,然后检查用户的访问级别(users.usr_access_level)。
如果users.usr_access_level的值不大于或等于2(这是普通用户的级别),那么它将重定向他们到signin或signout——换句话说,它会将他们注销并终止会话。
通过调整比较的值(例如1、2、3等),您可以确保具有特定值的用户只能访问此控制器:
function __construct() {
parent::__construct();
$this->load->helper('form');
$this->load->helper('url');
$this->load->helper('security');
$this->load->helper('file'); // for html emails
$this->load->helper('language');
$this->load->model('Users_model');
$this->load->library('session');
// Load language file
$this->lang->load('en_admin', 'english');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-warning" role="alert">', '</div>');
if ( ($this->session->userdata('logged_in') == FALSE) ||
(!$this->session->userdata('usr_access_level') >= 2) ) {
redirect('signin/signout');
}
}
index()函数允许普通用户在数据库中更新他们的详细信息。首先,我们为表单设置验证规则:
public function index() {
// Set validation rules
$this->form_validation->set_rules('usr_fname', $this->lang->line('usr_fname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_lname', $this->lang->line('usr_lname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_uname', $this->lang->line('usr_uname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_email', $this->lang->line('usr_email'), 'required|min_length[1]|max_length[255]|valid_email');
$this->form_validation->set_rules('usr_confirm_email', $this->lang->line('usr_confirm_email'), 'required|min_length[1]|max_length[255]|valid_email|matches[usr_email]');
$this->form_validation->set_rules('usr_add1', $this->lang->line('usr_add1'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_add2', $this->lang->line('usr_add2'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_add3', $this->lang->line('usr_add3'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_town_city', $this->lang->line('usr_town_city'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_zip_pcode', $this->lang->line('usr_zip_pcode'), 'required|min_length[1]|max_length[125]');
$data['id'] = $this->session->userdata('usr_id');
$data['page_heading'] = 'Edit my details';
// Begin validation
如果表单是第一次查看或已失败之前的验证规则,那么$this->form_validation() CodeIgniter 函数返回FALSE,加载users/me.php视图文件:
if ($this->form_validation->run() == FALSE) { // First load, or problem with form
在这里,我们定义了要在users/me.php视图文件中显示的 HTML 表单项的设置。由于我们正在编辑一个已经登录的用户,我们需要从数据库中获取他们的详细信息以便预先填充表单元素。
我们调用Users_model的get_user_details()函数,并将从会话中获取的用户 ID 传递给它:
$query = $this->Users_model->get_user_details($data['id']);
foreach ($query->result() as $row) {
$usr_fname = $row->usr_fname;
$usr_lname = $row->usr_lname;
$usr_uname = $row->usr_uname;
$usr_email = $row->usr_email;
$usr_add1 = $row->usr_add1;
$usr_add2 = $row->usr_add2;
$usr_add3 = $row->usr_add3;
$usr_town_city = $row->usr_town_city;
$usr_zip_pcode = $row->usr_zip_pcode;
}
一旦我们获取了用户详细信息并将它们保存到局部变量中,我们就将它们应用到表单项上。为此,我们使用 CodeIgniter 的set_value()函数,第一个参数是表单项的名称(例如,<input type="text" name="this-is-the-name" />)和第二个参数是表单项的实际值:
$data['usr_fname'] = array('name' => 'usr_fname', 'class' => 'form-control', 'id' => 'usr_fname', 'value' => set_value('usr_fname', $usr_fname), 'maxlength' => '100', 'size' => '35');
$data['usr_lname'] = array('name' => 'usr_lname', 'class' => 'form-control', 'id' => 'usr_lname', 'value' => set_value('usr_lname', $usr_lname), 'maxlength' => '100', 'size' => '35');
$data['usr_uname'] = array('name' => 'usr_uname', 'class' => 'form-control', 'id' => 'usr_uname', 'value' => set_value('usr_uname', $usr_uname), 'maxlength' => '100', 'size' => '35');
$data['usr_email'] = array('name' => 'usr_email', 'class' => 'form-control', 'id' => 'usr_email', 'value' => set_value('usr_email', $usr_email), 'maxlength' => '100', 'size' => '35');
$data['usr_confirm_email'] = array('name' => 'usr_confirm_email', 'class' => 'form-control', 'id' => 'usr_confirm_email', 'value' => set_value('usr_confirm_email', $usr_email), 'maxlength' => '100', 'size' => '35');
$data['usr_add1'] = array('name' => 'usr_add1', 'class' => 'form-control', 'id' => 'usr_add1', 'value' => set_value('usr_add1', $usr_add1), 'maxlength' => '100', 'size' => '35');
$data['usr_add2'] = array('name' => 'usr_add2', 'class' => 'form-control', 'id' => 'usr_add2', 'value' => set_value('usr_add2', $usr_add2), 'maxlength' => '100', 'size' => '35');
$data['usr_add3'] = array('name' => 'usr_add3', 'class' => 'form-control', 'id' => 'usr_add3', 'value' => set_value('usr_add3', $usr_add3), 'maxlength' => '100', 'size' => '35');
$data['usr_town_city'] = array('name' => 'usr_town_city', 'class' => 'form-control', 'id' => 'usr_town_city', 'value' => set_value('usr_town_city', $usr_town_city), 'maxlength' => '100', 'size' => '35');
$data['usr_zip_pcode'] = array('name' => 'usr_zip_pcode', 'class' => 'form-control', 'id' => 'usr_zip_pcode', 'value' => set_value('usr_zip_pcode', $usr_zip_pcode), 'maxlength' => '100', 'size' => '35');
$this->load->view('common/header', $data);
$this->load->view('nav/top_nav', $data);
$this->load->view('users/me', $data);
$this->load->view('common/footer', $data);
} else { // Validation passed, now escape the data
现在验证已经通过,我们将发布的数据保存到$data数组中,以便将其保存到Users_model的process_update_user()函数中:
$data = array(
'usr_fname' => $this->input->post('usr_fname'),
'usr_lname' => $this->input->post('usr_lname'),
'usr_uname' => $this->input->post('usr_uname'),
'usr_email' => $this->input->post('usr_email'),
'usr_add1' => $this->input->post('usr_add1'),
'usr_add2' => $this->input->post('usr_add2'),
'usr_add3' => $this->input->post('usr_add3'),
'usr_town_city' => $this->input->post('usr_town_city'),
'usr_zip_pcode' => $this->input->post('usr_zip_pcode')
);
if ($this->Users_model->process_update_user($id, $data)) {
redirect('users');
}
}
}
me控制器还包含change_password()函数。这允许访问控制器的用户更改他们的密码。一旦访问,/views/users/change_password.php视图文件将显示一个简单的表单,要求输入新密码。一旦表单提交并成功验证,将使用提供的新密码创建一个哈希值,并将其保存到已登录用户的记录中:
public function change_password() {
$this->load->library('form_validation');
$this->form_validation->set_rules('usr_new_pwd_1', $this->lang->line('signin_new_pwd_pwd'), 'required|min_length[5]|max_length[125]');
$this->form_validation->set_rules('usr_new_pwd_2', $this->lang->line('signin_new_pwd_confirm'), 'required|min_length[5]|max_length[125]|matches[usr_new_pwd_1]');
if ($this->form_validation->run() == FALSE) {
$data['usr_new_pwd_1'] = array('name' => 'usr_new_pwd_1', 'class' => 'form-control', 'type' => 'password', 'id' => 'usr_new_pwd_1', 'value' => set_value('usr_new_pwd_1', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signin_new_pwd_pwd'));
$data['usr_new_pwd_2'] = array('name' => 'usr_new_pwd_2', 'class' => 'form-control', 'type' => 'password', 'id' => 'usr_new_pwd_2', 'value' => set_value('usr_new_pwd_2', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('signin_new_pwd_confirm'));
$data['submit_path'] = 'me/change_password';
$this->load->view('common/login_header', $data);
$this->load->view('users/change_password', $data);
$this->load->view('common/footer', $data);
} else {
$hash = $this->encrypt->sha1($this->input->post('usr_new_pwd_1'));
$data = array(
'usr_hash' => $hash,
'usr_id' => $this->session->userdata('usr_id')
);
if ($this->Users_model->update_user_password($data)) {
redirect('signin/signout');
}
}
}
}
创建/path/to/codeigniter/application/controllers/register.php控制器文件,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Register extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->helper('form');
$this->load->helper('url');
$this->load->helper('security');
$this->load->model('Register_model');
$this->load->library('encrypt');
$this->lang->load('en_admin', 'english');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-warning" role="alert">', '</div>');
}
index()函数向新用户显示一个小表单。此表单允许他们输入基本信息,如电子邮件地址和姓名。一旦用户按下注册按钮并且表单成功验证,用户将收到一封欢迎邮件并被添加到数据库中。
首先,我们在views/users/register.php中设置表单的验证规则:
public function index() {
// Set validation rules
$this->form_validation->set_rules('usr_fname', $this->lang->line('first_name'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_lname', $this->lang->line('last_name'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_email', $this->lang->line('email'), 'required|min_length[1]|max_length[255]|valid_email|is_unique[users.usr_email]');
// Begin validation
if ($this->form_validation->run() == FALSE) { // First load, or problem with form
$this->load->view('common/login_header');
$this->load->view('users/register');
$this->load->view('common/footer');
} else {
一旦表单成功验证,我们就会为他们创建一封电子邮件。这是通过使用 CodeIgniter 的random_string()函数来完成的。我们生成一个由字母数字字符组成的八位字符串。这个字符串存储在$password变量中——我们需要这个变量来创建哈希(这将存储在users.usr_hash中)并通过电子邮件发送给用户(否则他们不知道自己的密码是是什么):
// Create hash from user password
$password = random_string('alnum', 8);
在我们创建他们的密码之后,我们为其创建一个哈希值。这是通过将$password传递给$this->encrypt->sha1()来完成的:
$hash = $this->encrypt->sha1($password);
现在,我们将所有内容保存到$data数组中,为写入数据库做准备。这是通过调用Register_model的register_user()函数并传递$data数组来完成的:
$data = array(
'usr_fname' => $this->input->post('usr_fname'),
'usr_lname' => $this->input->post('usr_lname'),
'usr_email' => $this->input->post('usr_email'),
'usr_is_active' => 1,
'usr_access_level' => 2,
'usr_hash' => $hash
);
如果register_user()函数返回true,那么我们将给用户发送一封电子邮件,否则我们将他们发送回register控制器:
if ($this->Register_model->register_user($data)) {
$file = read_file('../views/email_scripts/welcome.txt');
$file = str_replace('%usr_fname%', $data['usr_fname'], $file);
$file = str_replace('%usr_lname%', $data['usr_lname'], $file);
$file = str_replace('%password%', $password, $file);
redirect('signin');
} else {
redirect('register');
}
}
}
}
创建/path/to/codeigniter/application/controllers/signin.php控制器文件,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Signin extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->library('session');
$this->load->helper('form');
$this->load->helper('url');
$this->load->helper('security');
$this->lang->load('en_admin', 'english');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-warning" role="alert">', '</div>');
}
index()函数向用户显示一个表单,允许他们输入他们的电子邮件地址和密码。它还处理登录表单的任何验证。
首先,index()函数检查用户是否已经登录——毕竟,当用户已经登录时尝试登录是没有意义的。因此,我们检查logged_in用户数据项的值。如果存在并且等于TRUE,那么他们必须已经登录。如果是这种情况,那么我们将计算出他们的用户级别,以确定他们是否是普通用户还是管理员。如果他们是管理员,他们将被重定向到管理员区域,即users控制器;如果他们不是管理员用户,他们将被重定向到me控制器:
public function index() {
if ($this->session->userdata('logged_in') == TRUE) {
if ($this->session->userdata('usr_access_level') == 1) {
redirect('users');
} else {
redirect('me');
}
} else {
如果他们到达代码的这个点,那么他们还没有登录,这意味着我们必须显示一个表单,让他们可以登录。现在,我们定义登录表单的验证规则:
// Set validation rules for view filters
$this->form_validation->set_rules('usr_email', $this->lang->line('signin_email'), 'required|valid_email|min_length[5]|max_length[125]');
$this->form_validation->set_rules('usr_password', $this->lang->line('signin_password'), 'required|min_length[5]|max_length[30]');
if ($this->form_validation->run() == FALSE) {
$this->load->view('common/login_header');
$this->load->view('users/signin');
$this->load->view('common/footer');
} else {
假设验证通过,我们将他们的电子邮件和密码存储在局部变量中,加载Signin_model,并调用does_user_exist()函数,将用户提供的电子邮件地址传递给它。如果找到的不是一条记录,则表单将重定向到signin控制器,让用户再次尝试:
$usr_email = $this->input->post('usr_email');
$password = $this->input->post('usr_password');
$this->load->model('Signin_model');
$query = $this->Signin_model->does_user_exist($usr_email);
然而,如果恰好找到一条记录,我们将尝试让他们登录:
if ($query->num_rows() == 1) { // One matching row found
foreach ($query->result() as $row) {
// Call Encrypt library
$this->load->library('encrypt');
我们从用户提供的密码生成一个哈希值,并将其与does_user_exist()调用返回的数据库结果对象中的哈希值进行比较:
// Generate hash from a their password
$hash = $this->encrypt->sha1($password);
if ($row->usr_is_active != 0) { // See if the user is active or not
// Compare the generated hash with that in the database
if ($hash != $row->usr_hash) {
如果用户到达代码的这一部分,这意味着哈希值不匹配,因此我们将显示带有错误信息的登录视图:
// Didn't match so send back to login
$data['login_fail'] = true;
$this->load->view('common/login_header');
$this->load->view('users/signin', $data);
$this->load->view('common/footer');
} else {
然而,如果用户到达这里,那么哈希值匹配,用户提供的密码必须是正确的。因此,我们将一些项目打包到$data数组中,一旦他们登录,这些项目将对他们很有用:
$data = array(
'usr_id' => $row->usr_id,
'acc_id' => $row->acc_id,
'usr_email' => $row->usr_email,
'usr_access_level' => $row->usr_access_level,
'logged_in' => TRUE
);
然后,使用$this->session->set_userdata()为他们创建一个会话:
// Save data to session
$this->session->set_userdata($data);
最后,我们确定将他们重定向到哪个控制器。如果他们是管理员用户(users.usr_access_level = 1),他们将重定向到users;如果他们是普通用户(users.usr_access_level = 2),他们将重定向到me控制器;然而,如果users.usr_access_level不是1或2,则默认也将他们重定向到me控制器:
if ($data['usr_access_level'] == 2) {
redirect('me');
} elseif ($data['usr_access_level'] == 1) {
redirect('users');
} else {
redirect('me');
}
}
} else {
// User currently inactive
redirect('signin');
}
}
}
}
}
}
事物有起有落,或者类似这样的话;无论如何。已经登录的必须注销(可怕!)——signout()是一个快速函数,用于销毁会话并将用户重定向到signin控制器。
当用户(管理员或其他用户)点击top_nav.php视图中的注销链接时,将调用signin控制器。一旦重定向,signin控制器将识别他们已经不再登录,并显示登录表单:
public function signout() {
$this->session->sess_destroy();
redirect ('signin');
}
}
创建/path/to/codeigniter/application/controllers/users.php控制器文件,并向其中添加以下代码:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Users extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('file'); // for html emails
$this->load->model('Users_model');
$this->load->model('Password_model');
if ( ($this->session->userdata('logged_in') == FALSE) ||
($this->session->userdata('usr_access_level') != 1) ) {
redirect('signin');
}
}
好吧,首先要注意的是__construct()函数。我们测试用户的访问级别(users.usr_access_level)——如果它不等于1,那么他们就不是管理员用户——因此,我们将他们重定向出控制器:
public function index() {
$data['page_heading'] = 'Viewing users';
$data['query'] = $this->Users_model->get_all_users();
$this->load->view('common/header', $data);
$this->load->view('nav/top_nav', $data);
$this->load->view('users/view_all_users', $data);
$this->load->view('common/footer', $data);
}
现在,让我们看一下前面的函数。index()函数加载Users_model的get_all_users()函数,正如其名,它获取users表中的所有用户。这个结果存储在$data数组的query项中,然后传递给views/users/view_all_users.php视图文件。这个视图文件将以表格格式显示所有用户,并提供编辑和删除的两种选项。
new_user()函数处理系统内的用户创建。最初,new_user()函数设置验证规则:
public function new_user() {
// Set validation rules
$this->form_validation->set_rules('usr_fname', $this->lang->line('usr_fname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_lname', $this->lang->line('usr_lname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_uname', $this->lang->line('usr_uname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_email', $this->lang->line('usr_email'), 'required|min_length[1]|max_length[255]|valid_email|is_unique[users.usr_email]');
$this->form_validation->set_rules('usr_confirm_email', $this->lang->line('usr_confirm_email'), 'required|min_length[1]|max_length[255]|valid_email|matches[usr_email]');
$this->form_validation->set_rules('usr_add1', $this->lang->line('usr_add1'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_add2', $this->lang->line('usr_add2'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_add3', $this->lang->line('usr_add3'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_town_city', $this->lang->line('usr_town_city'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_zip_pcode', $this->lang->line('usr_zip_pcode'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_access_level', $this->lang->line('usr_access_level'), 'min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_is_active', $this->lang->line('usr_is_active'), 'min_length[1]|max_length[1]|integer|is_natural');
$data['page_heading'] = 'New user';
// Begin validation
在我们设置验证规则(如前述代码所示)之后,我们接着测试 $this->form_validation() 的返回值。如果是第一次访问页面或任何表单项验证失败,则返回 FALSE,然后执行以下代码。在这里,我们定义了在 views/users/new_user.php 视图中显示的 HTML 表单元素的设置:
if ($this->form_validation->run() == FALSE) { // First load, or problem with form
$data['usr_fname'] = array('name' => 'usr_fname', 'class' => 'form-control', 'id' => 'usr_fname', 'value' => set_value('usr_fname', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_lname'] = array('name' => 'usr_lname', 'class' => 'form-control', 'id' => 'usr_lname', 'value' => set_value('usr_lname', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_uname'] = array('name' => 'usr_uname', 'class' => 'form-control', 'id' => 'usr_uname', 'value' => set_value('usr_uname', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_email'] = array('name' => 'usr_email', 'class' => 'form-control', 'id' => 'usr_email', 'value' => set_value('usr_email', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_confirm_email'] = array('name' => 'usr_confirm_email', 'class' => 'form-control', 'id' => 'usr_confirm_email', 'value' => set_value('usr_confirm_email', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_add1'] = array('name' => 'usr_add1', 'class' => 'form-control', 'id' => 'usr_add1', 'value' => set_value('usr_add1', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_add2'] = array('name' => 'usr_add2', 'class' => 'form-control', 'id' => 'usr_add2', 'value' => set_value('usr_add2', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_add3'] = array('name' => 'usr_add3', 'class' => 'form-control', 'id' => 'usr_add3', 'value' => set_value('usr_add3', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_town_city'] = array('name' => 'usr_town_city', 'class' => 'form-control', 'id' => 'usr_town_city', 'value' => set_value('usr_town_city', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_zip_pcode'] = array('name' => 'usr_zip_pcode', 'class' => 'form-control', 'id' => 'usr_zip_pcode', 'value' => set_value('usr_zip_pcode', ''), 'maxlength' => '100', 'size' => '35');
$data['usr_access_level'] = array(1=>1, 2=>2, 3=>3, 4=>4, 5=>5);
$this->load->view('common/header', $data);
$this->load->view('nav/top_nav', $data);
$this->load->view('users/new_user',$data);
$this->load->view('common/footer', $data);
} else { // Validation passed, now escape the data
假设表单数据通过了验证,我们开始为用户创建密码。我们使用 CodeIgniter 的 random_string() 函数生成一个 8 位长度的字母数字字符串。
然后,我们使用 $this->encrypt->sha1() CodeIgniter 函数从这个密码生成哈希值,如下述代码片段所示。稍后,在代码中,我们将密码通过电子邮件发送给用户:
$password = random_string('alnum', 8);
$hash = $this->encrypt->sha1($password);
我们将表单输入和 $hash 保存到 $data 数组中:
$data = array(
'usr_fname' => $this->input->post('usr_fname'),
'usr_lname' => $this->input->post('usr_lname'),
'usr_uname' => $this->input->post('usr_uname'),
'usr_email' => $this->input->post('usr_email'),
'usr_hash' => $hash,
'usr_add1' => $this->input->post('usr_add1'),
'usr_add2' => $this->input->post('usr_add2'),
'usr_add3' => $this->input->post('usr_add3'),
'usr_town_city' => $this->input->post('usr_town_city'),
'usr_zip_pcode' => $this->input->post('usr_zip_pcode'),
'usr_access_level' => $this->input->post('usr_access_level'),
'usr_is_active' => $this->input->post('usr_is_active')
);
一旦它存储在 $data 数组中,我们尝试使用 Users_model 的 process_create_user() 函数将哈希值保存到数据库中:
if ($this->Users_model->process_create_user($data)) {
$file = read_file('../views/email_scripts/welcome.txt');
$file = str_replace('%usr_fname%', $data['usr_fname'], $file);
$file = str_replace('%usr_lname%', $data['usr_lname'], $file);
$file = str_replace('%password%', $password, $file);
redirect('users');
} else {
}
}
}
如果管理员用户选择编辑用户的详细信息,当他们查看完整的用户列表时,可以点击用户名称旁边的编辑按钮,如前述 index() 函数中描述的那样。如果他们点击编辑,则调用 edit_user() 函数——这是一个基本函数,它使用表单验证功能来验证用户详细信息,以防表单被提交。
初始时,我们首先定义表单验证规则:
public function edit_user() {
// Set validation rules
$this->form_validation->set_rules('usr_id', $this->lang->line('usr_id'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_fname', $this->lang->line('usr_fname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_lname', $this->lang->line('usr_lname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_uname', $this->lang->line('usr_uname'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_email', $this->lang->line('usr_email'), 'required|min_length[1]|max_length[255]|valid_email');
$this->form_validation->set_rules('usr_confirm_email', $this->lang->line('usr_confirm_email'), 'required|min_length[1]|max_length[255]|valid_email|matches[usr_email]');
$this->form_validation->set_rules('usr_add1', $this->lang->line('usr_add1'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_add2', $this->lang->line('usr_add2'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_add3', $this->lang->line('usr_add3'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_town_city', $this->lang->line('usr_town_city'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_zip_pcode', $this->lang->line('usr_zip_pcode'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_access_level', $this->lang->line('usr_access_level'), 'min_length[1]|max_length[125]');
$this->form_validation->set_rules('usr_is_active', $this->lang->line('usr_is_active'), 'min_length[1]|max_length[1]|integer|is_natural');
用户的主键 (users.usr_id) 被附加到编辑链接中,并传递给 edit_user() 函数。这用于在 users 表中查找用户。Users_model 的 get_user_details($id) 函数接受一个参数——即 $id 的值(如通过编辑链接传递或使用 $_POST 提交表单时传递)——并查找用户。一旦找到,查询的详细信息将被写入局部变量并保存到 $data 数组中。然后,它被传递到 edit_user.php 视图中,用于用正确数据填充表单项:
if ($this->input->post()) {
$id = $this->input->post('usr_id');
} else {
$id = $this->uri->segment(3);
}
$data['page_heading'] = 'Edit user';
// Begin validation
在我们设置验证规则之后,我们测试 $this->form_validation() 的返回值。如果是第一次访问页面或任何表单项验证失败,则返回 FALSE,然后执行以下代码。在这里,我们定义了在 views/users/edit_user.php 视图中显示的 HTML 表单元素的设置:
if ($this->form_validation->run() == FALSE) { // First load, or problem with form
$query = $this->Users_model->get_user_details($id);
foreach ($query->result() as $row) {
$usr_id = $row->usr_id;
$usr_fname = $row->usr_fname;
$usr_lname = $row->usr_lname;
$usr_uname = $row->usr_uname;
$usr_email = $row->usr_email;
$usr_add1 = $row->usr_add1;
$usr_add2 = $row->usr_add2;
$usr_add3 = $row->usr_add3;
$usr_town_city = $row->usr_town_city;
$usr_zip_pcode = $row->usr_zip_pcode;
$usr_access_level = $row->usr_access_level;
$usr_is_active = $row->usr_is_active;
}
我们在这里构建 HTML 表单元素,并在 $data 数组中定义它们的设置,如下述代码所示:
$data['usr_fname'] = array('name' => 'usr_fname', 'class' => 'form-control', 'id' => 'usr_fname', 'value' => set_value('usr_fname', $usr_fname), 'maxlength' => '100', 'size' => '35');
$data['usr_lname'] = array('name' => 'usr_lname', 'class' => 'form-control', 'id' => 'usr_lname', 'value' => set_value('usr_lname', $usr_lname), 'maxlength' => '100', 'size' => '35');
$data['usr_uname'] = array('name' => 'usr_uname', 'class' => 'form-control', 'id' => 'usr_uname', 'value' => set_value('usr_uname', $usr_uname), 'maxlength' => '100', 'size' => '35');
$data['usr_email'] = array('name' => 'usr_email', 'class' => 'form-control', 'id' => 'usr_email', 'value' => set_value('usr_email', $usr_email), 'maxlength' => '100', 'size' => '35');
$data['usr_confirm_email'] = array('name' => 'usr_confirm_email', 'class' => 'form-control', 'id' => 'usr_confirm_email', 'value' => set_value('usr_confirm_email', $usr_email), 'maxlength' => '100', 'size' => '35');
$data['usr_add1'] = array('name' => 'usr_add1', 'class' => 'form-control', 'id' => 'usr_add1', 'value' => set_value('usr_add1', $usr_add1), 'maxlength' => '100', 'size' => '35');
$data['usr_add2'] = array('name' => 'usr_add2', 'class' => 'form-control', 'id' => 'usr_add2', 'value' => set_value('usr_add2', $usr_add2), 'maxlength' => '100', 'size' => '35');
$data['usr_add3'] = array('name' => 'usr_add3', 'class' => 'form-control', 'id' => 'usr_add3', 'value' => set_value('usr_add3', $usr_add3), 'maxlength' => '100', 'size' => '35');
$data['usr_town_city'] = array('name' => 'usr_town_city', 'class' => 'form-control', 'id' => 'usr_town_city', 'value' => set_value('usr_town_city', $usr_town_city), 'maxlength' => '100', 'size' => '35');
$data['usr_zip_pcode'] = array('name' => 'usr_zip_pcode', 'class' => 'form-control', 'id' => 'usr_zip_pcode', 'value' => set_value('usr_zip_pcode', $usr_zip_pcode), 'maxlength' => '100', 'size' => '35');
$data['usr_access_level_options'] = array(1=>1, 2=>2, 3=>3, 4=>4, 5=>5);
$data['usr_access_level'] = array('value' => set_value('usr_access_level', ''));
$data['usr_is_active'] = $usr_is_active;
$data['id'] = array('usr_id' => set_value('usr_id', $usr_id));
$this->load->view('common/header', $data);
$this->load->view('nav/top_nav', $data);
$this->load->view('users/edit_user', $data);
$this->load->view('common/footer', $data);
} else { // Validation passed, now escape the data
假设表单输入通过了验证,我们将新的用户信息保存到 $data 数组中:
$data = array(
'usr_fname' => $this->input->post('usr_fname'),
'usr_lname' => $this->input->post('usr_lname'),
'usr_uname' => $this->input->post('usr_uname'),
'usr_email' => $this->input->post('usr_email'),
'usr_add1' => $this->input->post('usr_add1'),
'usr_add2' => $this->input->post('usr_add2'),
'usr_add3' => $this->input->post('usr_add3'),
'usr_town_city' => $this->input->post('usr_town_city'),
'usr_zip_pcode' => $this->input->post('usr_zip_pcode'),
'usr_access_level' => $this->input->post('usr_access_level'),
'usr_is_active' => $this->input->post('usr_is_active')
);
一旦所有内容都添加到 $data 数组中,我们尝试使用 Users_model 的 process_update_user() 函数更新用户的详细信息:
if ($this->Users_model->process_update_user($id, $data)) {
redirect('users');
}
}
}
通过在views/users/view_all_users.php文件中点击删除链接,将调用users控制器的delete_user()函数。与edit_user()函数类似,delete_user()使用附加在删除链接 URL 末尾的users_usr_id主键,并将其传递给Users_model的delete_user($id)函数。此模型函数接受一个参数——$id(如通过删除链接传递或如果表单被提交则使用$_POST传递)——并从users表中删除用户:
public function delete_user() {
// Set validation rules
$this->form_validation->set_rules('id', $this->lang->line('usr_id'), 'required|min_length[1]|max_length[11]|integer|is_natural');
if ($this->input->post()) {
$id = $this->input->post('id');
} else {
$id = $this->uri->segment(3);
}
$data['page_heading'] = 'Confirm delete?';
if ($this->form_validation->run() == FALSE) { // First load, or problem with form
$data['query'] = $this->Users_model->get_user_details($id);
$this->load->view('common/header', $data);
$this->load->view('nav/top_nav', $data);
$this->load->view('users/delete_user', $data);
$this->load->view('common/footer', $data);
} else {
if ($this->Users_model->delete_user($id)) {
redirect('users');
}
}
}
public function pwd_email() {
$id = $this->uri->segment(3);
send_email($data, 'reset');
redirect('users');
}
}
创建语言文件
与本书中的所有项目一样,我们正在使用语言文件来为用户提供文本。这样,您就可以启用多区域/多语言支持。
创建/path/to/codeigniter/application/language/english/en_admin_lang.php文件,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "Auth System";
// Top Nav
$lang['top_nav_users'] = "Users";
$lang['top_nav_new'] = 'New';
$lang['top_nav_signin'] = "Login";
$lang['top_nav_signout'] = "Logout";
// Login
$lang['signin_email'] = "Email";
$lang['signin_password'] = "Password";
$lang['admin_login_header'] = "Please sign in";
$lang['admin_login_email'] = "Email";
$lang['admin_login_password'] = "Password";
$lang['admin_login_signin'] = "Signin...";
$lang['admin_login_error'] = "Whoops! Something went wrong - have another go!";
$lang['forgot_pwd_header'] = 'Reset Password...';
$lang['forgot_pwd_instruction'] = 'Enter your email in the box below and if your email is in the database we will send you a new password' ;
$lang['signin_forgot_password'] = "Forgot Password?";
// Register
$lang['register_page_title'] = "Register...";
$lang['register_first_name'] = "First Name";
$lang['register_last_name'] = "Last Name";
$lang['register_email'] = "Email";
// Emails
$lang['email_subject_new_password'] = "Your new password.";
$lang['email_subject_reset_password'] = "Reset your password.";
// New/Edit User
$lang['usr_form_instruction_new'] = "New User Details";
$lang['usr_form_instruction_edit'] = "Edit User Details";
$lang['usr_id'] = "ID";
$lang['usr_fname'] = "First name";
$lang['usr_lname'] = "Last Name";
$lang['usr_uname'] = "Username";
$lang['usr_email'] = "Email";
$lang['usr_confirm_email'] = "Confirm Email";
$lang['usr_add1'] = "Address 1";
$lang['usr_add2'] = "Address 2";
$lang['usr_add3'] = "Address 3";
$lang['usr_town_city'] = "Town/City";
$lang['usr_zip_pcode'] = "Zip/Postal Code";
$lang['usr_access_level'] = "User Access Level";
$lang['is_active'] = "User is active?";
// Forgot password
$lang['forgot_pwd_success_heading'] = "Email Sent:";
$lang['forgot_pwd_success_msg'] = "An email has been sent to the address provided.";
// New password
$lang['signin_new_pwd_instruction'] = "Reset your password";
$lang['signin_new_pwd_email'] = "Your email";
$lang['signin_new_pwd_pwd'] = "Password";
$lang['signin_new_pwd_confirm'] = "Confirm password";
$lang['signin_new_pwd_code'] = "Code";
// Delete
$lang['delete_confirm_message'] = "Are you sure you want to delete the user: ";
将所有这些整合在一起
好的,这就是代码。现在,让我们看看它可以用作哪些方式——这将帮助我们了解它如何相互交互。
用户注册
以下是一系列步骤:
-
用户在浏览器中打开
register控制器,并被提示输入他们的名字、姓氏和电子邮件地址 -
用户提交表单,表单被发送到
index()注册函数 -
register控制器将用户的详细信息保存到users表中,并为它们生成密码 -
这将通过电子邮件发送给他们,并发送到之前提交的电子邮件地址
-
然后,用户可以登录系统并修改他们想要的详细信息
确保正确的访问权限
可以将控制器和某些特定功能分配给只有特定访问级别的用户访问。我们之前在章节中提到了这一点;然而,我们还将在这里讨论它。
看以下代码片段,特别是粗体部分:
if ( ($this->session->userdata('logged_in') == FALSE) ||
($this->session->userdata('usr_access_level') != 1) ) {
redirect('signin');
}
您可以将此函数放置在任何控制器或函数中,如您所愿;这样做将保护此代码块免受没有正确访问级别的用户的访问。第一部分检查用户是否已登录(即,如果存在会话),但第二个比较查看在登录时设置的用户的访问级别。通过调整检查的值,您可以定制对特定用户、用户组或访问级别的访问。
摘要
所以,这就是您所看到的——一个使用 Twitter Bootstrap 作为前端的前端简单的认证系统。它应该很容易适应和修改以满足您的需求,但仍然允许您完成基本操作。
在下一章中,我们将探讨创建一个简单的电子商务网站,这将允许您拥有一个简单的商店,并查看您可以如何扩展它的选项。
第七章:创建电子商务网站
这是一个小型、简洁的电子商务应用程序。没有管理 CMS 来管理产品(在本章中写太多会过于复杂),但有一个易于使用(并且对于您来说易于适应)的过程来显示产品并让客户订购它们。
在本章中,我们将涵盖:
-
设计和线框
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
整合所有内容
简介
在这个项目中,我们将创建一个简单的购物车。这个应用程序将允许客户查看产品,按类别过滤产品,并将产品添加到他们的购物车中。
它还将允许客户通过删除项目或更改这些项目的数量来更改他们的购物车。
最后,有一个客户详细信息表,允许将他们的个人详细信息保存到订单中以便处理。
为了创建这个项目的网络应用程序,我们将创建一个控制器;这将处理产品的显示、修改购物车中产品的数量,并处理订单的处理。
我们将创建一个语言文件来存储文本,这样在需要的情况下,您就可以拥有多种语言支持。
我们将创建所有必要的视图文件和一个与数据库接口的模型。
然而,这个应用程序以及本书中的其他所有应用程序都依赖于我们在第一章中做的基本设置,简介和共享项目资源;尽管您可以取大量代码并将其放入您可能已经拥有的任何应用程序中,但请记住,第一章中做的设置是这个章节的基础。
因此,无需多言,让我们开始吧。
设计和线框
和往常一样,在我们开始构建之前,我们应该看看我们打算构建什么。
首先,我们将简要描述我们的意图。我们计划构建一个应用程序,让人们可以像在线商店一样查看产品。他们可以按类别对这些产品进行排序。将产品添加到购物车中,并输入他们的详细信息以创建订单。将生成一个称为order_fulfilment_code的特殊代码(保存在orders.order_fulfilment_code数据库中)。此代码将允许您通过支付系统跟踪任何订单。
总之,为了更好地了解正在发生的事情,让我们看看以下网站地图:

所以这就是网站地图——首先要注意的是网站有多简单。这个项目只有四个主要区域。让我们逐一了解每个项目,并简要了解它们的功能:
-
首页:这是初始着陆区域。
index()函数显示要查看的产品,并显示用户可以过滤产品以查看相关项目的类别。因此,通过点击书籍类别,他们将只能看到被分配为书籍类别的产品。 -
添加到购物车:这处理将产品添加到用户购物车中的过程。购物车中的项目数量始终在导航栏中显示。
-
购物车:这显示了购物车中的项目列表以及增加或减少该购物车中每个项目数量的选项。
-
前往结账:这向用户显示一个表单,邀请他们输入信息。一旦他们点击“前往”,他们的订单和详细信息就会被添加到数据库中进行处理。
现在我们对网站的结构和形式有了相当好的了解,让我们来看看每个页面的线框图。
首页 – index()
以下截图展示了网站地图中1(首页index()项)的线框图。最初,用户会看到一个产品列表。该列表未经过筛选。在线框图的右侧是一个类别列表(如categories表中所示)。用户可以点击这些类别来筛选左侧查看的结果,点击所有类别将再次清除筛选。
每个产品下方都有一个添加到购物车按钮,允许用户将特定产品添加到购物车中。

添加到购物车 – add()
以下截图展示了用户点击并添加产品到购物车的过程。这是通过点击特定产品下方的添加到 购物车按钮完成的。点击此按钮将调用shop控制器的add()函数,然后该函数将调用 CodeIgniter Cart类的$this->cart->insert()函数,该函数将产品添加到购物车中。

购物车 – display_cart()
以下截图展示了网站地图中3(购物车display_cart()项)的线框图。用户会看到一个当前购物车中的项目列表。display_cart()函数可以通过两种方式访问——要么点击顶部导航菜单中的购物车链接,要么在点击网站地图中1(首页index()项)下显示的产品添加到购物车后立即进行。
点击前往结账将调用网站地图中4(前往结账项)的user_details()函数。

用户详情 – user_details()
以下截图展示了网站地图中4(前往结账user_details()项)的线框图。用户会看到一个表单,可以在其中添加他们的联系和配送详情。一旦用户输入详细信息并点击“前往”,他们的订单(购物车内容)和联系详情就会被写入orders和customer表,这两个表通过客户 ID 在orders表中连接。

文件概览
这是一个相对较小的项目,总的来说,我们只需要创建七个文件;具体如下:
-
/path/to/codeigniter/application/models/shop_model.php: 该文件提供对数据库的读写访问。 -
/path/to/codeigniter/application/views/shop/display_products.php: 该文件向用户显示产品列表,允许他们添加产品到购物车,并按类别过滤产品——如categories表中所定义的。 -
/path/to/codeigniter/application/views/shop/display_cart.php: 该文件向用户显示购物车的内容。有表单选项可以修改产品数量并进入结账流程。 -
/path/to/codeigniter/application/views/shop/user_details.php: 该文件向用户显示一个表单,允许他们输入订单履行的联系详情。用户信息存储在customer表中,该表通过customer表的主键与orders表(在orders表中)关联。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 该文件在页面顶部提供导航栏。 -
/path/to/codeigniter/application/controllers/shop.php: 该文件包含显示产品、将产品添加到购物车、修改购物车和处理客户详情所需的所有必要函数。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php: 该文件为应用程序提供语言支持。
前述七个文件的文件结构如下:
application/
├── controllers/
│ ├── shop.php
├── models/
│ ├── shop_model.php
├── views/shop/
│ ├── display_products.php
│ ├── display_cart.php
│ ├── user_details.php
├── views/nav/
│ ├── top_nav.php
├── language/english/
│ ├── en_admin_lang.php
创建数据库
好的,你应该已经按照第一章中所述设置了 CodeIgniter 和 Bootstrap,简介和共享项目资源。如果没有,那么你应该知道,本章中的代码是专门针对第一章中的设置编写的。然而,如果你还没有设置,也不是世界末日;代码可以轻松应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到您的数据库中:
CREATE DATABASE `shopdb`;
USE DATABASE `shopdb`;
CREATE TABLE `categories` (
`cat_id` int(11) NOT NULL AUTO_INCREMENT,
`cat_name` varchar(50) NOT NULL,
`cat_url_name` varchar(15) NOT NULL,
PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;
INSERT INTO `categories` VALUES (1,'Shirts','shirts'),(2,'Footware','footware'),(3,'Books','books'),(4,'Beauty','beauty'),(5,'Software','software'),(6,'Computers','computers'),(7,'Kitchen Ware','kitchenware'),(8,'Luggage','luggage'),(9,'Camping','camping'),(10,'Sports','sports');
CREATE TABLE `ci_sessions` (
`session_id` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '0',
`ip_address` varchar(16) COLLATE utf8_bin NOT NULL DEFAULT '0',
`user_agent` varchar(120) COLLATE utf8_bin DEFAULT NULL,
`last_activity` int(10) unsigned NOT NULL DEFAULT '0',
`user_data` text COLLATE utf8_bin NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `customer` (
`cust_id` int(11) NOT NULL AUTO_INCREMENT,
`cust_first_name` varchar(125) NOT NULL,
`cust_last_name` varchar(125) NOT NULL,
`cust_email` varchar(255) NOT NULL,
`cust_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`cust_address` text NOT NULL COMMENT 'card holder address',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
CREATE TABLE `orders` (
`order_id` int(11) NOT NULL AUTO_INCREMENT,
`cust_id` int(11) NOT NULL,
`order_details` text NOT NULL,
`order_subtotal` int(11) NOT NULL,
`order_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`order_closed` int(1) NOT NULL COMMENT '0 = open, 1 = closed',
`order_fulfilment_code` varchar(255) NOT NULL COMMENT 'the unique code sent to a payment provider',
`order_delivery_address` text NOT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
CREATE TABLE `products` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(255) NOT NULL,
`product_code` int(11) NOT NULL,
`product_description` varchar(255) NOT NULL,
`category_id` int(11) NOT NULL,
`product_price` int(11) NOT NULL,
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=latin1;
INSERT INTO `products` VALUES (1,'Running Shoes',423423,'These are some shoes',2,50),(2,'Hawaiian Shirt',34234,'This is a shirt',1,25),(3,'Slippers',23134,'Nice comfortable slippers',2,4),(4,'Shirt',2553245,'White Office Shirt',1,25),(5,'CodeIgniter Blueprints',5442342,'Some excellent projects to make and do (in CodeIgniter) - it\'s good value too!',3,25),(6,'Office Suite',34234123,'Writer, Calc, Presentation software',5,299),(7,'Anti-Virus',324142,'Get rid of those pesky viruses from your computer',5,29),(8,'Operating System',12341,'This can run your computer',5,30),(9,'Web Browser',42412,'Browse the web with a web browser (that\'s what they\'re for)',5,5),(10,'Dinner set',3241235,'6 dinner plates, 6 side plates, 6 cups',7,45),(11,'Champagne Glasses',1454352,'Crystal glasses to drink fizzy French plonk from ',7,45),(12,'Toaster',523234,'Capable of toasting 4 slices at once!',7,35),(13,'Kettle',62546245,'Heat water with this amazing kettle',7,25);
小贴士
现在看看最后那段 SQL 代码;它相当大且复杂。不要慌张;所有 SQL 代码都可以从本书在 Packt 网站上的支持页面在线获取。
你会看到我们首先创建的表是ci_sessions。我们需要这个表来允许 CodeIgniter 管理会话,特别是客户的购物车。然而,这只是从CodeIgniter 用户指南中可用的标准会话表,因此我不会包括该表的描述,因为它不是技术特定于这个应用程序的。但是,如果你感兴趣,可以在ellislab.com/codeigniter/user-guide/libraries/sessions.html找到描述。
好的,让我们逐个查看每个表中的每个项目,看看它们代表什么。首先,我们将查看categories表。
| 表:categories |
|---|
| 元素 |
cat_id |
cat_name |
cat_url_name |
现在看看products表:
| 表:products |
|---|
| 元素 |
product_id |
product_name |
product_code |
product_description |
category_id |
product_price |
接下来我们将看到customer表:
| 表:customer |
|---|
| 元素 |
cust_id |
cust_first_name |
cust_last_name |
cust_email |
cust_created_at |
cust_address |
最后,让我们看看orders表:
| 表:orders |
|---|
| 元素 |
order_id |
cust_id |
order_details |
order_subtotal |
order_created_at |
order_closed |
order_fulfilment_code |
order_delivery_address |
我们还需要对config/database.php文件进行修改,即设置数据库访问详情、用户名、密码等。
打开config/database.php文件并找到以下行:
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'your username';
$db['default']['password'] = 'your password';
$db['default']['database'] = 'shopdb';
现在编辑前面的行中的值,确保将这些值替换为更适合您设置和情况的值;因此,输入您的用户名、密码等。
调整config.php文件
在这个文件中有几件事情我们需要配置以支持会话和加密。因此,打开config/config.php文件并做出以下更改:
-
我们需要设置一个加密密钥;会话和 CodeIgniter 的加密功能都需要在
$config数组中设置一个加密密钥,所以找到以下行:$config['encryption_key'] = '';用以下内容替换它:
$config['encryption_key'] = 'a-random-string-of-alphanum-characters';小贴士
显然,实际上不要将值改为一个随机的字母数字字符序列;相反,改为,嗯,一个随机的字母数字字符序列——如果这说得通的话?是的,你知道我的意思。
-
找到以下行:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; $config['sess_encrypt_cookie'] = FALSE; $config['sess_use_database'] = FALSE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;用以下内容替换这些行:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = TRUE; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;
调整 routes.php 文件
我们希望将用户重定向到shop控制器而不是默认的 CodeIgniter welcome控制器。我们需要修改routes.php文件中的默认控制器设置以反映这一点:
-
打开
config/routes.php文件进行编辑,并找到以下行(文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。最初在一个 CodeIgniter 应用程序中,默认控制器被设置为
welcome;然而,我们不需要它。相反,我们希望默认控制器是shop。所以,找到以下行:$route['default_controller'] = "welcome";改成以下内容:
$route['default_controller'] = "shop"; $route['404_override'] = '';
创建模型
在本项目中有且只有一个模型——shop_model.php——它包含特定于搜索和将产品写入数据库的函数。
这是本项目唯一的模型;让我们简要地回顾一下其中的每个函数,以了解其一般功能,然后我们将更详细地进入代码部分。
在这个模型中有五个主要函数,具体如下:
-
get_product_details(): 这个函数接受一个参数——即将添加到购物车的产品的$product_id——并返回一个包含特定产品信息的数据库结果对象。这个模型函数被shop控制器的add()函数用来在产品添加到购物车之前获取正确的产品详情。 -
get_all_products(): 这个函数不接受任何参数。这个模型函数将返回产品列表(如products表中定义的)到shop控制器的index()函数。 -
get_all_products_by_category_name(): 这个函数接受一个参数——$cat_url_name(在数据库中定义为categories.cat_url_name)。当用户点击了分类筛选链接(在本章“首页 – index()”部分的页面右侧显示)时,会调用此函数。 -
get_all_categories(): 这个函数从categories表中获取分类。它用于填充分类列表(在本章“首页 – index()”部分的页面右侧显示)。 -
save_cart_to_database(): 这个函数接受两个参数:$cust_data和$order_data。$cust_data是用户在网站地图中第 4 点(继续结账的user_details()项目)提交的数据,而$order_data是他们购物车的内容。客户数据被添加到customer表中,生成的主键被用作orders表的外键。
这只是一个简要概述,所以让我们创建模型并讨论其功能。
在 /path/to/codeigniter/application/models/shop_model.php 文件中创建文件,并将以下代码添加到其中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Shop_model extends CI_Model {
function __construct() {
parent::__construct();
$this->load->helper('url');
}
public function get_product_details($product_id) {
$this->db->where('product_id', $product_id);
$query = $this->db->get('products');
return $query;
}
前面的 get_product_details() 函数返回所有产品的列表。如果用户没有过滤任何结果,即他们没有在 views/shop/display_products.php 文件中点击类别链接,这个函数将由 shop 控制器的 index() 函数调用:products() {
$q
public function get_all_uery = $this->db->get('products');
return $query;
}
前面的 get_all_products() 函数返回应用了过滤条件的产品列表。如果用户通过类别过滤了产品,即他们点击了 views/shop/display_products.php 文件中的类别链接,这个函数将由 shop 控制器的 index() 函数调用:
public function get_all_products_by_category_name($cat_url_name = null) {
if ($cat_url_name) {
$this->db->where('cat_url_name', $cat_url_name);
$cat_query = $this->db->get('categories');
foreach ($cat_query->result() as $row) {
$category_id = $row->cat_id;
}
$this->db->where('category_id', $category_id);
}
$query = $this->db->get('products');
return $query;
}
前面的 get_all_products_by_category_name() 函数返回 categories 表中所有类别的列表。这个模型函数是从 shop 控制器的 index() 函数中调用的,以向 views/shop/display_products.php 文件右侧的产品类别列表提供数据:
public function get_all_categories($cat_url_name = null) {
if ($cat_url_name) {
$this->db->where('cat_url_name', $cat_url_name);
}
$query = $this->db->get('categories');
return $query;
}
前面的 get_all_categories() 函数返回 categories 表中所有类别的列表。这个列表在 views/shop/display_products.php 文件中使用,其中 foreach 循环遍历数据库对象并显示类别给用户。用户可以点击一个类别并过滤他们的结果。
现在,看一下以下片段:
public function save_cart_to_database($cust_data, $order_data) {
$this->db->insert('customer', $cust_data);
$order_data['cust_id'] = $this->db->insert_id();
if ($this->db->insert('orders', $order_data)) {
return true;
} else {
return false;
}
}
}
前面的 save_cart_to_database() 函数将订单保存到数据库中;它将购物车中的数据以及用户在 views/shop/user_details.php 文件中输入的数据进行转换。
如您所见,模型相当直接和简洁,因此现在让我们看看视图。
创建视图
本项目中有四个视图,具体如下:
-
/path/to/codeigniter/application/views/shop/display_products.php: 这个视图向用户显示产品列表,并允许他们添加产品到购物车,也可以过滤产品。 -
/path/to/codeigniter/application/views/shop/display_cart.php: 这个视图显示用户购物车中的所有产品,允许他们更改购物车中产品的数量,并提供一个选项跳转到结账阶段。这是从 CodeIgniter 文档中可用的购物车模板的定制版本。 -
/path/to/codeigniter/application/views/shop/user_details.php: 这个视图向用户显示一个表单,允许他们输入有关订单的信息,例如他们的联系细节和送货地址。 -
/path/to/codeigniter/application/views/nav/top_nav.php:此视图显示顶级菜单。在此项目中,这非常简单,包含项目名称和链接到shop控制器以及名为 购物车 的链接;旁边有一个变量,显示值(默认为 0);然而,这实际上是任何时刻购物车中的项目数量。如果购物车中有七个项目,链接将显示为购物车(7)。
那是对视图的良好概述;现在让我们逐一查看每个视图,构建代码,并讨论它们的功能。
创建 /path/to/codeigniter/application/views/shop/display_products.php 文件,并将以下代码添加到其中:
<div class="row row-offcanvas row-offcanvas-right">
<div class="col-xs-12 col-sm-9">
<div class="row">
<?php foreach ($query->result() as $row) : ?>
<div class="col-6 col-sm-6 col-lg-4">
<h2><?php echo $row->product_name ; ?></h2>
<p>£<?php echo $row->product_price ; ?></p>
<p><?php echo $row->product_description ; ?></p>
<?php echo anchor('shop/add/'.$row->product_id, $this->lang->line('index_add_to_cart'), 'class="btn btn-default"') ; ?>
</div>
<?php endforeach ; ?>
</div>
</div>
上述代码块输出产品列表,并显示描述(products.product_description)、价格(products.product_price)和添加到购物车的链接。
使用 foreach 循环遍历 $query 中的产品。$query 的值由 Shop_model 的 get_all_products() 函数返回的数据填充;或者,如果用户已按类别(在下述 HTML 中解释)过滤,则 $query 由 Shop_model 的 get_all_products_by_category_name() 函数填充:
<div class="col-xs-6 col-sm-3 sidebar-offcanvas" id="sidebar" role="navigation">
<div class="list-group">
<?php echo anchor(base_url(), $this->lang->line('index_all_categories'), 'class="list-group-item"') ; ?>
<?php foreach ($cat_query->result() as $row) : ?>
<?php echo anchor('shop/index/'.$row->cat_url_name, $row->cat_name, 'class="list-group-item"') ; ?>
<?php endforeach ; ?>
</div>
</div>
</div>
上述代码块输出用户可以使用以过滤结果的类别列表。我们使用 foreach 循环遍历 $cat_query 数组。此数组由 Shop_model 的 get_all_categories() 函数提供。
创建 /path/to/codeigniter/application/views/shop/display_cart.php 文件,并将以下代码添加到其中:
<?php echo anchor('shop/user_details', $this->lang->line('display_cart_proceed_to_checkout'), 'type="button" class="btn btn-primary btn-lg"') ; ?>
<br /><br />
<?php echo form_open('shop/update_cart'); ?>
<table class="table">
<tr>
<th><?php echo $this->lang->line('display_cart_quantity') ; ?></th>
<th><?php echo $this->lang->line('display_cart_description') ; ?></th>
<th><?php echo $this->lang->line('display_cart_item_price') ; ?></th>
<th><?php echo $this->lang->line('display_cart_sub_total') ; ?></th>
</tr>
此视图负责向用户显示购物车的内容,并允许用户调整购物车中的项目数量。
查看以下代码行;通过它,我们创建了 $i 变量。这个变量在 foreach 循环中递增。我们使用 $i 变量给产品数量文本框赋予一个唯一的名称,即 1、2、3、4,依此类推:
<?php $i = 1; ?>
此 foreach 循环遍历 CodeIgniter Cart 类的 $this->cart->contents() 函数中的每个项目。每次迭代被视为 $item 变量:
<?php foreach ($this->cart->contents() as $items): ?>
<?php echo form_hidden($i . '[rowid]', $items['rowid']); ?>
<tr>
<td><?php echo form_input(array('name' => $i . '[qty]', 'value' => $items['qty'], 'maxlength' => '3', 'size' => '5')); ?></td>
<td>
<?php echo $items['name']; ?>
<?php if ($this->cart->has_options($items['rowid']) == TRUE): ?>
<p>
<?php foreach ($this->cart->product_options($items['rowid']) as $option_name => $option_value): ?>
<strong><?php echo $option_name; ?>:</strong> <?php echo $option_value; ?><br />
<?php endforeach; ?>
</p>
<?php endif; ?>
</td>
<td><?php echo $this->cart->format_number($items['price']); ?></td>
<td>£<?php echo $this->cart->format_number($items['subtotal']); ?></td>
</tr>
<?php $i++; ?>
<?php endforeach; ?>
<tr>
<td colspan="2"> </td>
<td><strong>Total</strong></td>
<td>£<?php echo $this->cart->format_number($this->cart->total()); ?></td>
</tr>
</table>
在 foreach 循环之后,我们向用户显示一个按钮。以下代码是用于提交表单以及任何调整后的项目数量的按钮:
<p><?php echo form_submit('', $this->lang->line('display_cart_update_cart'), 'class="btn btn-success"'); ?></p>
<?php echo form_close() ; ?>
创建 /path/to/codeigniter/application/views/shop/user_details.php 文件,并将以下代码添加到其中:
<div class="row row-offcanvas row-offcanvas-right">
<div class="col-xs-12 col-sm-9">
<div class="row">
<?php echo validation_errors(); ?>
<?php echo form_open('/shop/user_details') ; ?>
<?php echo form_input($first_name); ?><br />
<?php echo form_input($last_name); ?><br />
<?php echo form_input($email); ?><br />
<?php echo form_input($email_confirm); ?><br />
<?php echo form_textarea($payment_address); ?><br />
<?php echo form_textarea($delivery_address); ?><br />
<?php echo form_submit('', $this->lang->line('common_form_elements_go'), 'class="btn btn-success"') ; ?><br />
<?php echo form_close() ; ?>
</div>
</div>
</div>
上述代码块创建了一个表单,用户可以输入完成订单所需的联系详细信息。
创建 /path/to/codeigniter/application/views/nav/top_nav.php 文件,并将以下代码添加到其中:
<!-- Fixed navbar -->
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<?php echo base_url() ; ?>"><?php echo $this->lang->line('system_system_name'); ?></a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><?php echo anchor('shop', $this->lang->line('nav_home')) ; ?></li>
<li><?php echo anchor('shop/display_cart', ($items > 0) ? $this->lang->line('nav_cart_count') . '(' . $items . ')' : $this->lang->line('nav_cart_count') .'(0)') ; ?></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container theme-showcase" role="main">
上述代码块在页面顶部创建导航菜单。请查看以下加粗的代码,此处再次展示(重构后):
<li>
<?php
echo anchor('shop/display_cart',
($items > 0) ? $this->lang->line('nav_cart_count') .
'(' . $items . ')' : $this->lang->line('nav_cart_count').'(0)') ;
?>
</li>
上述代码块显示了与括号中的值一起的单词“Cart”。这个值最初被设置为 0(零)。然而,这个值实际上是购物车中的物品数量——如果没有物品在购物车中,这个数字将默认为零。
首先,我们使用 PHP 三元运算符在显示零和购物车中实际物品数量之间进行切换。如果物品数量大于零,那么购物车中肯定有一些物品。因此,我们显示那个数量,否则显示零。
单词“Cart”是在语言文件中设置的,但购物车中物品数量的值是从哪里来的呢?
购物车中的物品数量是由shop控制器中的几个函数计算得出的,这些函数包括index()、update_cart()和user_details()。让我们看看其中的一个(因为它们的工作方式相同)并看看它在index()函数中的工作方式;查看以下来自index()函数的代码段:
...
$cart_contents = $this->session->userdata('cart_contents');
$data['items'] = $cart_contents['total_items'];
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
...
我们从存储在cart_contents会话项中的购物车内容中获取内容,并将其存储在$cart_contents变量中(为了简单起见)。
CodeIgniter 的Cart类会自动跟踪购物车中所有物品的总数,并方便地将它存储在$cart_contents数组中的total_items项中。
然后,我们将$data['items']的值设置为total_items(这应该是购物车中的物品数量),并将其发送到nav/top_nav.php视图文件,在那里它紧挨着“Cart”这个词显示。
创建控制器
在这个项目中,我们将只创建一个控制器,即/path/to/codeigniter/application/controllers/shop.php。
现在我们来回顾一下那个控制器,看看代码,并讨论它是如何工作的。
创建/path/to/codeigniter/application/controllers/shop.php文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Shop extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->library('cart');
$this->load->helper('form');
$this->load->helper('url');
$this->load->helper('security');
$this->load->model('Shop_model');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
public function index() {
我们希望显示正确的产品,因此我们需要测试用户是否点击了views/shop/display_products.php文件右侧的其中一个筛选链接。我们测试是否存在第三个uri参数。
如果第三个参数不存在,那么我们可以安全地假设用户不想进行任何过滤。因此,我们调用Shop_model的get_all_products()函数。
如果存在第三个参数,那么用户必须正在过滤他们的结果。因此,我们调用get_all_products_by_category_name($this->uri->segment(3))函数,并将第三个参数传递给它。
第三个参数来自数据库中的categories.cat_url_name列,在views/shop/display_products.php文件中通过一个foreach循环写出来。
循环遍历由Shop_model的get_all_categories()函数填充的cat_query数据库对象,如下所示:
if (!$this->uri->segment(3)) {
$data['query'] = $this->Shop_model->get_all_products();
} else {
$data['query'] = $this->Shop_model->get_all_products_by_category_name($this->uri->segment(3));
}
如前一段所述,调用Shop_model的get_all_categories()函数,将其结果返回到$data['cat_query']。在views/shop/display_products.php文件中,它通过foreach循环遍历以创建分类列表:
$data['cat_query'] = $this->Shop_model->get_all_categories();
现在,我们从cart_contents会话项中获取购物车中的商品数量。关于这一点的完整解释在本章的创建视图部分,特别是在对/path/to/codeigniter/application/views/nav/top_nav.php视图文件的解释中:
$cart_contents = $this->session->userdata('cart_contents');
$data['items'] = $cart_contents['total_items'];
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
$this->load->view('shop/display_products', $data);
$this->load->view('common/footer');
}
以下add()函数将商品添加到购物车。它从views/shop/display_products.php文件中调用,当用户点击“添加到购物车”时。添加到购物车链接的第三个参数是产品 ID(products.product_id)。我们从 URI(它是第三个部分)中获取产品 ID,并将其传递给Shop_model的get_product_details($product_id)函数。这将返回产品详情到$query变量。我们遍历$query,提取产品的单个详情并将其保存到$data数组中:
public function add() {
$product_id = $this->uri->segment(3);
$query = $this->Shop_model->get_product_details($product_id);
foreach($query->result() as $row) {
$data = array(
'id' => $row->product_id,
'qty' => 1,
'price' => $row->product_price,
'name' => $row->product_name,
);
}
我们使用 CodeIgniter Cart类的$this->cart->insert();函数将$data数组保存到购物车中:
$this->cart->insert($data);
我们随后获取所有分类列表以及购物车中新的商品数量,并将它们发送到nav/top_nav.php视图文件。
shop/display_cart.php视图文件将使用 CodeIgniter Cart类的$this->cart->contents()函数遍历购物车的内容:
$data['cat_query'] = $this->Shop_model->get_all_categories();
$cart_contents = $this->session->userdata('cart_contents');
$data['items'] = $cart_contents['total_items'];
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
$this->load->view('shop/display_cart', $data);
$this->load->view('common/footer');
}
当用户在views/shop/display_cart.php文件中点击更新购物车按钮时,将调用update_cart()函数。当它被调用时,它遍历来自views/shop/display_cart.php表单的输入并将其保存到$data数组中;让我们看一下:
public function update_cart() {
$data = array();
$i = 0;
首先,我们创建一个名为$data的数组,在其中我们可以存储调整后的购物车数据(我们稍后会用到)。然后,我们创建一个$i变量;我们将使用这个变量来创建一个多维数组,在循环的每次迭代中增加$i的值——$i保持rowid值(购物车中产品的 ID)和qty值相关联。
我们遍历提交的数据(来自views/shop/display_cart.php中的表单),将循环的每次迭代视为$item。
每个$item都有一个rowid元素(产品在购物车中的位置)和qty,这是调整后的产品数量:
foreach($this->input->post() as $item) {
$data[$i]['rowid'] = $item['rowid'];
$data[$i]['qty'] = $item['qty'];
$i++;
}
现在购物车数据已经迭代完毕,并且已经进行了任何数量的调整,我们将使用 CodeIgniter Cart类的$this->cart->update()函数来更新购物车。然后,我们使用redirect()函数将用户重定向到shop控制器的display_cart()函数,该函数将向用户报告新的值:
$this->cart->update($data);
redirect('shop/display_cart');
}
实际上,对购物车数据的迭代是在views/shop/display_cart.php视图文件中完成的,但display_cart()函数的存在是为了提供一种特定方式来查看购物车中的商品。调用此函数将加载views/shop/display_cart.php视图:
public function display_cart() {
$data['cat_query'] = $this->Shop_model->get_all_categories();
$cart_contents = $this->session->userdata('cart_contents');
$data['items'] = $cart_contents['total_items'];
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
$this->load->view('shop/display_cart', $data);
$this->load->view('common/footer');
}
public function clear_cart() {
$this->cart->destroy();
redirect('index');
}
user_details() 函数负责向用户显示表单,允许他们输入联系信息,验证这些信息,并将购物车转换为订单。让我们详细看看它是如何工作的。
首先,我们开始设置表单提交的验证规则:
public function user_details() {
// Set validation rules
$this->form_validation->set_rules('first_name', $this->lang->line('user_details_placeholder_first_name'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('last_name', $this->lang->line('user_details_placeholder_last_name'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('email', $this->lang->line('user_details_placeholder_email'), 'required|min_length[1]|max_length[255]|valid_email');
$this->form_validation->set_rules('email_confirm', $this->lang->line('user_details_placeholder_email_confirm'), 'required|min_length[1]|max_length[255]|valid_email|matches[email]');
$this->form_validation->set_rules('payment_address', $this->lang->line('user_details_placeholder_payment_address'), 'required|min_length[1]|max_length[1000]');
$this->form_validation->set_rules('delivery_address', $this->lang->line('user_details_placeholder_delivery_address'), 'min_length[1]|max_length[1000]');
如果这是初始页面加载或表单提交出现错误,则 $this->form_validation->run() 函数将返回 FALSE。如果发生任何一种情况,我们将开始构建表单元素,为每个表单项定义设置:
if ($this->form_validation->run() == FALSE) {
$data['first_name'] = array('name' => 'first_name', 'class' => 'form-control', 'id' => 'first_name', 'value' => set_value('first_name', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('user_details_placeholder_first_name'));
$data['last_name'] = array('name' => 'last_name', 'class' => 'form-control', 'id' => 'last_name', 'value' => set_value('last_name', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('user_details_placeholder_last_name'));
$data['email'] = array('name' => 'email', 'class' => 'form-control', 'id' => 'email', 'value' => set_value('email', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('user_details_placeholder_email'));
$data['email_confirm'] = array('name' => 'email_confirm', 'class' => 'form-control', 'id' => 'email_confirm', 'value' => set_value('email_confirm', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('user_details_placeholder_email_confirm'));
$data['payment_address'] = array('name' => 'payment_address', 'class' => 'form-control', 'id' => 'payment_address', 'value' => set_value('payment_address', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('user_details_placeholder_payment_address'));
$data['delivery_address'] = array('name' => 'delivery_address', 'class' => 'form-control', 'id' => 'delivery_address', 'value' => set_value('delivery_address', ''), 'maxlength' => '100', 'size' => '35', 'placeholder' => $this->lang->line('user_details_placeholder_delivery_address'));
现在我们从 cart_contents 会话项中获取购物车中的项目数量。关于这一点的完整解释在本章的 创建视图 部分中,在 /path/to/codeigniter/application/views/nav/top_nav.php 视图文件的说明下。在获取导航栏中 Cart 链接的购物车内容后,我们将加载 views/shop/user_details.php 文件,该文件将负责显示表单:
$cart_contents = $this->session->userdata('cart_contents');
$data['items'] = $cart_contents['total_items'];
$this->load->view('common/header');
$this->load->view('nav/top_nav', $data);
$this->load->view('shop/user_details', $data);
$this->load->view('common/footer');
} else {
如果在提交表单时没有错误,那么我们将到达以下代码。我们定义两个数组——一个称为 $cust_data,它将存储用户在 views/shop/user_details.php 文件中的表单提交的信息,另一个称为 $order_details,它将存储购物车的序列化输出。因此,以下代码块保存用户的表单数据:
$cust_data = array(
'cust_first_name' => $this->input->post('cust_first_name'),
'cust_last_name' => $this->input->post('cust_last_name'),
'cust_email'=> $this->input->post('cust_email'),
'cust_address' => $this->input->post('payment_address'));
$payment_code 值充当一种钩子,可用于支付处理。例如,大多数支付处理系统支持添加一个 代码——通常是由商店应用程序生成的一串文本和/或数字,保存到数据库中,并发送给支付提供商。支付完成后,webhook 脚本将从支付处理系统接收一个信号,其中包含成功或错误消息(来自客户银行账户的尝试支付的成功或失败),以及 代码。这样,您可以确保正确的订单已被支付(或未支付);无论如何,$payment_code 是当前项目中以下方法:
$payment_code = mt_rand();
以下代码块将购物车数据保存到 $order_data 数组中。通过 CodeIgniter Cart 类的 $this->cart->contents() 函数从购物车中获取购物车内容。返回的数组传递给 serialize() PHP 函数,并写入 $order_data['order_details']:
$order_data = array(
'order_details' => serialize($this->cart->contents()),
'order_delivery_address' => $this->input->post('delivery_address'),
'order_closed' => '0',
'order_fulfilment_code' => $payment_code,
'order_delivery_address' => $this->input->post('payment_address'));
现在客户的联系信息和订单详情都存储在数组中,我们可以开始将它们保存到数据库中。我们调用 Shop_model 的 save_cart_to_database() 函数,并将 $cust_data 和 $order_data 数组传递给它。
Shop_model 的 save_cart_to_database() 函数首先将客户信息保存到 customer 表中,返回插入的主键,并使用该主键作为外键值放入 orders.cust_id:
if ($this->Shop_model->save_cart_to_database($cust_data, $order_data)) {
echo $this->lang->line('user_details_save_success');
} else {
echo $this->lang->line('user_details_save_error');
}
}
}
}
创建语言文件
与本书中的所有项目一样,我们正在使用语言文件向用户提供服务。这样,您就可以启用多区域/多语言支持。让我们创建语言文件。
创建/path/to/codeigniter/application/language/english/en_admin_lang.php文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "Shop";
// nav
$lang['nav_cart_count'] = "Cart ";
$lang['nav_home'] = "Home";
// index()
$lang['index_all_categories'] = "All categories";
$lang['index_add_to_cart'] = "Add to cart";
// display_cart()
$lang['display_cart_proceed_to_checkout'] = "Proceed to checkout";
$lang['display_cart_quantity'] = "Quantity";
$lang['display_cart_description'] = "Description";
$lang['display_cart_item_price'] = "Item Price";
$lang['display_cart_sub_total'] = "Sub-Total";
$lang['display_cart_update_cart'] = "Update Cart";
// user_details()
$lang['user_details_placeholder_first_name'] = "First Name";
$lang['user_details_placeholder_last_name'] = "Last Name";
$lang['user_details_placeholder_email'] = "Email";
$lang['user_details_placeholder_email_confirm'] = "Confirm Email";
$lang['user_details_placeholder_payment_address'] = "Payment Address";
$lang['user_details_placeholder_delivery_address'] = "Delivery Address";
$lang['user_details_save_success'] = "Order and Customer saved to DB";
$lang['user_details_save_error'] = "Could not save to DB";
将所有内容整合起来
好的,这里有一些示例,将帮助您将所有内容整合在一起。
过滤搜索
当你过滤搜索时,以下事件会发生:
-
用户访问网站,CodeIgniter 将他们路由到
shop控制器。shop控制器加载index()函数 -
index()函数识别出 URL 中没有第三个参数,因此它调用Shop_model的get_all_products()函数。 -
index()函数加载Shop_model的get_all_categories()函数,将返回的结果传递给$data['cat_query']。这被传递到views/shop/display_products.php文件,该文件使用foreach循环输出类别。 -
用户点击列表中的某个类别。URL 调用
index()函数,但这次带有第三个参数。 -
index()函数识别出这个第三个参数,并加载Shop_model的get_all_products_by_category_name()函数,将第三个uri段传递给它。 -
Shop_model的get_all_products_by_category_name()函数然后查找categories表中一个categories.cat_url_name值与第三个参数中提供的值相匹配的类别,并返回该类别的主键。 -
然后它在
products表中查找所有products.category_id值与上一步中找到的类别的主键相匹配的产品,使用get_all_products_by_category_name()返回查询,并将其发送到shop控制器的index()函数,该函数将其发送到views/shop/view_products.php文件。
添加到购物车
将商品添加到购物车的事件顺序如下:
-
用户访问网站,CodeIgniter 将他们路由到
shop控制器。shop控制器加载index()函数 -
index()函数识别出 URL 中没有第三个参数,因此它调用Shop_model的get_all_products()函数。 -
使用
foreach循环,views/shop/display_products.php文件遍历get_all_products()的结果对象,并依次显示每个产品。 -
用户点击了添加到购物车按钮
-
CodeIgniter 调用
shop控制器的add()函数 -
add()函数从第三个uri段中获取产品 ID,并将其发送到Shop_model的get_product_details()函数。 -
get_product_details()函数在products表中查找与传递给它的参数中的主键匹配的产品,并将其返回给$query变量。 -
使用
foreach循环遍历$query,获取产品的详细信息,如product_name和product_price,并将它们保存到名为$data的数组中,我们将将其添加到购物车中。我们还设置qty值为1(因为他们只添加一个商品)。 -
使用 CodeIgniter
Cart类的$this->cart->insert()函数,通过传递$data数组将产品添加到购物车中。 -
然后我们将用户引导到
display_cart(),以便他们可以做出任何修改。
修改产品数量
用户可以通过以下两种方式之一访问购物车:
-
通过点击页面顶部的导航栏中的购物车
-
在他们向购物车添加商品后自动跳转至购物车页面
我们将假设用户使用了这两种方法之一(因为它们都带我们到这里):
-
CodeIgniter 调用
display_cart()购物功能。 -
显示购物车的大部分工作发生在
views/shop/display_cart.php文件中,这是 CodeIgniter 文档中找到的模板的修改版本。 -
创建了一个变量
$i并赋予其值为1;这将随着循环的迭代而递增。 -
使用
foreach循环遍历 CodeIgniterCart类的$this->cart->contents()函数。对于每次迭代,我们调用$item。 -
迭代将每个产品的详细信息写入 HTML 表格中。
-
创建了一个名为
$i的 HTML 文本输入框,因此如果当前迭代是1,则文本框的名称将是1,如果当前迭代是4,则文本框的名称将是4。 -
购物车中有三个商品(三行)。每一行显示购物车中每种商品各有一个。用户希望更改第三行中产品的数量。
-
用户选择名为
3的文本框的值,并将该文本框中的值替换为数字2(这意味着用户希望购买产品 1 的一个商品,产品 2 的一个商品,和产品 3 的两个商品)。 -
用户按下更新购物车按钮。
-
CodeIgniter 调用
update_cart()购物功能。 -
update_cart()函数调整购物车中第三种产品的数量。
对于详细说明,请查看本章创建控制器部分的解释——寻找update_cart()函数描述。
摘要
在这个项目中,你看到了一个伟大的购物平台的开端。像往常一样,有一些事情你可以做来扩展功能,如下所示:
-
产品内容管理系统:本项目不附带用于管理产品或类别的 CMS——这仅仅是因为添加此类功能将是一个过于庞大的主题,难以涵盖。然而,也许你可以添加某种功能来管理产品,添加新产品、删除旧产品等。
-
产品图片:你可以在
products表中添加一列,用于存储图片文件名,然后通过 HTML<img src="img/li">回显该值。 -
``产品页面:你可以在产品标题中添加一个链接,打开新页面并显示有关该产品的详细信息,例如颜色、尺寸、重量、“盒内物品”等等。当然,你需要向
products表添加额外的列来支持新信息,但这可以相当容易地完成。``` -
**BOGOFF**: 动词,英国俚语——一个人对另一个人的一种鼓励,离开!离开!永远不再出现! 嗯,不是完全如此,但你可以添加一个**买一送一**(呃,不确定最后一个 F)选项。你可以添加逻辑,以便在选择了特定数量的产品时应用折扣。
第八章。创建待办事项列表
这是一个很好的小项目;这是每个人在日常工作中可能都需要的东西:待办事项列表。这个项目将为你提供一个创建任务并将它们标记为完成的小应用程序。你还可以在项目中扩展范围,真正使其成为你自己的。
在本章中,我们将涵盖以下主题:
-
设计和线框图
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
整合所有内容
简介
对了;在这个项目中,我们将创建一个允许用户创建任务并以列表形式查看这些任务的应用程序。任务也可以有截止日期;晚于截止日期的任务将以红色显示,这样你知道需要尽快执行该任务。
要创建这个应用程序,我们将创建一个控制器;这将处理任务的显示、创建这些任务、将这些任务标记为完成或未完成,以及处理这些任务的删除。
我们将创建一个语言文件来存储文本,以便在需要时提供多语言支持。
我们将创建所有必要的视图文件和一个模型来与数据库接口。
然而,这个应用以及本书中的其他应用,都依赖于我们在第一章中完成的基本设置,简介和共享项目资源;尽管你可以将大量代码片段放入你几乎已经拥有的任何应用中,但请记住,第一章中完成的设置是这个章节的基础。
所以,我们不再拖延,让我们开始吧。
设计和线框图
和往常一样,在我们开始构建之前,我们应该看看我们计划构建什么。
首先,简要描述我们的意图:我们计划构建一个应用程序,让人们能够添加他们需要完成的任务。它还将允许用户以列表形式查看这些任务并将它们标记为完成。如果他们愿意,他们还可以删除旧的或不再需要的任务。
总之,为了更好地了解正在发生的事情,让我们看一下以下网站地图:

因此,这是网站地图;首先要注意的是网站是多么简单。这个项目只有四个主要区域。让我们逐一了解每个项目,并简要了解它们的功能:
-
查看全部:这显示了一个创建任务的表单,并显示列表中的所有任务
-
创建:这处理将任务保存到数据库中的创建过程
-
完成/未完成:这将为任务设置完成或未完成的标记
-
删除:这将从数据库中删除任务
现在我们对网站的结构和形式有了相当好的了解,让我们看一下每个页面的线框图。
查看全部/创建
以下截图显示了网站地图中前述的 1 点(查看所有项)和 2 点(创建项)的线框。最初,用户会看到一个任务列表。他们能够点击 已完成 或 待办 按钮进入网站地图中的第 3 点(已完成/未完成项)。

删除
以下截图显示了网站地图中第 4 点(删除项)的线框。用户查看任务描述(tasks.task_desc)并被提供删除(从数据库中处理删除任务)或取消以返回网站地图中的第 1 点(查看所有项)的选项。

文件概览
这是一个相对较小的项目,总的来说,我们只需要创建六个文件;这些文件如下:
-
/path/to/codeigniter/application/models/tasks_model.php: 这提供了对tasks数据库表的读写访问。 -
/path/to/codeigniter/application/views/tasks/delete.php: 这向用户显示一个表单,要求他们确认删除任务。 -
/path/to/codeigniter/application/views/tasks/view.php: 这是tasks控制器的index()函数的视图。它向用户显示任务列表。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 这在页面顶部提供了一个导航栏。 -
/path/to/codeigniter/application/controllers/tasks.php: 这包含三个主要函数:index()、apply()和create()。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php: 这为应用程序提供语言支持。
前六个文件的文件结构如下:
application/
├── controllers/
│ ├── tasks.php
├── models/
│ ├── tasks_model.php
├── views/tasks/
│ ├── view.php
│ ├── delete.php
├── views/nav/
│ ├── top_nav.php
├── language/english/
│ ├── en_admin_lang.php
创建数据库
好的,你应该已经按照第一章中所述设置了 CodeIgniter 和 Bootstrap,简介和共享项目资源。如果没有,那么你应该知道,本章中的代码是专门针对第一章中的设置编写的。然而,如果你还没有这样做,也不是世界末日——代码可以轻松地应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到你的数据库中:
CREATE DATABASE `tasksdb`;
USE DATABASE `tasksdb`;
CREATE TABLE `ci_sessions` (
`session_id` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '0',
`ip_address` varchar(16) COLLATE utf8_bin NOT NULL DEFAULT '0',
`user_agent` varchar(120) COLLATE utf8_bin DEFAULT NULL,
`last_activity` int(10) unsigned NOT NULL DEFAULT '0',
`user_data` text COLLATE utf8_bin NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `tasks` (
`task_id` int(11) NOT NULL AUTO_INCREMENT,
`task_desc` varchar(255) NOT NULL,
`task_due_date` datetime DEFAULT NULL,
`task_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`task_status` enum('done','todo') NOT NULL,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
提示
现在,看看最后那段 SQL 代码,它相当大且复杂。不要慌张;所有 SQL 代码都可以从 Packt 网站上这本书的支持页面在线获取。
你会看到我们创建的第一个表是ci_sessions,我们需要允许 CodeIgniter 管理会话,特别是已登录的用户。然而,这只是从CodeIgniter 用户指南中可用的标准会话表,所以我不包括对该表的描述,因为它不是技术特定于这个应用程序的。但是,如果你感兴趣,可以在ellislab.com/codeigniter/user-guide/libraries/sessions.html找到描述。
对,让我们逐个查看每个表中的每个条目,看看它们代表什么:
| 表:tasks |
|---|
| 元素 |
task_id |
task_desc |
task_due_date |
task_created_at |
task_status |
我们还需要对config/database.php文件进行修改,具体是设置数据库访问详情,用户名、密码等。
打开config/database.php文件并找到以下行:
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'your username';
$db['default']['password'] = 'your password';
$db['default']['database'] = 'tasksdb';
编辑上一行的值,确保用更具体于你的设置和情况的值替换这些值;因此,输入你的用户名、密码等。
调整 config.php 文件
在这个文件中,我们需要配置一些内容以支持会话和加密。因此,打开config/config.php文件并做出以下更改:
-
我们需要设置一个加密密钥——会话和 CodeIgniter 的加密功能都需要在
$config数组中设置加密密钥,所以找到以下行:$config['encryption_key'] = '';将上一行替换为以下内容:
$config['encryption_key'] = 'a-random-string-of-alphanum-characters';小贴士
现在,显然,实际上不要将上一值直接更改为一个随机的字母数字字符序列,而是更改为,嗯,一个随机的字母数字字符序列——如果这说得通?是的,你知道我的意思。
-
找到以下行:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; $config['sess_encrypt_cookie'] = FALSE; $config['sess_use_database'] = FALSE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;将上一行替换为以下内容:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = TRUE; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;
调整 routes.php 文件
我们希望将用户重定向到tasks控制器而不是默认的 CodeIgniterwelcome控制器。为此,我们需要修改routes.php文件中的默认控制器设置:
-
打开
config/routes.php文件进行编辑,找到以下行(文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。在 CodeIgniter 应用程序中,默认控制器最初设置为
welcome;然而,我们不需要这个——相反,我们希望默认控制器是tasks。所以找到以下行:$route['default_controller'] = "welcome";更改为以下内容:
$route['default_controller'] = "tasks"; $route['404_override'] = '';
创建模型
在这个项目中只有一个模型,即 tasks_model.php,它包含特定于搜索和将任务写入数据库的函数。
这是本项目唯一的模型。让我们简要地回顾其中的每个函数,以了解其一般功能,然后我们将更详细地讨论代码。
在这个模型中有五个主要函数,如下所示:
-
get_tasks():这个函数有两个作用:首先,显示所有任务——例如,当用户首次访问网站或用户在表单中输入新任务时。 -
change_task_status():这个函数将数据库中的tasks.task_status值从todo或done中更改。设置为done的任务在列表中会被划掉,而设置为todo的任务则不会被划掉,并正常显示;这样,用户可以轻松地判断出哪些已完成,哪些尚未完成。 -
save_task():当用户从网站地图的 3(创建项目)点提交表单时,这个函数将任务保存到数据库中。 -
get_task():这个函数从tasks表中检索一个单独的任务。 -
delete():这个函数从tasks表中删除一个任务。
这只是一个简要概述,所以让我们创建模型并讨论其功能。
创建 /path/to/codeigniter/application/models/tasks_model.php 文件,并向其中添加以下代码:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Tasks_model extends CI_Model {
function __construct() {
parent::__construct();
}
get_tasks() 函数不接受任何参数。它从数据库返回所有任务,并将其返回给 tasks 控制器的 index() 函数。tasks/view.php 视图文件将遍历数据库结果对象,并在 HTML 表格中显示任务:
function get_tasks() {
$query = "SELECT * FROM `tasks` ";
$result = $this->db->query($query);
if ($result) {
return $result;
} else {
return false;
}
}
change_task_status() 函数将任务的状态从 todo 或 done 中更改。
它接受两个参数:$task_id 和 $save_data。$task_id 和 $save_data 的值由 tasks 控制器的 status() 函数传递。
$task_id 的值在用户点击 views/tasks/view.php 视图文件中的 It's Done 或 Still Todo 时设置;任一选项的 uri 段落的第四个参数是 tasks 表中任务的键(tasks.task_id),通过使用 CodeIgniter 函数 $this->uri->segment(4),我们获取该值并将其存储在 $task_id 本地变量中。
$save_data 值在 tasks 控制器中填充。它只包含一个项目,即 task_status,该值在 status() 函数中使用 uri 段落的第三个参数填充:
function change_task_status($task_id, $save_data) {
$this->db->where('task_id', $task_id);
if ($this->db->update('tasks', $save_data)) {
return true;
} else {
return false;
}
}
save_task() 函数接受一个参数——一个数据数组。这些数据由 tasks 控制器的 index() 函数提供。该函数会将一个任务保存到 tasks 表中,如果成功则返回 true,如果发生错误则返回 false:
function save_task($save_data) {
if ($this->db->insert('tasks', $save_data)) {
return true;
} else {
return false;
}
}
get_task() 函数接受一个参数——$task_id(即数据库中任务的唯一键)。它由 tasks 控制器的 delete() 函数提供,该函数使用它来提供删除确认表单中关于任务的信息。
用户在 views/tasks/view.php 文件中点击 删除 按钮,该文件的第三个参数是任务的唯一键。然后 tasks 控制器的 delete() 函数将使用 $this->uri->segment(3) CodeIgniter 函数从 URI 中获取该 ID。这个 ID 被传递给 get_task() 模型函数,该函数将返回数据库中任务的详细信息或 false 如果没有找到 ID:
function get_task($id) {
$this->db->where('task_id', $id);
$result = $this->db->get('tasks');
if ($result) {
return $result;
} else {
return false;
}
}
delete() 函数在数据库上执行操作以删除任务。它接受一个参数——任务的 ID,即该任务的唯一键:
function delete($id) {
$this->db->where('task_id', $id);
$result = $this->db->delete('tasks');
if ($result) {
return true;
} else {
return false;
}
}
}
创建视图
在这个项目中,有三个视图,具体如下:
-
/path/to/codeigniter/application/views/tasks/view.php: 这段代码向用户显示当前任务列表以及一个允许用户创建新任务的表单。 -
/path/to/codeigniter/application/views/tasks/delete.php: 这段代码向用户显示一个确认消息,询问他们是否真的想要删除任务。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 这段代码显示顶层菜单。在这个项目中,这非常简单;它包含一个项目名称和一个链接,可以跳转到tasks控制器。
这些是我们的三个视图文件。现在让我们逐一查看它们,构建代码,并讨论它们的功能。
创建 /path/to/codeigniter/application/views/tasks/view.php 文件,并向其中添加以下代码:
<div class="page-header">
<?php echo form_open('tasks/index') ; ?>
<div class="row">
<div class="col-lg-12">
<?php echo validation_errors() ; ?>
<div class="input-group">
<input type="text" class="form-control" name="task_desc" placeholder="<?php echo $this->lang->line('tasks_add_task_desc'); ?>">
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><?php echo $this->lang->line('tasks_add_task'); ?></button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div><!-- /.row -->
<div class="row">
<div class="form-group">
<div class="col-md-2">
<?php echo form_error('task_due_d'); ?>
<select name="task_due_d" class="form-control">
<option></option>
<?php for ( $i = 1; $i <= 30; $i++) : ?>
<option value="<?php echo $i ; ?>"><?php echo date('jS', mktime($i,0,0,0, $i, date('Y'))) ; ?></option>
<?php endfor ; ?>
</select>
</div>
<div class="col-md-2">
<?php echo form_error('task_due_m'); ?>
<select name="task_due_m" class="form-control">
<option></option>
<?php for ( $i = 1; $i <= 12; $i++) : ?>
<option value="<?php echo $i ; ?>"><?php echo date('F', mktime(0,0,0,$i, 1, date('Y'))) ; ?></option>
<?php endfor ; ?>
</select>
</div>
<div class="col-md-2">
<?php echo form_error('task_due_y'); ?>
<select name="task_due_y" class="form-control">
<option></option>
<?php for ($i = date("Y",strtotime(date("Y"))); $i <= date("Y",strtotime(date("Y").' +5 year')); $i++) : ?>
<option value="<?php echo $i;?>"><?php echo $i;?></option>
<?php endfor ; ?>
</select>
</div>
</div>
</div>
<?php echo form_close() ; ?>
</div>
上述代码块是用户可以用来创建新任务的表单。同时在这个代码块中还有验证错误代码(validation_errors()),我们将在这里显示从表单提交的数据中出现的任何错误:
<table class="table table-hover">
<?php foreach ($query->result() as $row) : ?>
<?php if (date("Y-m-d",mktime(0, 0, 0, date('m'), date('d'), date('y'))) > $row->task_due_date) {echo ' <tr class="list-group-item-danger">';} ?>
<?php if ($row->task_due_date == null) {echo ' <tr>';} ?>
<td width="80%"><?php if ($row->task_status == 'done') {echo '<strike>'.$row->task_desc.'</strike>' ;} else {echo $row->task_desc;} ?>
</td>
<td width="10%">
<?php if ($row->task_status == 'todo') {echo anchor ('tasks/status/done/'.$row->task_id, 'It\'s Done');} ?>
<?php if ($row->task_status == 'done') {echo anchor ('tasks/status/todo/'.$row->task_id, 'Still Todo');} ?>
</td>
<td width="10%"><?php echo anchor ('tasks/delete/'.$row->task_id, $this->lang->line('common_form_elements_action_delete')) ; ?>
</td>
</tr>
<?php endforeach ; ?>
</table>
上述表格输出了数据库中的任何任务。操作也在此代码块中,即 PHP 的三元运算符,它将状态从 已完成 切换到 待办,以及 删除 链接。
创建 /path/to/codeigniter/application/views/tasks/delete.php 文件,并向其中添加以下代码:
<h2><?php echo $page_heading ; ?></h2>
<p class="lead"><?php echo $this->lang->line('delete_confirm_message');?></p>
<?php echo form_open('tasks/delete'); ?>
<?php if (validation_errors()) : ?>
<h3>Whoops! There was an error:</h3>
<p><?php echo validation_errors(); ?></p>
<?php endif; ?>
<?php foreach ($query->result() as $row) : ?>
<?php echo $row->task_desc; ?>
<br /><br />
<?php echo form_submit('submit', $this->lang->line('common_form_elements_action_delete'), 'class="btn btn-success"'); ?>
or <?php echo anchor('tasks',$this->lang->line('common_form_elements_cancel'));?>
<?php echo form_hidden('id', $row->task_id); ?>
<?php endforeach; ?>
<?php echo form_close() ; ?>
上述代码块包含一个询问用户是否真的希望删除任务的表单。
创建 /path/to/codeigniter/application/views/nav/top_nav.php 文件,并向其中添加以下代码:
<!-- Fixed navbar -->
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<?php echo anchor('tasks', $this->lang->line('system_system_name'),'class="navbar-brand"') ; ?>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container theme-showcase" role="main">
这个视图非常基础,但仍然发挥着重要作用。它显示了一个返回到 tasks 控制器的 index() 函数的选项。
创建控制器
在这个项目中,我们只创建一个控制器,即 /path/to/codeigniter/application/controllers/tasks.php。
现在让我们来看看那个控制器,查看代码,并讨论其功能。
创建 /path/to/codeigniter/application/controllers/tasks.php 文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Tasks extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
$this->load->helper('text');
$this->load->model('Tasks_model');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
index()函数执行两项任务:显示任务列表和处理表单提交(验证、错误检查等)。
初始时,我们为表单设置验证规则,如下所示:
public function index() {
$this->form_validation->set_rules('task_desc', $this->lang->line('tasks_task_desc'), 'required|min_length[1]|max_length[255]');
$this->form_validation->set_rules('task_due_d', $this->lang->line('task_due_d'), 'min_length[1]|max_length[2]');
$this->form_validation->set_rules('task_due_m', $this->lang->line('task_due_m'), 'min_length[1]|max_length[2]');
$this->form_validation->set_rules('task_due_y', $this->lang->line('task_due_y'), 'min_length[4]|max_length[4]');
如果表单中存在错误或这是第一次访问页面,那么我们将构建表单元素,定义它们的设置并准备好在视图中绘制它们:
if ($this->form_validation->run() == FALSE) {
$page_data['job_title'] = array('name' => 'job_title', 'class' => 'form-control', 'id' => 'job_title', 'value' => set_value('job_title', ''), 'maxlength' => '100', 'size' => '35');
$page_data['task_desc'] = array('name' => 'task_desc', 'class' => 'form-control', 'id' => 'task_desc', 'value' => set_value('task_desc', ''), 'maxlength' => '255', 'size' => '35');
$page_data['task_due_d'] = array('name' => 'task_due_d', 'class' => 'form-control', 'id' => 'task_due_d', 'value' => set_value('task_due_d', ''), 'maxlength' => '100', 'size' => '35');
$page_data['task_due_m'] = array('name' => 'task_due_m', 'class' => 'form-control', 'id' => 'task_due_m', 'value' => set_value('task_due_m', ''), 'maxlength' => '100', 'size' => '35');
$page_data['task_due_y'] = array('name' => 'task_due_y', 'class' => 'form-control', 'id' => 'task_due_y', 'value' => set_value('task_due_y', ''), 'maxlength' => '100', 'size' => '35');
接下来,我们将从数据库中检索所有任务并将它们存储在$page_data['query']数组中。我们将此数组发送到tasks/view.php文件,在那里将使用foreach($query->result as $row)进行循环——每个任务将连同已完成、待办和删除选项一起以表格形式输出:
$page_data['query'] = $this->Tasks_model->get_tasks();
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('tasks/view', $page_data);
$this->load->view('common/footer');
} else {
如果表单没有错误,那么我们尝试在数据库中创建任务。首先,我们查看用户是否尝试为任务设置截止日期。我们通过在post数组中查找日期字段来完成此操作。
我们需要所有三个(日、月、年)项来创建截止日期,因此我们检查是否所有三项都已设置。如果所有三项都已设置,那么我们将构建一个字符串,该字符串将是日期。这被保存在$task_due_date变量中。如果所有三个日期项尚未设置(可能只有两个),那么我们只需将$task_due_date的值设置为null:
if ($this->input->post('task_due_y') && $this->input->post('task_due_m') && $this->input->post('task_due_d')) {
$task_due_date = $this->input->post('task_due_y') .'-'. $this->input->post('task_due_m') .'-'. $this->input->post('task_due_d');
} else {
$task_due_date = null;
}
然后,我们创建一个数组并将其传递给Tasks_model的save_task()函数。$save_data数组包含任务描述、可能已应用(或null值)的任何日期以及task_status的默认值;这最初设置为todo:
$save_data = array(
'task_desc' => $this->input->post('task_desc'),
'task_due_date' => $task_due_date,
'task_status' => 'todo'
);
然后,将$save_data数组发送到Tasks_model的save_task()函数。如果保存操作成功,该函数将返回true;如果发生错误,则返回false。无论结果如何,我们都会使用 CodeIgniter 的$this->session->set_flashdata()函数设置一条消息(成功消息或错误消息,这些消息的内容在语言文件中),并重定向到tasks控制器的index()函数,该函数将向用户显示所有任务(并希望显示刚刚创建的任务):
if ($this->Tasks_model->save_task($save_data)) {
$this->session->set_flashdata('flash_message', $this->lang->line('create_success_okay'));
} else {
$this->session->set_flashdata('flash_message', $this->lang->line('create_success_fail'));
}
redirect ('tasks');
}
}
status()函数用于将任务状态从done更改为todo。如果您将鼠标悬停在已完成或待办链接上,您将看到 URI。其格式看起来像http://www.domain.com/tasks/status/todo/1(如果任务在数据库中设置为done)或http://www.domain.com/tasks/status/done/1(如果任务在数据库中设置为todo)。第三个参数始终是任务当前状态的相反,因此如果任务设置为done,URI 将显示todo,如果设置为todo,URI 将显示done。
第四个参数是主键(在前面的示例中,这是1)。
当用户点击已完成或待办时,status()函数获取第三个和第四个参数并将它们发送到Tasks_model的status()函数:
public function status() {
$page_data['task_status'] = $this->uri->segment(3);
$task_id = $this->uri->segment(4);
我们取第三个和第四个参数并发送给Tasks_model的change_task_status()函数。如果更新成功,change_task_status()函数将返回true,如果有错误,则返回false。我们使用 CodeIgniter 的$this->session->set_flashdata()函数设置用户消息,并重定向到tasks控制器的index()函数:
if ($this->Tasks_model->change_task_status($task_id, $page_data)) {
$this->session->set_flashdata('flash_message', $this->lang->line('status_change_success'));
} else {
$this->session->set_flashdata('flash_message', $this->lang->line('status_change_fail'));
}
redirect ('tasks');
}
delete()函数做两件事。它向用户显示有关任务的详细信息,以便他们能够决定是否真的想删除任务,并且如果用户确认,它还会处理该任务的删除。首先,我们设置表单的验证规则。这是用户用来确认删除的表单:
public function delete() {
$this->form_validation->set_rules('id', $this->lang->line('task_id'), 'required|min_length[1]|max_length[11]|integer|is_natural');
由于该函数可以通过点击删除或提交表单被用户访问,因此任务 ID 可以来自 URI(在删除的情况下)或表单中的隐藏表单元素。因此,我们检查表单是否正在提交或首次访问,并从post或get中获取 ID:
if ($this->input->post()) {
$id = $this->input->post('id');
} else {
$id = $this->uri->segment(3);
}
$data['page_heading'] = 'Confirm delete?';
if ($this->form_validation->run() == FALSE) {
然后我们将 ID 发送到Tasks_model的get_task()函数,该函数将返回作为数据库对象的任务详情。这被保存在$data['query']中,并发送到tasks/delete.php视图文件,其中用户被要求确认他们是否真的想删除该任务:
$data['query'] = $this->Tasks_model->get_task($id);
$this->load->view('common/header', $data);
$this->load->view('nav/top_nav', $data);
$this->load->view('tasks/delete', $data);
$this->load->view('common/footer', $data);
} else {
如果表单提交没有错误,那么我们调用Tasks_model的delete()函数,以便删除任务:
if ($this->Tasks_model->delete($id)) {
redirect('tasks');
}
}
}
}
创建语言文件
与本书中的所有项目一样,我们正在使用语言文件为用户提供文本。这样,您可以启用多区域/多语言支持。让我们创建语言文件。
创建/path/to/codeigniter/application/language/english/en_admin_lang.php文件,并向其中添加以下代码:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "Todo";
// Tasks - view.php
$lang['tasks_add_task'] = "Add";
$lang['tasks_add_task_desc'] = "What have you got to do?";
$lang['tasks_task_desc'] = "Task Description";
$lang['tasks_set_done'] = "Mark as done";
$lang['tasks_set_todo'] = "Mark as todo";
$lang['task_due_d'] = "Due Day";
$lang['task_due_m'] = "Due Month";
$lang['task_due_y'] = "Due Year";
$lang['status_change_success'] = "Task updated";
$lang['status_change_fail'] = "Task not updated";
将所有内容组合在一起
好的,这里有一些示例,将有助于将所有内容组合在一起。
用户添加任务
添加任务的顺序如下:
-
用户访问网站,CodeIgniter 将其路由到
tasks控制器。 -
tasks控制器默认加载index()函数。index()函数检查表单验证是否为假:if ($this->form_validation->run() == FALSE) { ... -
由于这是第一次加载,表单尚未提交,因此它将等于
false。然后index()函数定义task_desc文本字段的设置,调用Tasks_model的get_tasks()函数(该函数从数据库返回所有任务),然后加载视图文件,将数据库对象传递给views/tasks/view.php文件。 -
用户输入
Chase meeting room booking字符串,选择未来三天的日期,然后点击添加提交表单。 -
表单已提交,
index()验证了task_desc表单元素和三个日期下拉框的值。验证现在已通过。三个日期字段被连接起来形成一个日期字符串,将其输入到数据库并保存为
$task_due_date:$task_due_date = $this->input->post('task_due_y') .'-'. $this->input->post('task_due_m') .'-'. $this->input->post('task_due_d'); -
这些
$task_due_date和task_desc值被保存到一个名为$save_data的数组中。同时保存的是tasks表中task_status字段的默认值。此值设置为todo。 -
在数据库成功保存操作后,用户被重定向到
index(),在那里显示他们的新任务。
用户更改任务状态
用户更改任务状态时执行的事件如下:
-
用户访问网站,CodeIgniter 将他们路由到
tasks控制器。 -
tasks控制器默认加载index()函数。index()函数检查表单验证是否为 false:if ($this->form_validation->run() == FALSE) { ... -
由于这是第一次加载且表单尚未提交,它将等于 false。然后
index()函数定义task_desc文本字段的设置,调用Tasks_model的get_tasks()函数(该函数从数据库返回所有任务),然后加载视图文件,将数据库对象传递给views/tasks/view.php文件。 -
用户看到任务“追逐会议室预订”并且(知道这个任务已完成)点击了已完成。
-
CodeIgniter 加载
status()任务函数。 -
status()函数获取 URI 的第三个参数(todo或done)和第四个参数(任务的唯一键)并将它们设置为$page_data['task_status']和$task_id局部变量。 -
这两个变量被发送到
Tasks_model的change_task_status()函数。 -
change_task_status()函数接受$task_id值和新状态,并对该任务执行 Active Record 更新,如果成功或发生错误则返回 true 或 false 值。 -
status()函数查看返回值并根据情况设置会话闪存消息:如果成功则显示成功消息,如果不成功则显示错误消息。 -
然后,用户被重定向到
index(),在那里他们可以看到更新的任务状态。
摘要
所以,这是一个相当小的应用程序——可能是在书中最小的一个——但它绝对不是没有用。您可以用这个待办事项列表作为管理您可能有的任何任务的简单方式;然而,总有改进的空间。您可以通过以下方式添加更多功能到项目中,包括以下内容:
-
添加排序功能:您可以添加排序函数,仅显示逾期(超期)、已完成或待办的任务。
-
添加分类:你可以在创建任务的表单中添加一个下拉菜单。这个下拉菜单可以(例如)包含红色、绿色、蓝色、黄色、橙色等颜色选项。一个任务可以被分配一个颜色,并且这个颜色可以在显示每个任务的表格中显示出来。你可以使用 Bootstrap 标签标记;例如,警告标签的
span(<span class="label label-warning">警告</span>)会给你一个很大的颜色块——不过,请改变一下“警告”这个词! -
添加进度和进度条:你可以在 HTML 下拉菜单中添加预设的百分比值:25%,50%,75%,100%等,这样你可以定义任务完成了多少。
第九章。创建职位发布板
现在有一些相当复杂的职位发布板,也有一些设计得非常糟糕。有些我想到的甚至根本不能像你想象的那样工作,有些则完全不能正常工作。我相信它们都有大量的风险投资资金,可能还赚了一些利润,所以我真不明白为什么它们不能齐心协力,做出真正能工作的事情;实际上,这并不是一件特别困难的事情。
在这个项目中的职位发布板虽然小而简洁,但仍有扩展的空间——如果你跳到总结部分,你会看到一些你可以添加的功能,使其更加丰富,但我相信你也有自己的想法。
在本章中,我们将涵盖以下主题:
-
设计和线框图
-
创建数据库
-
创建模型
-
创建视图
-
创建控制器
-
整合一切
简介
那么我们这个项目到底要做什么呢?我们将创建一个应用程序,允许用户创建将在“板”上显示的职位广告。用户可以搜索特定术语,并将返回一些结果。
其他用户可以创建将在这些板上显示的广告。
为了创建这个应用程序,我们将创建一个控制器;这将处理职位的显示、新职位的创建以及申请职位。
我们将创建一个语言文件来存储文本,以便在需要时提供多语言支持。
我们将创建所有必要的视图文件和一个与数据库交互的模型。
然而,这个应用程序以及本书中的其他应用程序都依赖于我们在第一章中完成的基本设置,简介和共享项目资源;尽管你可以将大量代码提取出来并放入你几乎已经拥有的任何应用程序中,但请记住,第一章中完成的基本设置是这个章节的基础。
因此,我们不再拖延,让我们开始吧。
设计和线框图
和往常一样,在我们开始构建之前,我们应该看看我们打算构建什么。
首先,简要描述我们的意图:我们计划构建一个应用程序,让人们能够以职位发布板的形式浏览职位广告。
人们将能够创建将在搜索列表中出现的职位广告。其他人将被允许申请这些广告中的职位,申请将连同工作详情和申请者信息以电子邮件的形式发送给广告商。
总之,为了更好地了解正在发生的事情,让我们看一下以下网站地图:

所以这就是网站地图;首先要注意的是网站有多简单。这个项目只有三个主要区域。让我们逐一了解每个项目,并简要了解它们的功能:
-
Jobs/Search: 想象这是一个起点。用户将看到网站上可用的活跃职位的列表。用户可以查看职位详情并申请(带他们到网站地图的2点),或者点击导航栏上的创建并转到网站地图的3点(创建项)。
-
Detail/Apply: 用户将看到招聘广告的详细信息,例如开始日期、地点和工作描述,以及广告商的联系方式。在职位详情下方还有一个表单,允许用户申请该职位。申请的详细信息将通过电子邮件发送给招聘广告商(
jobs.job_advertiser_email)。 -
Create: 这将向用户显示一个表单,允许他们创建一个招聘广告。一旦广告创建,它将在搜索列表中显示。
现在我们对网站的结构和形式有了相当好的了解,让我们来看看每个页面的线框图。
Job/Search
以下截图显示了网站地图中1点(Create 项)的线框图。最初,用户会看到一个当前职位的列表。显示职位标题和描述。描述保持固定长度——即职位描述的前 50 个单词。他们可以点击职位标题或申请链接,转到网站地图的2点(Detail/Apply 项)。

Detail/Apply
以下截图显示了网站地图中2点(Detail/Apply 项)的线框图。用户可以看到招聘广告的详细描述和一个允许用户输入他们的详细信息并发送职位申请的表单——这个申请的详细信息将通过电子邮件发送给招聘广告商。

Create
以下截图显示了网站地图中3点(Create 项)的线框图。任何用户都可以发布招聘广告。这向用户显示一个表单,允许他们输入他们的招聘广告详情并将其保存到数据库中。

文件概述
这是一个相对较小的项目,总的来说,我们只需要创建七个文件;这些文件如下:
-
/path/to/codeigniter/application/models/jobs_model.php: 这提供了对jobs数据库表的读写访问。 -
/path/to/codeigniter/application/views/jobs/apply.php: 这为我们提供了一个界面,允许用户查看招聘广告的详细信息,以及一个允许任何用户申请职位的表单。 -
/path/to/codeigniter/application/views/jobs/create.php: 这向用户显示一个表单,允许他们创建一个招聘广告。 -
/path/to/codeigniter/application/views/jobs/view.php: 这是jobs控制器index()函数的视图。它显示搜索表单并列出任何结果。 -
/path/to/codeigniter/application/views/nav/top_nav.php: 这在页面顶部提供了一个导航栏。 -
/path/to/codeigniter/application/controllers/jobs.php:这包含三个主要函数:index()、apply()和create()。 -
/path/to/codeigniter/application/language/english/en_admin_lang.php:这为应用程序提供语言支持。
前面七个文件的结构如下:
application/
├── controllers/
│ ├── jobs.php
├── models/
│ ├── jobs_model.php
├── views/create/
│ ├── create.php
│ ├── apply.php
│ ├── view.php
├── views/nav/
│ ├── top_nav.php
├── language/english/
│ ├── en_admin_lang.php
创建数据库
好吧,你应该已经按照第一章中描述的方式设置了 CodeIgniter 和 Bootstrap;如果没有,那么你应该知道本章中的代码是专门针对第一章中的设置编写的。但是,如果你没有这样做,也没有关系——代码可以轻松地应用于其他情况。
首先,我们将构建数据库。将以下 MySQL 代码复制到你的数据库中:
CREATE DATABASE `jobboarddb`;
USE `jobboarddb`;
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories` (
`cat_id` int(11) NOT NULL AUTO_INCREMENT,
`cat_name` varchar(25) NOT NULL,
PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `categories` VALUES (1,'IT'),(2,'Legal'),(3,'Management'),(4,'Purchasing');
DROP TABLE IF EXISTS `ci_sessions`;
CREATE TABLE `ci_sessions` (
`session_id` varchar(40) COLLATE utf8_bin NOT NULL DEFAULT '0',
`ip_address` varchar(16) COLLATE utf8_bin NOT NULL DEFAULT '0',
`user_agent` varchar(120) COLLATE utf8_bin DEFAULT NULL,
`last_activity` int(10) unsigned NOT NULL DEFAULT '0',
`user_data` text COLLATE utf8_bin NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
DROP TABLE IF EXISTS `jobs`;
CREATE TABLE `jobs` (
`job_id` int(11) NOT NULL AUTO_INCREMENT,
`job_title` varchar(50) NOT NULL,
`job_desc` text NOT NULL,
`cat_id` int(11) NOT NULL,
`type_id` int(11) NOT NULL,
`loc_id` int(11) NOT NULL,
`job_start_date` datetime NOT NULL,
`job_rate` int(5) NOT NULL,
`job_advertiser_name` varchar(50) NOT NULL,
`job_advertiser_email` varchar(50) NOT NULL,
`job_advertiser_phone` varchar(20) NOT NULL,
`job_sunset_date` datetime NOT NULL,
`job_created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`job_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `jobs` VALUES (1,'PHP Developer','PHP Developer required for a large agency based in London. Must have MVC experience\n',1,1,1,'2014-09-24 00:00:00',400,'Rob Foster','rob@bluesuncreative.com','01234123456','2015-09-26 00:00:00','2014-09-17 09:00:18'),(2,'CodeIgniter Developer','Small London agency urgently requires a CodeIgniter developer to work on small eCommerce project.',1,1,1,'0000-00-00 00:00:00',350,'Lucy','lucy@londonagencycomain.com','01234123456','2015-09-26 00:00:00','2014-09-17 11:22:19'),(3,'Flash Developer','Paris based agency requires Flash Developer to work on new built project',1,1,2,'0000-00-00 00:00:00',350,'Brian','brian@frenchdesignagenct.fr','01234123456','2015-09-26 00:00:00','2014-09-17 11:23:39');
DROP TABLE IF EXISTS `locations`;
CREATE TABLE `locations` (
`loc_id` int(11) NOT NULL AUTO_INCREMENT,
`loc_name` varchar(25) NOT NULL,
PRIMARY KEY (`loc_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `locations` VALUES (1,'England'),(2,'France'),(3,'Germany'),(4,'Spain');
DROP TABLE IF EXISTS `types`;
CREATE TABLE `types` (
`type_id` int(11) NOT NULL AUTO_INCREMENT,
`type_name` varchar(25) NOT NULL,
PRIMARY KEY (`type_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `types` VALUES (1,'Contract'),(2,'Full Time'),(3,'Part Time');
提示
现在,看看最后那段 SQL 代码,它相当大且复杂。别慌,所有 SQL 代码都可以在 Packt 网站上的本书支持页面上找到。
你会看到我们创建的第一个表是ci_sessions。我们需要这个表来允许 CodeIgniter 管理会话,特别是登录用户。然而,这只是从CodeIgniter 用户指南中可用的标准会话表,因此我不会包括该表的描述,因为它不是技术特定于这个应用程序的。但是,如果你感兴趣,可以在ellislab.com/codeigniter/user-guide/libraries/sessions.html找到描述。
好的,让我们逐个查看每个表中的每个条目,看看它们代表什么。首先,我们将查看categories表:
| 表:categories |
|---|
| 元素 |
cat_id |
cat_name |
接下来,我们将查看types表:
| 表:types |
|---|
| 元素 |
type_id |
type_name |
现在,让我们看看locations表:
| 表:locations |
|---|
| 元素 |
loc_id |
loc_name |
最后,我们将查看jobs表:
| 表:jobs |
|---|
| 元素 |
job_id |
job_title |
job_desc |
cat_id |
type_id |
loc_id |
job_start_date |
job_rate |
job_advertiser_name |
job_advertiser_email |
job_advertiser_phone |
job_sunset_date |
job_created_at |
我们还需要对config/database.php文件进行修改,即设置数据库访问详情、用户名、密码等。
打开config/database.php文件并找到以下行:
$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'your username';
$db['default']['password'] = 'your password';
$db['default']['database'] = 'jobboarddb';
编辑上一行中的值,确保用更具体于你的设置和情况的值替换这些值;因此,输入你的用户名、密码等。
调整config.php文件
在这个文件中有几件事情我们需要配置以支持会话和加密。因此,打开config/config.php文件并做出以下更改。
-
我们需要设置一个加密密钥;会话和 CodeIgniter 的加密功能都需要在
$config数组中设置一个加密密钥,所以找到以下行:$config['encryption_key'] = '';更改为以下内容:
$config['encryption_key'] = 'a-random-string-of-alphanum-characters';提示
现在显然,实际上不要将这个值直接改为一个随机的字母数字字符序列,而是改为,嗯,一个随机的字母数字字符序列——如果这样理解的话?是的,你知道我的意思。
-
找到这些行:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = FALSE; $config['sess_encrypt_cookie'] = FALSE; $config['sess_use_database'] = FALSE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;将它们更改为以下内容:
$config['sess_cookie_name'] = 'ci_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = TRUE; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300;
调整routes.php文件
我们希望将用户重定向到jobs控制器,而不是默认的 CodeIgniter welcome控制器。为此,我们需要修改routes.php文件中的默认控制器设置:
-
打开
config/routes.php文件进行编辑,并找到以下行(文件底部附近):$route['default_controller'] = "welcome"; $route['404_override'] = ''; -
首先,我们需要更改默认控制器。最初在一个 CodeIgniter 应用程序中,默认控制器设置为
welcome;然而,我们不需要这个——相反,我们希望默认控制器是jobs。所以,找到以下行:$route['default_controller'] = "welcome";用以下内容替换:
$route['default_controller'] = "jobs"; $route['404_override'] = '';
创建模型
在此项目中只有一个模型——jobs_model.php——它包含特定于搜索和将职位广告写入数据库的函数。
这是此项目的唯一模型,因此让我们创建模型并讨论其功能。
创建/path/to/codeigniter/application/models/jobs_model.php文件,并将其中的以下代码添加到该文件中:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Jobs_model extends CI_Model {
function __construct() {
parent::__construct();
}
function get_jobs($search_string) {
if ($search_string == null) {
$query = "SELECT * FROM `jobs` WHERE DATE(NOW()) < DATE(`job_sunset_date`) ";
} else {
$query = "SELECT * FROM `jobs` WHERE `job_title` LIKE '%$search_string%'
OR `job_desc` LIKE '%$search_string%' AND DATE(NOW()) < DATE(`job_sunset_date`)";
}
$result = $this->db->query($query);
if ($result) {
return $result;
} else {
return false;
}
}
function get_job($job_id) {
$query = "SELECT * FROM `jobs`, `categories`, `types`, `locations` WHERE
`categories`.`cat_id` = `jobs`.`cat_id` AND
`types`.`type_id` = `jobs`.`type_id` AND
`locations`.`loc_id` = `jobs`.`loc_id` AND
`job_id` = ? AND
DATE(NOW()) < DATE(`job_sunset_date`) ";
$result = $this->db->query($query, array($job_id));
if ($result) {
return $result;
} else {
return flase;
}
}
function save_job($save_data) {
if ($this->db->insert('jobs', $save_data)) {
return $this->db->insert_id();
} else {
return false;
}
}
function get_categories() {
return $this->db->get('categories');
}
function get_types() {
return $this->db->get('types');
}
function get_locations() {
return $this->db->get('locations');
}
}
此模型中有六个主要函数,具体如下:
-
get_jobs(): 此函数有两个作用:首先,显示所有职位——例如,当用户首次访问网站时——其次,当用户输入搜索词时,查询将更改以在job_title和job_desc中查找特定的搜索词。 -
get_job(): 此函数获取特定职位广告的详细信息,用于网站地图的2(详情/申请)项。 -
save_job(): 当用户从网站地图的3(创建项目)处提交表单时,此函数会将职位广告保存到数据库中。 -
get_categories(): 此函数从categories表获取类别。它用于在创建过程中填充类别下拉菜单。 -
get_types(): 此函数从types表获取类型。它用于在创建过程中填充类型下拉菜单。 -
get_locations(): 此函数从locations表获取位置。它用于在创建过程中填充位置下拉菜单。
首先考虑get_jobs()函数,如前所述,此函数有两个用途:
-
要返回所有结果,即列出所有职位
-
返回与用户搜索匹配的结果
当用户访问网站时,他们会被路由到jobs/index。这将导致get_jobs()模型函数搜索数据库。在这次初次访问中,$search_string变量将是空的(因为用户没有搜索任何内容)。这将导致if语句的第一部分执行,基本上返回所有有效的职位。
然而,如果用户正在搜索某些内容,那么$search_string变量将不会为空;它将包含用户在views/jobs/view.php表单中输入的搜索词。
这将导致if语句的第二部分执行,将$search_term添加到数据库查询中:
function get_jobs($search_string) {
if ($search_string == null) {
$query = "SELECT * FROM `jobs` WHERE DATE(NOW()) < DATE(`job_sunset_date`) ";
} else {
$query = "SELECT * FROM `jobs` WHERE `job_title` LIKE '%$search_string%'
OR `job_desc` LIKE '%$search_string%' AND DATE(NOW()) < DATE(`job_sunset_date`)";
}
$result = $this->db->query($query);
if ($result) {
return $result;
} else {
return false;
}
}
这两个查询都只会返回日落日期尚未过去的查询结果。jobboarddb.job_sunset_date字段包含一个日期,即职位广告将停止在搜索词中显示。
接下来,我们将查看get_job()函数。此函数接收来自jobs控制器的$job_id值。当用户在views/jobs/view.php中点击申请链接时,jobs控制器从$this->uri->segment(3)获取职位广告的 ID。
get_job()函数简单地返回网站地图2(详情/申请)项的所有数据。
它将categories、types和locations表连接到jobs表,以确保在views/jobs/apply.php视图中显示正确的类别、类型和位置,以及特定的职位广告详情。
然后我们继续到 save_job() 函数。该函数接受来自 jobs 控制器的一个数据数组。jobs 控制器的 create() 函数将 $save_data 数组发送到 save_job() 模型函数。$save_data 数组包含 views/jobs/create.php 视图文件中的表单输入。
在成功保存后,返回插入的主键。
现在我们将同时介绍三个函数——_categories()、get_types() 和 get_locations()(因为它们执行的功能相当相似)。这三个函数从各自的表中获取所有类别、类型和位置。这些函数由 jobs 控制器的 create() 函数调用,以确保下拉列表填充了正确的数据。
创建视图
在此项目中,有四种视图,具体如下:
-
/path/to/codeigniter/application/views/jobs/view.php:此视图向用户显示当前工作的列表。 -
/path/to/codeigniter/application/views/jobs/create.php:此视图允许工作发布者输入工作广告详情。表单提交到jobs控制器的create()函数。 -
/path/to/codeigniter/application/views/jobs/apply.php:此视图向用户显示一个表单,允许他们输入申请工作的信息。它还显示验证错误。 -
/path/to/codeigniter/application/views/nav/top_nav.php:此视图显示顶级菜单。在此项目中,这非常简单,因为它包含项目名称和链接到jobs控制器。
这些是我们的四个视图文件。现在,让我们逐一过目,构建代码,并讨论其功能。
创建 /path/to/codeigniter/application/views/jobs/view.php 文件,并向其中添加以下代码:
<div class="page-header">
<h1>
<?php echo form_open('jobs/index') ; ?>
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input type="text" class="form-control" name="search_string" placeholder="<?php echo $this->lang->line('jobs_view_search'); ?>">
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><?php echo $this->lang->line('jobs_view_search'); ?></button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div><!-- /.row -->
<?php echo form_close() ; ?>
</h1>
</div>
<table class="table table-hover">
<?php foreach ($query->result() as $row) : ?>
<tr>
<td><?php echo anchor ('jobs/apply/'.$row->job_id, $row->job_title) ; ?><br /><?php echo word_limiter($row->job_desc, 50) ; ?>
</td>
<td>Posten on <?php echo $row->job_created_at ; ?><br />Rate is £<?php echo $row->job_rate ; ?>
</td>
<td><?php echo anchor ('jobs/apply/'.$row->job_id, $this->lang->line('jobs_view_apply')) ; ?>
</td>
</tr>
<?php endforeach ; ?>
</table>
此视图有两个功能:
-
在页面顶部显示一个简单的搜索表单。这是用户可以搜索匹配搜索词的工作的地方。
-
在网页顶部显示工作列表的 HTML 表格。这些是数据库中的当前活动工作。如果工作的日落日期(
jobs.job_sunset_date)尚未过去,则认为该工作处于活动状态。
搜索表单提交到 jobs 控制器的 index() 函数——此控制器函数将搜索词传递给 Jobs_model 的 get_jobs($search_term) 函数。它将被添加到数据库查询中;此查询将在 jobs.job_title 和 jobs.job_desc 中查找匹配该词的文本。
创建 /path/to/codeigniter/application/views/jobs/create.php 文件,并向其中添加以下代码:
<?php if ($this->session->flashdata('flash_message')) : ?>
<div class="alert alert-info" role="alert"><?php echo $this->session->flashdata('flash_message');?></div>
<?php endif ; ?>
<p class="lead"><?php echo $this->lang->line('job_create_form_instruction_1');?></p>
<div class="span8">
<?php echo form_open('jobs/create','role="form" class="form"') ; ?>
<div class="form-group">
<?php echo form_error('job_title'); ?>
<label for="job_title"><?php echo $this->lang->line('job_title');?></label>
<?php echo form_input($job_title); ?>
</div>
<div class="form-group">
<?php echo form_error('job_desc'); ?>
<label for="job_desc"><?php echo $this->lang->line('job_desc');?></label>
<?php echo form_textarea($job_desc); ?>
</div>
类型下拉列表由 Jobs_model 中的 get_types() 函数填充。它返回一个结果对象,我们可以遍历它,使用户能够选择类型:
<div class="form-group">
<?php echo form_error('type_id'); ?>
<label for="type_id"><?php echo $this->lang->line('type');?></label>
<select name="type_id" class="form-control">
<?php foreach ($types->result() as $row) : ?>
<option value="<?php echo $row->type_id ; ?>"><?php echo $row->type_name ; ?></option>
<?php endforeach ; ?>
</select>
</div>
类别下拉列表由 Jobs_model 中的 get_categories() 函数填充。它返回一个结果对象,我们可以遍历它,使用户能够选择类别:
<div class="form-group">
<?php echo form_error('cat_id'); ?>
<label for="cat_id"><?php echo $this->lang->line('cat');?></label>
<select name="cat_id" class="form-control">
<?php foreach ($categories->result() as $row) : ?>
<option value="<?php echo $row->cat_id ; ?>"><?php echo $row->cat_name ; ?></option>
<?php endforeach ; ?>
</select>
</div>
位置下拉菜单由Jobs_model中的get_locations()函数填充。它返回一个结果对象,我们遍历它,允许用户选择位置:
<div class="form-group">
<?php echo form_error('loc_id'); ?>
<label for="loc_id"><?php echo $this->lang->line('loc');?></label>
<select name="loc_id" class="form-control">
<?php foreach ($locations->result() as $row) : ?>
<option value="<?php echo $row->loc_id ; ?>"><?php echo $row->loc_name ; ?></option>
<?php endforeach ; ?>
</select>
</div>
<label for="sunset_d"><?php echo $this->lang->line('job_start_date');?></label>
<div class="row">
<div class="form-group">
<div class="col-md-2">
<?php echo form_error('startd'); ?>
<select name="startd" class="form-control">
<?php for ( $i = 1; $i <= 30; $i++) : ?>
<?php if (date('j', time()) == $i) : ?>
<option selected value="<?php echo $i ; ?>"><?php echo date('jS', mktime($i,0,0,0, $i, date('Y'))) ; ?></option>
<?php else : ?>
<option value="<?php echo $i ; ?>"><?php echo date('jS', mktime($i,0,0,0, $i, date('Y'))) ; ?></option>
<?php endif ; ?>
<?php endfor ; ?>
</select>
</div>
<div class="col-md-2">
<?php echo form_error('startm'); ?>
<select name="startm" class="form-control">
<?php for ( $i = 1; $i <= 12; $i++) : ?>
<?php if (date('m', time()) == $i) : ?>
<option selected value="<?php echo $i ; ?>"><?php echo date('F', mktime(0,0,0,$i, 1, date('Y'))) ; ?></option>
<?php else : ?>
<option value="<?php echo $i ; ?>"><?php echo date('F', mktime(0,0,0,$i, 1, date('Y'))) ; ?></option>
<?php endif ; ?>
<?php endfor ; ?>
</select>
</div>
<div class="col-md-2">
<?php echo form_error('starty'); ?>
<select name="starty" class="form-control">
<?php for ($i = date("Y",strtotime(date("Y"))); $i <= date("Y",strtotime(date("Y").' +3 year')); $i++) : ?>
<option value="<?php echo $i;?>"><?php echo $i;?></option>
<?php endfor ; ?>
</select>
</div>
</div>
</div>
<div class="form-group">
<?php echo form_error('job_rate'); ?>
<label for="job_rate"><?php echo $this->lang->line('job_rate');?></label>
<?php echo form_input($job_rate); ?>
</div>
<div class="form-group">
<?php echo form_error('job_advertiser_name'); ?>
<label for="job_advertiser_name"><?php echo $this->lang->line('job_advertiser_name');?></label>
<?php echo form_input($job_advertiser_name); ?>
</div>
<div class="form-group">
<?php echo form_error('job_advertiser_email'); ?>
<label for="job_advertiser_email"><?php echo $this->lang->line('job_advertiser_email');?></label>
<?php echo form_input($job_advertiser_email); ?>
</div>
<div class="form-group">
<?php echo form_error('job_advertiser_phone'); ?>
<label for="job_advertiser_phone"><?php echo $this->lang->line('job_advertiser_phone');?></label>
<?php echo form_input($job_advertiser_phone); ?>
</div>
<label for="sunset_d"><?php echo $this->lang->line('job_sunset_date');?></label>
<div class="row">
<div class="form-group">
<div class="col-md-2">
<?php echo form_error('sunset_d'); ?>
<select name="sunset_d" class="form-control">
<?php for ( $i = 1; $i <= 30; $i++) : ?>
<?php if (date('j', time()) == $i) : ?>
<option selected value="<?php echo $i ; ?>"><?php echo date('jS', mktime($i,0,0,0, $i, date('Y'))) ; ?></option>
<?php else : ?>
<option value="<?php echo $i ; ?>"><?php echo date('jS', mktime($i,0,0,0, $i, date('Y'))) ; ?></option>
<?php endif ; ?>
<?php endfor ; ?>
</select>
</div>
<div class="col-md-2">
<?php echo form_error('sunset_m'); ?>
<select name="sunset_m" class="form-control">
<?php for ( $i = 1; $i <= 12; $i++) : ?>
<?php if (date('m', time()) == $i) : ?>
<option selected value="<?php echo $i ; ?>"><?php echo date('F', mktime(0,0,0,$i, 1, date('Y'))) ; ?></option>
<?php else : ?>
<option value="<?php echo $i ; ?>"><?php echo date('F', mktime(0,0,0,$i, 1, date('Y'))) ; ?></option>
<?php endif ; ?>
<?php endfor ; ?>
</select>
</div>
<div class="col-md-2">
<?php echo form_error('sunset_y'); ?>
<select name="sunset_y" class="form-control">
<?php for ($i = date("Y",strtotime(date("Y"))); $i <= date("Y",strtotime(date("Y").' +3 year')); $i++) : ?>
<option value="<?php echo $i;?>"><?php echo $i;?></option>
<?php endfor ; ?>
</select>
</div>
</div>
</div>
<span class="help-block"><?php echo $this->lang->line('job_sunset_date_help') ; ?></div>
<div class="form-group">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button> or <? echo anchor('jobs',$this->lang->line('common_form_elements_cancel'));?>
</div>
<?php echo form_close() ; ?>
</div>
</div>
与验证过程相关的任何错误消息——例如缺少的必填表单字段——也会在这个视图文件中显示在表单字段旁边,触发错误。为此,我们使用 CodeIgniter 的form_error()验证函数。
创建/path/to/codeigniter/application/views/jobs/apply.php文件,并将以下代码添加到其中:
<?php if ($this->session->flashdata('flash_message')) : ?>
<div class="alert alert-info" role="alert"><?php echo $this->session->flashdata('flash_message');?></div>
<?php endif ; ?>
<div class="row">
<div class="col-sm-12 blog-main">
<div class="blog-post">
<?php foreach ($query->result() as $row) : ?>
<h2 class="blog-post-title"><?php echo $row->job_title ; ?></h2>
<p class="blog-post-meta">Posted by <?php echo $row->job_advertiser_name . ' on ' . $row->job_created_at ; ?></p>
<table class="table">
<tr>
<td>Start Date
</td>
<td><?php echo $row->job_start_date ; ?>
</td>
<td>Contact Name
</td>
<td><?php echo $row->job_advertiser_name ; ?>
</td>
</tr>
<tr>
<td>Location
</td>
<td><?php echo $row->loc_name ; ?>
</td>
<td>Contact Phone
</td>
<td><?php echo $row->job_advertiser_phone ; ?>
</td>
</tr>
<tr>
<td>Type
</td>
<td><?php echo $row->type_name ; ?>
</td>
<td>Contact Email
</td>
<td><?php echo $row->job_advertiser_email ; ?>
</td>
</tr>
</table>
<p><?php echo $row->job_desc ; ?></p>
<?php endforeach ; ?>
</div>
</div>
</div>
<p class="lead"><?php echo $this->lang->line('apply_instruction_1') . $job_title ;?></p>
<div class="span12">
<?php echo form_open('jobs/apply','role="form" class="form"') ; ?>
<div class="form-group">
<?php echo form_error('app_name'); ?>
<label for="app_name"><?php echo $this->lang->line('app_name');?></label>
<?php echo form_input($app_name); ?>
</div>
<div class="form-group">
<?php echo form_error('app_email'); ?>
<label for="app_email"><?php echo $this->lang->line('app_email');?></label>
<?php echo form_input($app_email); ?>
</div>
<div class="form-group">
<?php echo form_error('app_phone'); ?>
<label for="app_phone"><?php echo $this->lang->line('app_phone');?></label>
<?php echo form_input($app_phone); ?>
</div>
<div class="form-group">
<?php echo form_error('app_cover_note'); ?>
<label for="app_cover_note"><?php echo $this->lang->line('app_cover_note');?></label>
<?php echo form_textarea($app_cover_note); ?>
</div>
<input type="hidden" name="job_id" value="<?php echo $this->uri->segment(3) ; ?>" />
<div class="form-group">
<button type="submit" class="btn btn-success"><?php echo $this->lang->line('common_form_elements_go');?></button> or <? echo anchor('jobs',$this->lang->line('common_form_elements_cancel'));?>
</div>
<?php echo form_close() ; ?>
</div>
</div>
看一下视图文件顶部,特别是foreach($query->result() as $row)循环中的代码,该循环显示职位的详细信息。它被组织成一个 HTML 表格,清楚地分隔了职位广告的主要点,如开始日期、工作地点和联系详情。唯一不在表格中的是职位描述。
在foreach()循环下方有一个 HTML 表单,允许用户输入他们的联系详情和一小段说明他们对该职位兴趣的封面信。当用户点击Go时,表单将被提交。
有一个名为job_id的隐藏字段元素,其外观如下:
<input type="hidden" name="job_id" value="<?php echo $this->uri->segment(3) ; ?>" />
这个包含职位广告 ID 的隐藏字段确保当表单提交时,jobs/apply() 函数可以使用正确的 ID 查询数据库,并获取与职位关联的正确电子邮件地址(jobs.job_advertiser_email),然后使用 PHP 的mail()函数,将包含申请者详情的电子邮件发送给职位发布者。
创建/path/to/codeigniter/application/views/nav/top_nav.php文件,并将以下代码添加到其中:
<!-- Fixed navbar -->
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<?php echo base_url() ; ?>"><?php echo $this->lang->line('system_system_name'); ?></a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><?php echo anchor('jobs/create', 'Create') ; ?></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container theme-showcase" role="main">
这个视图相当基础,但仍然发挥着重要作用。它显示了一个返回到jobs控制器index()函数的选项。
创建控制器
在这个项目中,我们将只创建一个控制器,即/path/to/codeigniter/application/controllers/jobs.php。
在这个项目中只有一个控制器,现在让我们来看看它。我们将查看代码并讨论其功能。
在这个控制器中有三个主要功能,如下所示:
-
index(): 这将向用户显示初始的职位广告列表。它还显示搜索框并显示可能返回的任何结果。 -
create(): 这将向任何用户显示一个表单,允许用户创建一个职位广告。 -
apply(): 如果用户点击Apply按钮或职位标题,则会访问此功能。
创建/path/to/codeigniter/application/controllers/jobs.php文件,并将以下代码添加到其中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Jobs extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
$this->load->helper('text');
$this->load->model('Jobs_model');
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>');
}
首先查看index(),你会看到这个函数首先做的事情之一是调用Jobs_model的get_jobs()函数,并将搜索字符串传递给它。如果用户在搜索框中没有输入搜索字符串,那么这个 POST 数组项将是空的,但那没关系,因为我们已经在模型中测试了它。
此查询的结果存储在$page_data['query']中,准备好传递给views/jobs/view.php文件,其中foreach()循环将显示每个工作广告:
public function index() {
$this->form_validation->set_rules('search_string', $this->lang->line('search_string'), 'required|min_length[1]|max_length[125]');
$page_data['query'] = $this->Jobs_model->get_jobs($this->input->post('search_string'));
我们为search_string设置验证规则。如果是第一次查看页面或验证失败,则$this->form_validation()将返回一个假值:
if ($this->form_validation->run() == FALSE) {
$page_data['search_string'] = array('name' => 'search_string', 'class' => 'form-control', 'id' => 'search_string', 'value' => set_value('search_string', $this->input->post('search_string')), 'maxlength' => '100', 'size' => '35');
为了向用户显示工作列表,我们调用Jobs_model的get_jobs()函数,将其传递给用户输入的任何搜索字符串,并将数据库结果对象存储在$page_data数组的项目查询中。我们将$page_data数组传递给views/jobs/view.php文件:
$page_data['query'] = $this->Jobs_model->get_jobs($this->input->post('search_string'));
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('jobs/view', $page_data);
$this->load->view('common/footer');
} else {
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('jobs/view', $page_data);
$this->load->view('common/footer');
}
}
create()函数稍微复杂一些;最初,我们设置表单验证规则——那里没有什么真正有趣的东西可以看——但紧接着,我们调用三个模型函数:get_categories()、get_types()和get_locations(),其结果存储在各自的$save_data数组项中,如下所示:
$page_data['categories'] = $this->Jobs_model->get_categories();
$page_data['types'] = $this->Jobs_model->get_types();
$page_data['locations'] = $this->Jobs_model->get_locations();
我们将在view/jobs/create.php文件中遍历这些结果,并填充 HTML 选择下拉菜单。
无论如何,在此之后,我们检查表单是否已提交,如果是的话,是否提交时存在错误。我们构建表单元素,指定每个元素的设置,并将它们发送到$page_data数组中,以供views/jobs/create.php视图使用。
如果表单提交后没有错误,我们将打包所有帖子输入并发送到Jobs_model的save_job()函数:
如果保存操作成功,我们将设置成功消息的闪存数据,向用户表明他们的工作已被保存,以便他们知道它现在将出现在搜索结果中。然而,如果它没有成功,我们将返回一个错误消息:
public function create() {
$this->form_validation->set_rules('job_title', $this->lang->line('job_title'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('job_desc', $this->lang->line('job_desc'), 'required|min_length[1]|max_length[3000]');
$this->form_validation->set_rules('cat_id', $this->lang->line('cat_id'), 'required|min_length[1]|max_length[11]');
$this->form_validation->set_rules('type_id', $this->lang->line('type_id'), 'required|min_length[1]|max_length[11]');
$this->form_validation->set_rules('loc_id', $this->lang->line('loc_id'), 'required|min_length[1]|max_length[11]');
$this->form_validation->set_rules('start_d', $this->lang->line('start_d'), 'min_length[1]|max_length[2]');
$this->form_validation->set_rules('start_m', $this->lang->line('start_m'), 'min_length[1]|max_length[2]');
$this->form_validation->set_rules('start_y', $this->lang->line('start_y'), 'min_length[1]|max_length[4]');
$this->form_validation->set_rules('job_rate', $this->lang->line('job_rate'), 'required|min_length[1]|max_length[6]');
$this->form_validation->set_rules('job_advertiser_name', $this->lang->line('job_advertiser_name'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('job_advertiser_email', $this->lang->line('job_advertiser_email'), 'min_length[1]|max_length[125]');
$this->form_validation->set_rules('job_advertiser_phone', $this->lang->line('job_advertiser_phone'), 'min_length[1]|max_length[125]');
$this->form_validation->set_rules('sunset_d', $this->lang->line('sunset_d'), 'min_length[1]|max_length[2]');
$this->form_validation->set_rules('sunset_m', $this->lang->line('sunset_m'), 'min_length[1]|max_length[2]');
$this->form_validation->set_rules('sunset_y', $this->lang->line('sunset_y'), 'min_length[1]|max_length[4]');
$page_data['categories'] = $this->Jobs_model->get_categories();
$page_data['types'] = $this->Jobs_model->get_types();
$page_data['locations'] = $this->Jobs_model->get_locations();
if ($this->form_validation->run() == FALSE) {
$page_data['job_title'] = array('name' => 'job_title', 'class' => 'form-control', 'id' => 'job_title', 'value' => set_value('job_title', ''), 'maxlength' => '100', 'size' => '35');
$page_data['job_desc'] = array('name' => 'job_desc', 'class' => 'form-control', 'id' => 'job_desc', 'value' => set_value('job_desc', ''), 'maxlength' => '3000', 'rows' => '6', 'cols' => '35');
$page_data['start_d'] = array('name' => 'start_d', 'class' => 'form-control', 'id' => 'start_d', 'value' => set_value('start_d', ''), 'maxlength' => '100', 'size' => '35');
$page_data['start_m'] = array('name' => 'start_m', 'class' => 'form-control', 'id' => 'start_m', 'value' => set_value('start_m', ''), 'maxlength' => '100', 'size' => '35');
$page_data['start_y'] = array('name' => 'start_y', 'class' => 'form-control', 'id' => 'start_y', 'value' => set_value('start_y', ''), 'maxlength' => '100', 'size' => '35');
$page_data['job_rate'] = array('name' => 'job_rate', 'class' => 'form-control', 'id' => 'job_rate', 'value' => set_value('job_rate', ''), 'maxlength' => '100', 'size' => '35');
$page_data['job_advertiser_name'] = array('name' => 'job_advertiser_name', 'class' => 'form-control', 'id' => 'job_advertiser_name', 'value' => set_value('job_advertiser_name', ''), 'maxlength' => '100', 'size' => '35');
$page_data['job_advertiser_email'] = array('name' => 'job_advertiser_email', 'class' => 'form-control', 'id' => 'job_advertiser_email', 'value' => set_value('job_advertiser_email', ''), 'maxlength' => '100', 'size' => '35');
$page_data['job_advertiser_phone'] = array('name' => 'job_advertiser_phone', 'class' => 'form-control', 'id' => 'job_advertiser_phone', 'value' => set_value('job_advertiser_phone', ''), 'maxlength' => '100', 'size' => '35');
$page_data['sunset_d'] = array('name' => 'sunset_d', 'class' => 'form-control', 'id' => 'sunset_d', 'value' => set_value('sunset_d', ''), 'maxlength' => '100', 'size' => '35');
$page_data['sunset_m'] = array('name' => 'sunset_m', 'class' => 'form-control', 'id' => 'sunset_m', 'value' => set_value('sunset_m', ''), 'maxlength' => '100', 'size' => '35');
$page_data['sunset_y'] = array('name' => 'sunset_y', 'class' => 'form-control', 'id' => 'sunset_y', 'value' => set_value('sunset_y', ''), 'maxlength' => '100', 'size' => '35');
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('jobs/create', $page_data);
$this->load->view('common/footer');
} else {
在这一点上,数据已经通过验证,并存储在$save_data数组中,准备将其保存到数据库中:
$save_data = array(
'job_title' => $this->input->post('job_title'),
'job_desc' => $this->input->post('job_desc'),
'cat_id' => $this->input->post('cat_id'),
'type_id' => $this->input->post('type_id'),
'loc_id' => $this->input->post('loc_id'),
'job_start_date' => $this->input->post('start_y') .'-'.$this->input->post('start_m').'-'.$this->input->post('start_d'),
'job_rate' => $this->input->post('job_rate'),
'job_advertiser_name' => $this->input->post('job_advertiser_name'),
'job_advertiser_email' => $this->input->post('job_advertiser_email'),
'job_advertiser_phone' => $this->input->post('job_advertiser_phone'),
'job_sunset_date' => $this->input->post('sunset_y') .'-'.$this->input->post('sunset_m').'-'.$this->input->post('sunset_d'),
);
$save_data数组随后被发送到Jobs_model的save_job()函数,该函数将使用set_flashdata()生成确认消息,如果保存操作成功,或者错误消息,如果操作失败:
if ($this->Jobs_model->save_job($save_data)) {
$this->session->set_flashdata('flash_message', $this->lang->line('save_success_okay'));
redirect ('jobs/create/');
} else {
$this->session->set_flashdata('flash_message', $this->lang->line('save_success_fail'));
redirect ('jobs');
}
}
}
最后,我们到达apply()函数。这稍微简单一些。像create()一样,我们首先定义我们的表单项验证规则,然后检查表单是否被提交(提交)。我们这样做是因为工作 ID 可以通过两种方式传递。
第一种方式是使用$this->uri->segment(3)。如果用户点击views/jobs/view.php文件中的申请链接或工作标题,ID 将通过第三个uri段传递给apply()函数。
第二种方式是$this->input->post('job_id')。如果表单已被提交,ID 将通过 POST 数组传递给apply()函数。在views/jobs/view.php文件中有一个隐藏的表单元素,名为job_id,其值被填充为正在查看的工作的实际 ID:
public function apply() {
$this->form_validation->set_rules('job_id', $this->lang->line('job_title'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('app_name', $this->lang->line('app_name'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('app_email', $this->lang->line('app_email'), 'required|min_length[1]|max_length[125]');
$this->form_validation->set_rules('app_phone', $this->lang->line('app_phone'), 'min_length[1]|max_length[125]');
$this->form_validation->set_rules('app_cover_note', $this->lang->line('app_cover_note'), 'required|min_length[1]|max_length[3000]');
if ($this->input->post()) {
ID 存储在$page_data数组中的job_id项中,并传递给Jobs_model的get_job()函数:
$page_data['job_id'] = $this->input->post('job_id');
} else {
$page_data['job_id'] = $this->uri->segment(3);
}
$page_data['query'] = $this->Jobs_model->get_job($page_data['job_id']);
我们接着测试是否有任何返回。我们使用 CodeIgniter 的num_rows()函数来查看返回的数据库对象中是否有任何行。如果没有,我们就设置一个闪存消息,说明该工作已不再可用。
可能是在用户点击申请链接和实际提交申请之间,工作广告已经变得不可用;也就是说,其job_sunset_date已经过去,或者有人可能手动输入了一个随机的 ID,而这个 ID 恰好不存在。无论如何,无论是什么原因,如果没有返回结果,就会向用户显示一个闪存消息。然而,如果找到了,我们就从数据库中提取数据,并将其存储为局部变量:
if ($page_data['query']->num_rows() == 1) {
foreach ($page_data['query']->result() as $row) {
$page_data['job_title'] = $row->job_title;
$page_data['job_id'] = $row->job_id;
$job_advertiser_name = $row->job_advertiser_name;
$job_advertiser_email = $row->job_advertiser_email;
}
} else {
$this->session->set_flashdata('flash_message', $this->lang->line('app_job_no_longer_exists'));
redirect('jobs');
}
然后我们继续进行表单验证过程。如果是初始页面视图或者提交过程中有错误,那么$this->form_validation->run()将返回FALSE;如果是这样,我们就构建表单项,定义它们的设置:
if ($this->form_validation->run() == FALSE) {
$page_data['job_id'] = array('name' => 'job_id', 'class' => 'form-control', 'id' => 'job_id', 'value' => set_value('job_id', ''), 'maxlength' => '100', 'size' => '35');
$page_data['app_name'] = array('name' => 'app_name', 'class' => 'form-control', 'id' => 'app_name', 'value' => set_value('app_name', ''), 'maxlength' => '100', 'size' => '35');
$page_data['app_email'] = array('name' => 'app_email', 'class' => 'form-control', 'id' => 'app_email', 'value' => set_value('app_email', ''), 'maxlength' => '100', 'size' => '35');
$page_data['app_phone'] = array('name' => 'app_phone', 'class' => 'form-control', 'id' => 'app_phone', 'value' => set_value('app_phone', ''), 'maxlength' => '100', 'size' => '35');
$page_data['app_cover_note'] = array('name' => 'app_cover_note', 'class' => 'form-control', 'id' => 'app_cover_note', 'value' => set_value('app_cover_note', ''), 'maxlength' => '3000', 'rows' => '6', 'cols' => '35');
$this->load->view('common/header');
$this->load->view('nav/top_nav');
$this->load->view('jobs/apply', $page_data);
$this->load->view('common/footer');
如果提交过程中没有错误,那么我们将构建一封要发送给工作广告发布者的电子邮件;这封电子邮件将发送到jobs.job_advertiser_email中包含的电子邮件地址。
} else {
我们使用 PHP 函数str_replace();在电子邮件中替换变量,将变量替换为从数据库或表单提交中提取的详细信息,例如申请人的联系信息和求职信:
$body = "Dear %job_advertiser_name%,\n\n";
$body .= "%app_name% is applying for the position of %job_title%,\n\n";
$body .= "The details of the application are:\n\n";
$body .= "Applicant: %app_name%,\n\n";
$body .= "Job Title: %job_title%,\n\n";
$body .= "Applicant Email: %app_email%,\n\n";
$body .= "Applicant Phone: %app_phone%,\n\n";
$body .= "Cover Note: %app_cover_note%,\n\n";
$body = str_replace('%job_advertiser_name%', $job_advertiser_name, $body);
$body = str_replace('%app_name%', $this->input->post('app_name'), $body);
$body = str_replace('%job_title%', $page_data['job_title'], $body);
$body = str_replace('%app_email%', $this->input->post('app_email'), $body);
$body = str_replace('%app_phone%', $this->input->post('app_phone'), $body);
$body = str_replace('%app_cover_note%', $this->input->post('app_cover_note'), $body);
如果电子邮件成功发送,我们将向申请人发送一个闪存消息,通知他们他们的申请已经发送,如下面的代码所示;这并不等同于验证错误。验证错误已经在之前处理过,如果验证没有通过,我们不会进入表单处理的这一步。实际上,我们说的是如果电子邮件没有正确发送——可能是因为某些原因mail()失败了——那么申请就不会被发送。这就是我们想要表达的意思:
if (mail($job_advertiser_email, 'Application for ' . $page_data['job_title'], $body)) {
$this->session->set_flashdata('flash_message', $this->lang->line('app_success_okay'));
} else {
$this->session->set_flashdata('flash_message', $this->lang->line('app_success_fail'));
}
redirect ('jobs/apply/'.$page_data['job_id']);
}
}
}
创建语言文件
就像本书中的所有项目一样,我们正在使用语言文件来向用户提供文本。这样,你可以启用多区域/多语言支持。让我们创建语言文件。
创建/path/to/codeigniter/application/language/english/en_admin_lang.php文件,并将其中的以下代码添加到该文件中:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
// General
$lang['system_system_name'] = "Job Board";
// Jobs - view.php
$lang['jobs_view_apply'] = "Apply";
$lang['jobs_view_search'] = "Search";
// Jobs - create.php
$lang['job_create_form_instruction_1'] = "Enter the information about your job advert below...";
$lang['job_title'] = "Title";
$lang['job_desc'] = "Description";
$lang['type'] = "Job type";
$lang['cat'] = "Category";
$lang['loc'] = "Location";
$lang['job_start_date'] = "Start date";
$lang['job_rate'] = "Rate";
$lang['job_advertiser_name'] = "Your name (or company name)";
$lang['job_advertiser_email'] = "Your email address";
$lang['job_advertiser_phone'] = "Your phone number";
$lang['job_sunset_date'] = "Sunset date";
$lang['job_sunset_date_help'] = "Your job advert will be live up to this date, after which it will not appear in searches and cannot be applied for";
$lang['save_success_okay'] = "Your advert has been saved";
$lang['save_success_fail'] = "Your advert cannot be saved at this time";
// Jobs - Apply
$lang['apply_instruction_1'] = "Fill out the form below to apply for ";
$lang['app_name'] = "Your name ";
$lang['app_email'] = "Your email ";
$lang['app_phone'] = "Your phone number ";
$lang['app_cover_note'] = "Cover note ";
$lang['app_success_okay'] = "Your application has been sent ";
$lang['app_success_fail'] = "Your application cannot be sent at this time ";
$lang['app_job_no_longer_exists'] = "Unfortunately we are unable to process your application as the job is no longer active";
将所有内容整合在一起
好的,以下是一些示例,将有助于将所有内容整合在一起。
用户创建工作广告
让我们看看创建工作广告的过程是如何具体工作的:
-
用户访问网站,会看到一个工作列表、搜索框和导航栏。
-
如果用户想要创建一个新的工作,他们就会点击
views/nav/top_nav.php文件中包含的创建链接。 -
CodeIgniter 加载
jobs控制器的create()函数。 -
create()函数在views/jobs/create.php视图文件中显示表格。有三个 HTML 下拉表单元素允许用户选择工作类型、类别和位置。这些下拉列表分别由Jobs_model的get_types()、get_categories()和get_locations()函数填充。 -
用户填写表格并点击Go按钮提交表格。
-
表单被提交到
jobs控制器的create()函数;经过验证后通过。 -
jobs控制器的create()函数将验证后的表单输入发送到Jobs_model的save_job()函数,其中它被保存到jobs数据库表中。
用户查看工作
现在我们将看到用户如何查看工作:
-
用户访问网站,并显示一份工作列表、搜索框和导航栏。
-
用户点击列表中第一个工作的标题。
-
CodeIgniter 加载
jobs控制器的apply()函数。 -
apply()函数查看 URI 中的第三个部分(这是在上一步骤中工作标题 URL 中传递的job_id值)并将其传递给Jobs_model中的get_job()函数。 -
get_job()函数从数据库中提取工作的详细信息,并将数据库结果对象返回给jobs控制器。 -
jobs控制器将数据库结果对象发送到views/jobs/apply.php视图文件,其中foreach()循环遍历对象,输出工作的详细信息。
用户搜索工作
用户搜索工作时遵循的步骤流程如下:
-
用户访问网站,并显示一份工作列表、搜索框和导航栏。
-
用户在搜索框中输入单词
CodeIgniter并按下Enter键。 -
CodeIgniter 框架随后调用
jobs控制器的index()函数。 -
index()函数调用Jobs_model的get_jobs()函数,并传递search_string帖子项:$page_data['query'] = $this->Jobs_model->get_jobs($this->input->post('search_string')); -
Jobs_model的get_jobs()函数识别到有搜索字符串作为输入,并运行正确的数据库查询,查看jobs.job_title和jobs.job_desc以查看是否有文本字符串与用户的搜索字符串匹配。 -
找到与工作广告匹配的字符串。
-
结果对象被返回到
views/jobs/view.php文件,其中foreach()循环遍历结果对象,显示工作的摘要详情。 -
用户可以自由点击申请链接进一步了解详情或申请工作。
用户申请工作
当用户想要申请工作时,会执行以下步骤:
-
用户访问网站,并显示一份工作列表、搜索框和导航栏。
-
用户点击列表中第一个工作的标题。
-
CodeIgniter 加载
jobs控制器的apply()函数。 -
apply()函数查看 URI 的第三个部分(这是在上一步骤中工作标题的 URL 中传递的job_id值)并将其传递给Jobs_model中的get_job()函数。 -
get_job()函数从数据库中提取工作详情,并将数据库结果对象返回给jobs控制器。 -
jobs控制器将数据库结果对象发送到views/jobs/apply.php视图文件,其中foreach()循环遍历对象,输出工作的详细信息。 -
用户在职位描述下方填写他们的详细信息,然后点击 Go。
-
表单提交到
jobs控制器的apply()函数进行验证;一旦通过,jobs控制器将查询Jobs_model的get_job()函数以找到jobs.job_advertiser_email和jobs.job_advertiser_name值,以便将申请电子邮件发送给广告商。
摘要
因此,我们有一个基本的招聘板应用程序;它能够允许人们创建工作,显示这些工作,搜索工作,并且它还允许人们申请这些工作。然而,仍然有改进和扩展更多功能的空间;也许您可以做以下事情:
-
为申请人添加电子邮件确认。您可以在
jobs/apply()函数中添加一个功能,当申请人申请工作时发送确认电子邮件。 -
限制申请数量。您可以为每个工作添加限制申请数量的功能;需要逻辑来计算哪个先到:日落日期或申请限制。
-
您可以对结果进行分页。目前,所有活跃的工作都在
jobs/index()函数中显示。您可能希望添加分页功能,将每页的工作数量限制为一定的数量——例如每页 25 个。 -
您可以拥有详细的搜索选项。您可以添加更复杂的搜索,例如添加一个下拉菜单来指定位置或工作类型等。
-
您可以删除旧的工作广告。您可以创建一个小型的 Cron 脚本,删除那些超过其日落日期(
jobs.job_sunset_date)的工作。这将使数据库的大小更加合理,并确保只有活跃的工作被保留。
那么,我们就到这里——我们结束了!我们学到了什么……嗯,你应该有一系列的项目可以准备使用——最好的是,它们都非常简单,所以您可以很容易地在此基础上扩展并添加更多功能和功能;至少,您应该有一个 基础 平台,您可以在其上构建任何数量的应用程序。
一定要查看 CodeIgniter 网站 (www.codeigniter.com/) 以获取常规更新和新版本。别忘了这本书中的代码可以从 Packt 网站在线获取,所以您不必痛苦地从页面复制到屏幕,每个项目的 SQL 语句也在那里。
对了——就是这样,结束了!



浙公网安备 33010602011771号