PHP7-和-MySQL8-实践指南-全-

PHP7 和 MySQL8 实践指南(全)

原文:Practical PHP 7, MySQL 8, and MariaDB Website Databases

协议:CC BY-NC-SA 4.0

一、创建并测试数据库和表

本章介绍了数据库的概念和测试数据库的实用方法。使用这些示例,您将创建一个 MariaDB 或 MySQL 数据库和一个表。通过学习这些示例,您将会熟悉数据库管理界面。

完成本章后,学生将能够

  • 定义和设计数据库和表

  • 安装和使用 WAMP 软件包

  • 使用 phpMyAdmin 创建一个数据库和表

  • 使用用户 ID 和密码保护 phpMyAdmin 和数据库

  • 删除数据库和/或表

数据库可以用来存储产品、客户的详细信息、社团或俱乐部成员的记录等等。它们可以存储姓名、密码、地址、电子邮件地址、注册日期、博客条目和电话号码。数据库可以被看作是包含数据表的文件夹。数据表有;数据库表中的行被称为记录。表 1-1 显示了一个典型的数据库表。

表 1-1

典型的数据库

|

用户 id

|

名字

|

姓氏

|

电子邮件

|

密码

|

电话

|
| --- | --- | --- | --- | --- | --- |
| one | 凯文 | 烧水用水壶 | kev@kettle.com | K3ttl3fur | 305 111 1111 |
| Two | 苏珊(女子名) | 炖锅 | sue@kitchen.org.uk | n @您的 5 | 01111 222 1111 |
| three | 奥利弗 | 烤箱 | oliver@cokker.co.uk | H0tst0v3 | 03333 111 4444 |

定义开发人员、管理员和用户

在本书中,术语开发者(又名站长)指的是设计和制作数据库的人;他们将把数据库整合到一个网站上。有时可能会用到术语网站管理员网页设计师。用的时候一般和开发者是一个意思。在本书的一些教程中,管理员会员秘书这两个词的意思相同,这些教程都是基于为一个俱乐部建立数据库。单词管理员表示负责监控和维护数据库表内容的人员。显然,一个人可以既是开发人员又是管理员。然而,大多数开发人员会维护数据库的结构,但不想要修改和删除记录的麻烦;这应该是管理者的角色(比如俱乐部或社团的会员秘书)。

用户是查看网站数据库并可能与之交互的任何普通公众成员。出于安全原因,用户对数据库的访问极其有限;然而,他们将被允许注册会员,登录到一个特殊的部分,或改变他们的密码。

警告

委托开发数据库的组织必须遵守数据库开发所在国的法规和法律。在英国,必须遵守数据库开发地区的数据保护法。如果这些数据将被用于盈利,这一点尤为重要。这可能需要获得许可证。此外,开发人员和管理员通常必须签署一份文件,确认他们永远不会披露数据库中记录的个人详细信息。英国信息专员办公室(ICO)要求根据拥有该数据库的组织的收入支付年度许可费。美国没有相应的法律,但是各州的隐私法可能有所不同。您必须了解并遵守客户所在地区的数据保护法。

可供多个国家访问的数据库将需要满足每个国家的要求。有时这需要创建不同版本的数据库和/或网站,否则就不需要。许多法律法规落后于提供尽可能安全的数据库环境的总体需求。在设计和使用数据库时,开发人员应该总是选择“最安全”的方法。仅用于教育目的的数据库(如本书中所示的数据库)不必满足要求和法规(如许可)。

定义交互式网站

交互式网站通常被称为动态网站;然而,这本书使用了互动这个词,因为动态可以表示很多事情。例如,它可能意味着感人的,强大的,引人注目的,浮华的,令人兴奋的。对于初学者来说,这些含义都不能定义与用户交互的网页。

动态(Dynamic)通常用来表示令人兴奋,但在交互式注册表单中几乎看不到兴奋。动态也是一个音乐术语,意思是响度或速度的改变或变化。如果动态可以指变化,为什么动态模板被设计成从一个网页到另一个网页提供一致性?术语交互有一个明确的含义,从现在开始将在本书中使用。

MariaDB 和 MySQL(使用 PHP)允许用户和管理员使用网站页面与数据库进行交互。例如,用户可以通过网站上的注册页面注册成为某个组织的成员。用户将能够为成员资格表提供他们的个人数据。数据库管理系统然后自动将用户的输入输入到管理员的表格中;这减轻了管理员的工作量。网站的注册页面可以被编程来过滤用户的数据输入并进行验证。在交互式页面上,用户甚至可以更新自己在数据库中的记录。

交互性意味着管理员的工作量大大减少,但不是完全减少。例如,如果数据库用于书店,管理员仍然需要输入任何新的书名和价格。另一方面,当某本书的库存需要补充时,交互式数据库可以被编程来提醒管理员。

在第二章,你将学习开发一个简单的交互式网站。

仅对交互式数据库表使用 MariaDB 或 MySQL

非交互式数据表意味着只有管理员才能输入或修改表中的信息。使用电子表格(如 Microsoft Excel)更容易创建和管理非交互式数据表。然而,网站用户不能与这样的数据表进行交互。使用 MariaDB 或 MySQL 数据库管理系统(DBMS)来创建数据表的非交互式(静态)版本就像用大锤砸坚果一样。网站用户没有输入,也不能搜索或更新数据。

对非交互式版本使用 MariaDB 或 MySQL DBMS 不会减少管理员的工作量;他们必须输入所有成员的数据,并验证这些数据是真实的。

注意

一些交互式网页不需要数据库就能运行。例如,“联系我们”表单可以被认为是交互式的,因为它接受用户的输入,并通过电子邮件经由 PHP 表单处理程序将其传输给网站所有者;这可以在没有数据库的情况下轻松实现。在本书中,术语交互总是指用户可以与数据库交互。

开发和维护数据库的方法

管理数据库的四种方法如下(最简单的方法在前,最难的在后):

  • phpMyAdmin(或其他管理工具)

  • 服务器端编程语言(Professional Hypertext Preprocessor 的缩写)

  • SQL 脚本

  • 命令行

在本书中,我们将主要使用前两种方法(phpMyAdmin 和 PHP)。我们将使用 SQL 脚本和命令行来简要演示如何创建、更新和分发您的数据库。对于交互式数据库,您将需要一些 PHP 文件。在创建交互式数据库之前,您不需要掌握大量的 PHP 知识。我们将在每个项目的适当位置介绍您需要的 PHP 语法——也就是说,在上下文中。一步一步,充分工作的例子将向您展示 MariaDB 和 MySQL 可以做什么以及如何做。

由于图形用户界面(GUI)的普及,已经开发出图形用户界面来促进开发和维护数据库的任务。这些管理工具是开发包的一部分,开发包包括 web 服务器(Apache)、数据库(MariaDB 或 MySQL)和编程语言(PHP)。在本书中,我们将介绍两个最流行的软件包:XAMPP 和 EasyPHP。

简要了解 Web 服务器通信

数据库需要一个服务器、一个 DBMS 和一个 PHP 处理器,如图 1-1 所示。这些可以作为一个已经配置好的一体化软件包下载。本书中项目的测试和开发基于免费的 XAMPP 和 EasyPHP 包。

img/314857_2_En_1_Fig1_HTML.jpg

图 1-1

Web 服务器通信

图 1-1 显示了 XAMPP 和 EasyPHP 开发平台内置的主要组件。它们如下:

  • Apache 是大多数主机使用的网页服务器,用于本地主机(用户计算机)上的 web 开发。Web 服务器通过查看文件结尾()来确定网页是否包含 PHP 代码。php 。如果 PHP 代码存在,代码被传递给 PHP 解释器执行。如果 PHP 解释器确定 PHP 程序中存在与数据库相关的代码或 SQL,这些信息将被传递给 MySQL 或 MariaDB 数据库管理系统。数据库管理系统执行 SQL 语句并将结果返回给 PHP 解释器。PHP 解释器完成 PHP 代码的执行,并将结果返回给 Apache 服务器。Apache 服务器收集 PHP 代码的结果,以及任何 HTML、CSS 和/或 JavaScript,并将这些信息发送到用户机器上的 web 浏览器。网络浏览器然后执行 HTML、CSS 和/或 JavaScript 代码。

  • MySQL 和 MariaDB 不仅仅是数据库;它们也是数据库管理系统。这些包包括创建、管理和保护数据库的工具。

  • PHP 处理器解释并执行任何 PHP 代码。如果有语法(编码错误)、逻辑错误或系统错误(内存不足),它将抛出错误或异常。

  • phpMyAdmin 是一个基于 GUI 的管理工具,用于创建和维护数据库及其表。

一个单一的一体化软件包,如 XAMPP 或 EasyPHP,包含图 1-1 中提到的四个程序,被称为 WAMP (Windows、Apache、MySQL/MariaDB 和 PHP)。在 WAMPs 中,主要组件是预先配置好的,这样它们就可以相互通信。在苹果操作系统上相当于 MAMP,在 Linux 电脑上相当于 LAMP。

文件夹 htdocs (或 eds-www )是你的网页的默认存储和可执行区域。默认情况下,Apache 会在 htdocs (或 eds-www )中查找你的网页。这些页面可以被设计成允许用户与数据库交互。其他页面将在用户看不见的情况下运行,在浏览器和数据库之间来回传输信息。页面通常是 HTML 和 PHP 文件或者两者的组合。

警告

图 1-1 中的所有东西可能都已经安装在远程主机上,但是在学习的过程中,千万不要使用远程主机来创建数据库。出于安全原因,在熟练掌握之前,不要使用远程主机。我们建议您在自己的计算机上使用 all-in-one 软件包学习和开发数据库。注意,安装在自己电脑上的一体机包纯粹是开发工具。该数据库在开发和彻底测试后,最终可以上传到主机,供用户使用。

用于测试的免费开发平台

您将无法以正常的方式测试您的工作——也就是说,通过使用浏览器来查看位于您硬盘上的数据库和 PHP 代码。当浏览器请求包含 PHP 的网页时,PHP 代码在 web 服务器中执行。代码的执行结果将显示在浏览器中。如果您查看浏览器代码(右键单击网页并选择 View Source),您将看不到 PHP 代码。您将看到代码执行产生的结果。这让那些习惯于在浏览器中看到自己创建的任何 HTML、CSS 或标准 JavaScript 代码的初级开发人员感到困惑。记住,HTML、CSS 和标准 JavaScript 都是在浏览器中执行的。PHP 代码在 web 服务器中执行,即使该服务器在您自己的 PC 上。

在您的计算机上使用 all-in-one 软件包将允许您查看您创建的所有代码以及在您的 PC 上执行这些代码的结果。这本书假设你在学习的时候会在你自己的电脑上使用一个软件包,并用于开发未来的数据库驱动的网站。

注意

在当前黑客试图破坏或收集个人、公司和政府数据的世界中,互联网托管网站上的所有网页必须得到保护。本书中最早的项目必然是简单的,并且有一些安全的特性。但是,不建议您将这些页面上传到互联网主机。当你获得了经验和信心,并且你确信你了解如何保护网站和数据库的安全,你可以改编这本书的后期项目用于你自己的网站,然后将它们上传到远程主机。

在自己的电脑上使用 XAMPP

XAMPP 软件包是免费的,并且已经过预配置,因此组件可以相互通信。这消除了通常下载几个单独的组件,然后配置它们一起工作的麻烦。XAMPP 包括 Windows (WAMP)、Linux (LAMP)和苹果(Mac)操作系统(MAMP)的软件包。本书中的示例是使用 WAMP 环境开发的。然而,这些例子也应该在灯或 MAMP 环境中工作。

在撰写本文时,XAMPP 的最新版本是 7.2.4。整本书都使用这个版本。它的组件版本如下:Apache 2.4.33、MariaDB 10.1.25、PHP 7.2.4 和 phpMyAdmin 4.8.0,以及其他工具。本书中的例子并不完全兼容 7.0 之前的 PHP 版本。

注意

如果您安装的版本比本书中演示的版本新,安装步骤可能会略有不同。您应该参考 XAMPP 网站( www.apachefriends.org )或在互联网上搜索安装说明(尝试 youtube.com )以获得本书中未演示的任何版本。如果新版本包含重大升级(例如 PHP 8),可能需要对一些代码进行更改。查看 Apress 网站,了解与主要版本发布相关的代码变更。

在我们给你下载 XAMPP 的指导之前,我们需要解决一个困扰每个初学者的问题,这个问题是关于将一个开发的数据库从 XAMPP 或 EasyPHP 转移到远程主机。如果您在自己的计算机上使用这些软件包中的一个,将会出现一个问题,如下一节的标题所述:

我能把数据库从 XAMPP 或 EasyPHP 转移到远程主机吗?

困扰初学者的主要问题是:“如果我在本地 WAMP 上开发一个数据库,我能轻松地将它迁移到远程主机上吗?”初学者完全有理由担心,因为大多数手册很少给出关于这个主题的提示。然而,答案是这样的:“是的,您将能够移动数据库。”你会在本书后面找到完整的说明。

现在我们将提供下载和安装 XAMPP 的信息。

警告

如果你想安装多个免费的 WAP,可以在同一台电脑上安装 EASYPHP 和 XAMPP。但是,在打开另一个之前,请确保其中一个已关闭;否则,它们将争夺相同的端口,并导致令人讨厌的问题。

下载并安装 XAMPP

XAMPP 需要最少的配置。要下载该软件包,请前往 www.apachefriends.org/

点击主页面顶部菜单中的下载,如图 1-2 所示。下载页面将显示当前可用的版本。如果可以的话,选择 XAMPP 的 7.2.4 版本,以确保你在本书中使用的所有例子都是兼容的。如果有版本 7 的次要版本(比如 7.3.0),它可能仍然是兼容的。如果版本 7 不可用,请下载最新版本。然后查看 Apress 网站,看看本书中的例子是否需要修改代码。下载页面会不时变化,因此您可能需要浏览页面以找到显示的版本。

img/314857_2_En_1_Fig2_HTML.jpg

图 1-2

安装 XAMPP

注意

下载页面将说明您必须安装 C++运行时库。XAMPP 及其相关工具是使用 C++创建的。如果您使用的是当前版本的 Windows,那么您可能已经有了正确版本的 C++。但是,如果您收到一条错误消息,指出它已丢失,请按照错误消息提供的链接下载正确的版本。

单击最新版本旁边的下载按钮。这将开始下载过程。该文件将自动下载到您电脑上的下载文件夹中。根据您使用的浏览器,可能会询问您是否要运行或保存该程序。有些浏览器会自动保存程序,并要求你点击文件来运行它。如果您不知道如何在您使用的浏览器中找到下载的文件,请在互联网上搜索相关信息。文件下载完成后,双击该文件(或根据提示单击运行选项)开始安装过程。

环境将提示您请求安装程序的权限。单击“是”继续该过程。安装程序将决定您机器上的安全设置。它可能会警告您,您的安全性可能会限制某些可用的 XAMPP 选项。您可以选择继续安装或停止当前安装并暂时关闭您的安全程序。如果关闭安全保护,请确保你的电脑不再连接到互联网。安装程序还会查看您的用户帐户控制(UAC)设置。这些设置根据您登录系统时使用的用户 ID 来限制对文件夹和文件的访问。如果您使用的是没有管理权限的计算机,安装例程将提供一条警告消息,提示您将程序安装在不同于默认位置(程序文件)的位置。按照提示进行操作,并在提示到不同位置时更改默认设置,例如 c:/XAMPP

将出现欢迎使用 XAMPP 安装向导屏幕。单击“下一步”继续安装。下一个屏幕将显示所有可供安装的选项。如果您是初学者,只需通过单击“下一步”按钮接受已经检查的项目。中级和高级用户可能会考虑删除您确定不会使用的项目。如果需要,您可以随时安装它们。下一个屏幕将显示安装位置。使用 c:/XAMPP 来避免任何可能的安全访问问题。单击下一步。下一个屏幕将试图说服你也看看安装 CMS 程序(如 WordPress)。现在,取消选中“了解更多”框。有兴趣可以以后再装。单击“下一步”按钮。下一个屏幕将显示设置就绪。单击下一步。安装将开始。默认情况下,启动控制面板复选框将被选中。保持选中状态。单击完成按钮。在控制面板显示之前,您可能会收到语言选择提示。

如果在安装过程中出现错误,复制错误信息并粘贴到搜索引擎(如 Google)中。查看建议解决方案的列表。转到一个受信任的网站,并按照指示来纠正您的错误。

XAMPP 控制面板上标有运行的项目通常会自动出现,然后您就可以停止各种模块。如果它们没有自动启动,请单击 Apache 和 MySQL 的 XAMPP 控制面板上的开始按钮。如果一个按钮显示“停止”,则该模块已经在运行。接下来呢?如果你被要求将模块作为服务运行,选择将 Apache 和 MySQL 作为服务运行,然后当你双击 XAMPP 桌面图标时,这些模块将自动启动(图 1-3 )。

img/314857_2_En_1_Fig3_HTML.jpg

图 1-3

XAMPP 的标志

在桌面上为 XAMPP 的 htdocs 文件夹创建一个快捷方式,放在 XAMPP 图标旁边,如图 1-4 所示。使用这个快捷方式将你的 PHP 文件加载到 C:\xampp\htdocs 文件夹中。

img/314857_2_En_1_Fig4_HTML.jpg

图 1-4

节省时间的快捷方式

如果在安装过程中没有创建桌面图标,我们建议您转到 C:\xampp 文件夹,然后为【xampp-control.exe】文件创建一个桌面快捷方式。

为了方便起见,将两个桌面物品并排放置,如图 1-4 所示。一个图标启动和停止 XAMPP,另一个允许你直接在 XAMPP htdocs 文件夹中创建和修改页面。

一个常见的问题是 Skype 和其他应用也使用端口 80,这是 Apache 的默认端口。如果另一个应用正在使用这个端口,Apache 将不会启动。日志(图 1-5 )将显示 Apache 是否因为端口被占用而无法启动。如果出现这种情况,请转到 XAMPP 控制面板,然后单击 Netstat 按钮。此窗口将列出您的 PC 上运行的所有应用以及它们使用的端口。确定一个未使用的端口(如 8080)并关闭 Netstat 窗口。然后单击 Apache 旁边的 Config 按钮。选择 httpd.conf 。配置文件将在记事本(或您的默认文本编辑器)中打开。点击编辑,点击搜索,输入 80 。单击查找下一行,直到找到以下行:

Listen 80

将该行更改为:

Listen 8080

或者,将其更改为您决定使用的任何端口。

现在返回到搜索并单击查找下一行,直到找到以下行:

ServerName: localhost: 80

将该行更改为:

ServerName: localhost: 8080

或者将其更改为您决定使用的端口。

保存文件,并关闭记事本(或您的文本编辑器)。转到 XAMPP 控制面板,点击页面右上角的配置按钮。单击服务和端口设置按钮。单击 Apache 选项卡。输入 Apache 的新端口(8080 或您决定使用的任何端口)。单击保存。关闭配置窗口。现在点击控制面板中 Apache 旁边的开始按钮(图 1-5 )。几秒钟后,Apache 应该会启动。这将由 Apache 后面的绿色背景表示。您还需要启动 MySQL,因为没有 Apache 的运行,它无法启动。

从 XAMPP 开始

从现在开始,要在 XAMPP 测试您的页面,双击桌面图标并检查 Apache 和 MySQL/MariaDB 是否已经启动。如果它们尚未启动,请单击 Apache 的启动按钮。启动后,点击 MySQL/MariaDB 的开始按钮,然后最小化控制面板。

图 1-5 为 XAMPP 控制面板。

img/314857_2_En_1_Fig5_HTML.jpg

图 1-5

XAMPP 控制面板

我们建议您总是最小化控制面板,以便您有一个清晰的桌面来开始您的数据库工作。

启动 Apache 和 MySQL/MariaDB 后,您可以测试您的安装并检查所有的 XAMPP 示例和工具;为此,请在浏览器中输入以下地址之一:

http://localhost/
http://127.0.0.1/

如果您添加了端口号,请确保以如下示例之一的格式包含相同的端口号:

http://127.0.0.1:8080/
http://localhost:8080/

关闭 XAMPP

测试完数据库和 PHP 文件后,关闭 XAMPP。这将为数据库开发以外的任务释放内存。要关闭,单击任务栏上的最大化 XAMPP 控制面板,然后单击控制面板上的退出按钮,如图 1-6 所示。或者,您可以右键单击通知区域中的图标,然后单击退出。

img/314857_2_En_1_Fig6_HTML.jpg

图 1-6

结束 XAMPP 计划

数据库及其数据的安全性极其重要。XAMPP 提供了一种使计算机上的数据库和表免受有害干扰的方法,这将在本章后面介绍。

MariaDB 和 MySQL 8 在哪里?

此时,您可能会对 XAMPP 和 MariaDB 数据库的位置感到困惑。XAMPP 的创造者确实从使用 MySQL 数据库转向使用 MariaDB 数据库;然而,我们刚才解释的一切似乎表明 MySQL 仍然是 XAMPP 的一部分。然而,事实并非如此。当创建者决定切换数据库系统时,他们只需对配置进行微小的修改就可以使用 MariaDB。由于 MariaDB 的操作与 MySQL 的免费版本没有任何不同,所以创建者决定保留 MySQL 名称。由于您可能不熟悉这个环境,这可能会令人困惑。然而,对于 XAMPP 的当前用户来说,这允许在数据库环境之间进行简单的转换。请记住,无论你在 XAMPP 哪里看到 MySQL,它都是 MariaDB。

这本书的书名包括 MySQL 8 。MySQL 8 是一个完整的专业版本,拥有最新的功能,包括商业分析工具。但是,MySQL 8 没有提供 XAMPP 或 EasyPHP,完整版也不是免费的。如果你正在为公司或政府机构开发网站,你应该考虑使用 MySQL 8(或最新版本)。任何版本的 MySQL 都可以附加到 XAMPP。您确实需要阅读 MySQL 文档,以确保您附加的版本与您安装的 Apache 和 PHP 版本兼容。添加新版本的 MySQL 属于中级技能。我们在上一章提供了指导,以帮助您做好转换的准备。

在自己的电脑上使用 EasyPHP

如果你更喜欢使用 EasyPHP,或者你是一个初学者,并且你的设计师同事已经说服你必须使用 MySQL 的早期版本(即 MariaDB 不能使用),你可以安装 EasyPHP,这是一个 WAMP 包,包括免费版本的 MySQL。它还包括 MongoDB 数据库系统,这是一个非 SQL 数据库。MongoDB 不在本书讨论范围之内。

没有理由需要安装这两个软件包。但是,如果您选择这样做,您可能需要一次运行一个,或者调整端口以便两个端口可以同时运行。一旦安装完毕,您将会看到这两个包以相似的方式工作。所有示例都已经在两种环境下进行了测试。

下载并安装 EasyPHP

要下载最新版本的 EasyPHP,请访问以下站点:

www.easyphp.org

单击主页中间的 Download 按钮,下载 easyPHP 开发服务器的非 beta 版本。本书中用于测试和运行示例的版本是 17.0,包括 PHP 7.2、Apache 2.4 和 MySQL 5.7(最新的免费版本)。如果您注意到一个新的主要版本(如 18.0),请查看本书的 Apress 网站,以了解在所示的示例中是否有任何代码更改的要求。

安装程序应该会自动启动。Windows 环境将要求您通过单击“是”按钮来允许安装。单击该按钮后,设置程序将询问您的语言偏好。接下来,它将允许您更改已安装文件的位置。如果您对自己的电脑(如公司拥有的电脑)没有管理权限,您可能无法在程序文件下安装 EasyPHP。我们建议将安装文件的位置更改为以下位置:

C:\ easy PHP-DevServer-xx(xx是你正在安装的版本比如 17)

单击下一步。安装过程将询问您是否想要创建一个桌面图标。我们建议您创建一个以方便访问。单击下一步。当前屏幕将验证您的安装位置。单击安装。安装完成后,最后一个屏幕将询问您是否要启动开发服务器。单击“完成”启动它。如果在安装过程中出现错误,将错误复制到搜索引擎(如 Google)中寻找解决方案。在提供的列表中,转到一个众所周知的网站,并尝试按照指示来修复您的错误。

启动 EasyPHP

转到系统托盘中的 EasyPHP 图标,右键单击并启动仪表板。如果仪表板无法启动,请关闭开发服务器(右键单击图标并选择 Exit),并尝试从桌面快捷方式启动它。仪表板使用端口 1111。如果您仍然不能启动仪表板,右键单击开发服务器图标,选择工具,然后打开端口控制器。验证另一个进程没有使用同一个端口。如果另一个进程正在使用该端口,请停止该进程并重复前面的步骤。

在仪表板内(图 1-7 ,点击 HTTP SERVER 下方的开始按钮。点击这个按钮应该可以启动 Apache 和 MySQL。如果服务器正常启动,“开始”按钮将变为“停止”按钮。此时,HTTP 服务器和数据库服务器都应该在标题下面显示停止按钮。如果遇到错误,尝试停止开发服务器(右键单击系统托盘图标并选择 Quit)并重新启动它(单击桌面上的图标)。如果您仍然有错误,请将错误复制并粘贴到搜索引擎(如 Google)中,以查看可能的解决方案列表。从一个已知的站点选择一个建议的解决方案,并尝试按照指示去做。

img/314857_2_En_1_Fig7_HTML.jpg

图 1-7

EasyPHP 开发服务器

关闭 EasyPHP

每当您需要关闭 EasyPHP 时,只需通过进入系统托盘,右键单击图标并选择 Quit 来关闭开发服务器。这将关闭所有与 EasyPHP 相关的进程。

phpMyAdmin 安全性

phpMyAdmin 在 XAMPP 和 EasyPHP 中的初始安装有用户名 root ,没有密码。如果您在自己的计算机上使用这些设置,则在连接到互联网时会有安全风险。如果您创建数据库不仅仅是为了个人使用或自己的教育,那么您应该创建一个 phpMyAdmin 密码。如果您与其他人在同一个房间工作,如果密码不泄露给其他人,密码将提供安全性。作为最佳实践,您应该用密码保护您的开发环境。

如果你还没有进入 phpMyAdmin,请进入 XAMPP 控制面板或 easyPHP DevServer 页面。在 XAMPP,点击 MySQL 右边的管理。在 EasyPHP 中,点击页面上显示的 phpMyAdmin 版本右边的打开按钮(图 1-8 )。

img/314857_2_En_1_Fig8_HTML.jpg

图 1-8

phpMyAdmin 页面

请注意,如果您已经更改了默认端口,您可能需要在启动 phpMyAdmin 时添加端口号。例如,如果您将端口更改为 8080,phpMyAdmin 的地址现在如下所示:

http://localhost:8080/phpMyAdmin/

点击 phpMyAdmin 中的用户账户选项卡(图 1-9 )。

img/314857_2_En_1_Fig9_HTML.jpg

图 1-9

phpMyAdmin 用户帐户选项卡

单击最后一个条目 root localhost 右侧的编辑权限链接。将显示编辑权限页面(图 1-10 )。单击页面顶部的更改密码链接。

img/314857_2_En_1_Fig10_HTML.jpg

图 1-10

phpMyAdmin 编辑权限页面

在两个框中输入您的密码。单击页面底部附近的“确定”按钮。如果密码更改,您将收到密码已更改的通知。您现在需要告诉 phpMyAdmin 使用新密码。

在 XAMPP,进入你电脑上的 XAMPP 文件夹。打开文件夹 phpMyAdmin。现在在微软记事本(或其他文本编辑器)中打开文件config.inc.php

在 easyPHP 中,进入 EasyPHP-DevServer-xx 文件夹,进入 eds-modules ,打开 phyMyAdmin 文件夹。现在在微软记事本(或其他文本编辑器)中打开文件config.inc.php

搜索以下内容:

['auth_type'] = 'config';

将该行更改为:

['auth_type'] = 'cookie';

保存文件。

如果您使用的是 XAMPP,请返回控制面板。停止并重启 MySQL。返回 phpMyAdmin 页面(http://localhost/phpMyAdmin/http://localhost:8080/phpMyAdmin/)。该页面现在应该要求您输入用户 ID (root)和密码(您刚刚创建的那个)。

如果您使用的是 EasyPHP,请尝试返回 DevServer 页面并重启服务器。然后单击 phpMyAdmin 的 open 按钮。输入用户 ID (root)和密码(您刚刚创建的那个)。如果在输入用户 ID 和密码后出现错误消息,指出 mysql_native_password 丢失,请按照下面的说明操作。

进入 EasyPHP-DevServer-xx 文件夹,进入 eds-binaries ,然后进入 dbserver ,进入 MySQL 文件夹,最后进入 bin 文件夹。该文件夹包含几个可用于运行和管理 MySQL 和 phpMyAdmin 的应用。但是,这些应用只能在命令行界面中运行。为了更容易地找到和使用它们,我们将把这个位置放在 PC 的 Path 变量中。这允许您只执行您需要的程序,而无需输入完整的路径名。转到浏览器中的 URL 行,双击该地址。现在应该突出显示完整的地址。右键单击突出显示的地址,然后选择复制。

转到搜索图标(系统托盘的右下角),点击它,并输入系统变量。单击编辑系统环境变量结果。将出现“系统属性”窗口。单击环境变量按钮。在提供的框中(顶部框代表机器的所有用户,底部框代表您登录时使用的用户 ID),选择路径代码。然后单击编辑按钮。现在单击“新建”按钮。现在将在框中提供一个空间来输入您复制的路径。在框中单击,右键单击,然后选择粘贴(或按 Ctrl+V)。您复制的路径现在应该在框中。确保它是到媒体夹位置的完整路径。正确后,单击“确定”按钮。您的 bin 现在被添加到 PATH 变量中。单击每个窗口的“确定”,关闭窗口并提交变更。现在,您需要关闭所有进程并重启您的机器。这将导致 Windows 使用新路径。

重新启动机器后,重启 EasyPHP DevServer 并确保 HTTP 服务器和 mySQL 服务器都已启动。转到窗口的搜索图标,输入 cmd 。然后按回车键。这将打开命令行窗口。现在,您终于可以将缺少的模块添加到 phpMyAdmin 中了。

输入 mysql -u root 并点击回车键。你现在应该会看到一个 mysql 提示(mysql >)。输入使用 MySQL;(不要忘记分号)并点击回车键。这告诉服务器使用 MySQL 数据库。输入下面一行来添加缺少的插件:

update user set plugin="mysql_native_password" where user="root";

您应该会看到一条消息“Query OK”现在,还可以在命令行中使用以下行来更改密码:

update user set Password=PASSWORD('12345') where user="root";

12345 应该替换为您之前设置的密码。您还应该看到一条消息“Query OK”您的所有更改应该都已完成。通过输入 exit 关闭 mysql 提示符。按 X 键关闭命令窗口。返回 phpMyAdmin 并单击用户帐户。您现在应该看到 Yes 标志已经设置,表明 root 现在有了密码。注意,即使设置了密码,EasyPHP 仍然可能自动让您进入 phpMyAdmin。

这种密码情况发生在免费 MySQL edition 的当前版本(截至本书出版之日)上。正如您之前注意到的,这在 XAMPP 不会发生,因为 XAMPP 使用的是 MariaDB。

如果你对这些步骤有任何问题,并想退出,最简单的方法是使用主软件包文件夹下的卸载程序卸载软件包,然后重新安装 XAMPP 或 EasyPHP。如前所述,如果你收到错误信息,将它们复制到网络搜索引擎(Google)中,寻找建议的解决方案。

直接访问 phpMyAdmin

如果您已经安装了 XAMPP 并且服务器已经启动,那么您可以通过在任何浏览器中输入以下 URL 来直接访问 phpMyAdmin:

http://localhost/phpMyAdmin/http://127.0.0.1/phpMyAdmin/

(如果您更改了默认端口 80,请记住包括最后的正斜杠和端口号。)

如果您已经安装了 easyPHP 并且服务器已经启动,您可以通过在任何浏览器中输入以下 URL 之一来直接访问 phpMyAdmin:

http://localhost:8080/eds-modules/phpmyadmin470x170718155219/
http://127.0.0.1:8080/eds-modules/phpmyadmin470x170718155219/

(您的实际 phpmyadmin 版本/构建可能不同,这将创建一个不同的 phyMyAdmin 文件夹。)

一定要包括http://;否则,浏览器将在互联网上而不是在您的 PC 上查找该位置。

注意

描述 phpMyAdmin 使用的部分适用于任何开发平台:XAMPP、WAMPServer 或 easyPHP。

您可能已经在 XAMPP 或 EasyPHP 中设置了密码,所以无论何时访问 phpMyAdmin,您可能都需要使用该密码登录。这样可以防止互联网机器人和人类干扰你的数据库。如果你和别人一起在办公室工作,后一种情况很重要——你工作的地方可能会有间谍或爱管闲事的人。如果为 phpMyAdmin 创建了密码,会出现一个对话框,如图 1-11 所示。

img/314857_2_En_1_Fig11_HTML.jpg

图 1-11

在对话框中输入密码以访问 phpMyAdmin

输入您的用户名(root)和密码,然后单击 Go 按钮。phpMyAdmin 加载相当慢,但它最终会出现。

请注意,开源程序在不断改进和升级,您可能会发现您的 XAMPP 或 EasyPHP 包中有比本书中使用的版本更新的 phpMyAdmin 版本。您可能还会在 phpMyAdmin 主窗口中看到升级消息,提醒您有新版本。就个人数据而言,安全性是最重要的,所以这些增量更新对你来说是一件好事,尽管它们确实意味着书中的一些截图可能不再准确反映你在屏幕上看到的内容。如果一个界面看起来与本书中显示的有些不同,不要担心;用法通常是相似的。

phpMyAdmin 接口起初看起来可能有点令人畏惧,但是当我们需要使用它们时,我们将介绍它的相关部分。现在,您可以关闭 phpMyAdmin 窗口。

您现在已经知道了如何安装和保护 XAMPP 和 easyPHP,还学习了如何启动和停止服务器。你刚刚读到的大部分内容可能非常新,但有一些部分你会认识,因为它们遵循正常的 Microsoft Windows 文件和文件夹组织。

熟悉的片段

在 XAMPP 软件包中,文件夹和文件的结构将为 Windows 用户所熟悉,尽管它们的名称可能无法识别。

图 1-12 显示了 XAMPP 文件夹。

img/314857_2_En_1_Fig12_HTML.jpg

图 1-12

XAMPP 包中的文件夹;您的列表可能略有不同

在图 1-12 中,注意 htdocs 文件夹。这是你放置你的网站和数据库的所有 PHP 文件和 HTML 页面的地方。

在 XAMPP 文件夹中,你会发现一个名为 MySQL 的文件夹。这个文件夹包含一个名为数据的文件夹,数据库和表格将存放在这里。数据库必须有唯一的名称。数据文件夹中的一个文件包含关于数据库的所有信息,其文件类型为 **。选择*。

表格是文件;当你创建了任何表格后,这些表格也将存在于名为 data 的文件夹中,它们的文件类型为 *。frm

在 easyPHP 中,一旦你打开 EasyPHP - Devserver-xx 文件夹,你会发现一个 eds-www 文件夹。这是放置所有 PHP 文件和 HTML 页面的地方。以下文件路径是数据库文件的位置:

C:\ easy PHP-Devserver-17 \ eds-binaries \ dbserver \ MySQL 5717 x86x 170717151041 \ data

中级或高级开发人员应该考虑创建虚拟目录,这是可执行文件的其他位置。虽然本书没有涉及这个主题,但是你可以通过搜索 XAMPP 虚拟目录easyPHP 虚拟目录在互联网上找到更多信息。

现在,您已经熟悉了将要使用的工具的外观和感觉,您已经准备好继续前进了。下一节将带您接近创建您的第一个数据库和表。

规划数据库:重要的第一步

第一步也是最重要的一步是规划数据库,这样你就有实际的东西可以用了。让我们假设我们需要为一个组织的成员计划一个数据库。

首先,决定数据库的名称。我们将这个数据库命名为 simpledb 。请记住,数据库就像一个空文件夹,最终将包含一个或多个表。

然后,将数据项组合成一个表。我们将该表命名为用户

接下来,决定你想要表格中的什么信息;您的决定没有约束力,因为您可以在开发过程中更改数据库的任何部分。假设我们需要关于用户的五条信息。我们在本章前面的表 1-1 和表 1-2 中列出了一些典型数据。

表 1-2

simpledb 数据库中用户表的计划草案

|

用户 id

|

名字

|

姓氏

|

电子邮件

|

密码

|

注册日期

|
| --- | --- | --- | --- | --- | --- |
|   | 凯文 | 水壶 | kev@kettle.co.uk | k3 TTL 2fur |   |
|   | 苏珊 | 炖锅 | sue@kitchen.org.uk | n @ its5 |   |
|   | 奥利弗 | 烤箱 | oliver@cooker.co.uk | H0tsts0v3 |   |

表格中的每一行称为一条记录,每一个单元格称为一个字段。一个数据库可以包含多个表。我们使用了一些虚构的名字来帮助设计这张桌子。第一列标记为 user_id,这一列是对五列数据的补充。稍后将解释列 user _ id 暂时接受它,并确保它是空的。此外,将注册日期留空,因为这是自动输入;它不需要输入值,也不需要分配空间。

现在我们必须为数据分配一些空间。表 1-3 显示了我们为每个项目分配的字符数。

表 1-3

用户表中列允许的字符数

|

用户 id

|

名字

|

姓氏

|

电子邮件

|

密码

|

注册日期

|
| --- | --- | --- | --- | --- | --- |
| six | Thirty | Forty | Fifty | Sixty |   |

写下或打印这两个表格,并把它们放在手边,因为在接下来的阶段你会用到它们。

现在,决定数据库的用户名和密码,并将该信息输入到您的笔记本中。需要四条信息:数据库名称、主机、密码和用户名。在本项目中,这些因素如下:

  • 名称 : simpledb

  • 主机:本地主机

  • 密码 : Hmsv1ct0ry1(这来自 hmsvictory,但是第一个字母大写,并且将 I 替换为 1,将 o 替换为 0,作为强密码)

  • 用户 : horatio

为了生成更安全的密码,phpMyAdmin 包含了一个密码生成器。

接下来,我们将使用 phpMyAdmin 创建我们的第一个数据库。

使用 phpMyAdmin 创建数据库

直接打开 phpMyAdmin,如本节开头所述,或者通过 XAMPP 管理页面或 easyPHP 仪表板。请记住,数据库服务器必须正在运行才能使用 phpMyAdmin。

单击顶部菜单中的数据库选项卡。然后你会看到如图 1-13 所示的界面。

img/314857_2_En_1_Fig13_HTML.jpg

图 1-13

用于创建数据库的 phpMyAdmin 接口

键入数据库的名称。对于本例,它将是 simpledb,全部小写。您应该将排序规则更改为 utf8-general-ci;latin1_swedish_ci 的默认值会引起混淆。

注意

排序规则确定字符类型、排序能力、性能和数据的其他特征。排序规则 utf8-general-ci 是一个较旧的标准,可以满足本书中数据示例的需要。您的数据要求可能需要不同的排序规则。

然后单击 Create 按钮(将 Collation 字段设置为 utf8-general-ci)。单击“创建”按钮后,页面将切换到“创建表”页面。然而,首先我们想改变数据库的属性。检查页面顶部(左侧)的左箭头。这将返回到主页。再次单击数据库选项卡。现在,上一页看起来类似于图 1-14 。

img/314857_2_En_1_Fig14_HTML.jpg

图 1-14

在页面的下半部分,选择数据库名称旁边的框

如图 1-14 所示,当您选择新数据库旁边的框时,单击 Check Privileges,您将被带到一个屏幕,在那里您将看到有权访问该数据库的用户列表。为了保证数据库的安全,您必须添加用户名和密码。点击添加用户账号,如图 1-15 所示。

img/314857_2_En_1_Fig15_HTML.jpg

图 1-15

添加用户帐户图标位于该图的底部

点击添加用户帐户将加载添加用户帐户屏幕,如图 1-16 所示。

img/314857_2_En_1_Fig16_HTML.jpg

图 1-16

此屏幕允许您添加用户和密码

警告

添加用户名和密码是必不可少的;否则,您的数据库将是不安全的,容易受到不道德的个人或他们的机器人的攻击。这是最需要培养的习惯。请务必在笔记本上记录用户和密码的详细信息。请确保创建一个包含大小写字母、数字和特殊字符的强密码。做一个详细的记录会让你以后少受几个小时的挫折。

使用下拉菜单,接受第一个字段中的默认使用文本字段,并在它右侧的字段中输入用户名。在标记为主机的第二个字段中,选择本地。单词 localhost 将出现在右边的字段中。Localhost 是您计算机上服务器的默认名称。在第三个字段中输入密码,并在下方的字段中再次输入密码进行确认。如果你想要一些独特和更安全的东西,生成密码按钮将创建一个随机的强密码。

向下滚动到用户帐户的数据库,然后单击授予数据库的所有权限。因为你是站长,你需要能够处理数据库的方方面面;因此,你需要所有的特权。如果添加其他用户,您需要通过取消选择 Drop、Delete 和 Shutdown 等框来限制他们的权限。

向下滚动到表单的底部,然后单击“确定”或“执行”按钮。现在,您已经创建了数据库并保护它免受攻击。数据库可以被视为一个空文件夹,最终将包含一个或多个表。

注意

如果你在使用 phpMyAdmin 的时候迷路了,看不到下一步该做什么,总是点击左边面板顶部的小房子。将鼠标悬停在图标上,以确保它是主屏幕按钮。

现在我们将创建我们的第一个表。

使用 phpMyAdmin 创建一个表

现在让我们使用 phpMyAdmin web 页面将一个或多个数据表插入到数据库中。此页面将使您能够完全控制您的表,包括故障排除和备份。

单击新数据库的名称;你会在左边的面板找到它。然后你会看到如图 1-17 所示的屏幕。

img/314857_2_En_1_Fig17_HTML.jpg

图 1-17

单击页面底部的“Go”按钮创建表格

输入表格的名称,并指定列数。然后单击页面底部的“Go”按钮。你将被带到一个屏幕,显示列翻转 90 度,使列看起来像行;如图 1-18 所示。字段为空,等待您定义表格。

img/314857_2_En_1_Fig18_HTML.jpg

图 1-18

六行代表六列。列标题将输入左侧的字段中。

使用我们之前计划的表 1-2 和 1-3 中的数据,并输入列名、数据类型和字符数。创建用户表的细节在表 1-4 中给出。

表 1-4

用户表的属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 用户 id | 介质 | six | 没有人 | 无符号 | -是吗 | 主要的 | ·······················。 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 注册日期 | DATETIME |   |   |   | -是吗 |   | -是吗 |

除了 user_id 之外,接受每个项目的所有默认设置。在这里,您需要选择 UNSIGNED、PRIMARY 和 type 同时选择 A_I(自动增量)框。如果您发现输入值 6、UNSIGNED 和 PRIMARY 会导致您的 phpMyAdmin 版本产生错误(或警告),那么您可以保留这些项的默认值。如后文所述,当您单击 A_I 时,系统会要求您选择 PRIMARY。MEDIUMINT 将默认为 6 号。

标题类型下的各种类别将在后面解释;标题长度/值指的是最大字符数。registration_date 的长度/值留空,因为长度是预先确定的。不要在标题 Default 和 NULL 下输入任何内容。属性 UNSIGNED 意味着 user_id 整数不能是负数。user_id 的索引是主索引,A_I 表示 id 号的自动递增;当每个用户注册到数据库中时,他们被赋予一个唯一的号码。每增加一个新用户,该数字就增加一。当选择 A_I 复选框时,一个弹出窗口可能会要求您验证它是否是主索引,并再次请求大小。不要在弹出窗口中输入尺寸。如果你这样做,你会得到一个错误。图 1-19 显示了指定属性的屏幕。

img/314857_2_En_1_Fig19_HTML.jpg

图 1-19

此屏幕允许您指定列标题和内容类型

行代表列,它们非常宽;您可能需要水平滚动来输入一些信息。当你向右滚动时,你会发现更多的选项,但是在本教程中我们不需要它们。

你如何填写字段?在标题名称下左侧的字段中输入六个列标题。在标题类型下的第二列字段中选择列的类型。从下拉菜单中选择它们。此表中使用的类型如下:

  • MEDIUMINT 可以存储从负 8,388,608 到正 8,388,607 的整数。如果用户数量永远不会超过 65,535,您可以选择下一个最小的类别 SMALLINT。

  • VARCHAR 指定长度从 0 到 255 的可变字符串。

  • CHAR 是传统上用于密码的字符串。确保这个长度为 60 个字符,以便您的 PHP 代码可以散列密码。当前的 PHP 哈希算法会将密码转换为 60 个字符的哈希字符串。比如说,用户的密码可以是 6 到 12 个字符长,但是它仍然会作为一个散列的 60 个字符的字符串存储在数据库中。这将在第二章中进一步讨论。

    注意在 PHP 的新版本中,散列密码的长度可能会增加。在设置表中该字段的长度之前,请确保研究当前的哈希大小。

  • DATETIME 以 YYYY-MM-DD-HH:MM:SS 格式存储日期和时间。

在标题长度/值下的第三列字段中输入字符数。这些数字请参考表 1-4 。

在标题 Default 下,接受缺省值 None。如果需要,此字段允许您输入默认值。

您可能需要向右滚动以完成字段的填写。在标题“属性”下,使用下拉菜单为 user_id 选择“无符号”。这确保了整数范围从 0 到 16,777,215。负数量不适用于 user_id。

接下来的两个条目只涉及 user_id(图 1-20 )。

img/314857_2_En_1_Fig20_HTML.jpg

图 1-20

user_id 列的两个额外条目

对于 user_id,在标题 Index 下,单击下拉菜单选择 PRIMARY。user_id 应该始终是主索引。

在标题 A_I 下,选择最上面的复选框,这样当每个新记录添加到数据库中时,user_id 号会自动递增。如前所述,您可能会看到一个弹出窗口,要求您验证它是主要的和大小。不要在弹出窗口中输入尺寸。验证和/或添加任何缺失的信息,然后单击确定。

输入表格和图 1-20 中显示的附加信息。完成信息输入后,滚动到底部并单击保存按钮。

警告

如果您忘记为 user_id 选择 A_I 框,当您稍后尝试输入第二条记录时,您将收到一条错误消息。该消息将说明您正在尝试为 user_id 创建一个重复值 0。

有些人喜欢混合使用 GUI 和命令行来对表格进行编程;phpMyAdmin 允许您通过使用 SQL 语言来做到这一点。然而,这本书将主要使用 phpMyAdmin GUI。接下来描述 SQL 替代方法。如果您愿意,可以跳过这一节,但是我们建议您以后再来看这一节,因为您肯定会在其他更高级的书籍和教程中遇到 SQL。

SQL 替代方案

下一节描述了使用 phpMyAdmin 创建数据库和表的一种稍微快捷的方法。SQL 代表结构化查询语言;它是 MySQL、MariaDB 和许多其他数据库的官方语言。你会很高兴看到它使用简单的英语命令。唯一的问题是,在 SQL 窗口中比在图 1-19 中显示的 phpMyAdmin 接口中更容易产生打字错误或拼写错误。

使用 SQL,可以创建一个带有密码和用户名的数据库。这样就省了好几个步骤。

我们假设您已经创建了数据库 simpledb,所以我们不能再使用这个名称。假设管理员(Adrian)想要使用以下信息创建一个名为 members 的数据库:

  • 数据库名称:成员

  • 特权:全部

  • 用户名:阿德里安

  • 密码:订书机 12

图 1-21 显示了在 SQL 窗口中创建数据库的代码。

img/314857_2_En_1_Fig21_HTML.jpg

图 1-21

SQL 窗口

在 phpMyAdmin 中,在窗口左侧面板的数据库“树”上单击 New,通知 phpMyAdmin 您不再使用 simpleDB 数据库。单击 SQL 选项卡(显示为圆圈)以显示 SQL 窗口。

图 1-21 中显示的细节必须按以下格式输入:

CREATE DATABASE members;
GRANT ALL
ON members.*
TO 'adrian'
IDENTIFIED BY 'stapler12';

通过在每行末尾按 Enter 键,可以在单独的行上输入每一项。SQL 关键字(如 CREATE DATABASE)传统上是大写的。其他项目通常以小写字母输入。注意分号和单引号——它们很重要。当您对条目感到满意时,单击 Go 按钮。

如果输入的代码正确,将显示两条查询语句,表明数据库已创建,adrian 作为用户被附加。它们将指示空的结果集。这是可以的,因为数据库中不存在任何东西。

现在,我们将使用 SQL 窗口在成员数据库中创建一个名为 users 的表。

单击 phpMyAdmin 左侧面板上的数据库“树”中的成员数据库。如果成员数据库没有出现,请刷新页面使其出现。打开 SQL 窗口,输入以下内容:

CREATE TABLE users (
user_id MEDIUMINT (6) UNSIGNED
AUTO_INCREMENT,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(40) NOT NULL,
email VARCHAR(50) NOT NULL,
password CHAR(60) NOT NULL,
registration_date DATETIME,
PRIMARY KEY (user_id)
);

前面代码中显示的 NOT NULL 将要求在字段中输入数据。这是必填字段。在本例中,所有字段实际上都是必需的,因为 registration_date 是自动填充的。

图 1-22 显示了 SQL 窗口中的详细信息。

img/314857_2_En_1_Fig22_HTML.jpg

图 1-22

在 phpMyAdmin 的 SQL 窗口中创建一个表

注意括号都是普通括号,不是花括号。在每一行之后按 Enter 键,并且记住在最后一行的末尾加上右括号和分号。每一项都用逗号分隔(第 3 行到第 8 行);如果你的表格有六列,你应该有六个逗号。单击 Go 按钮,表将被创建。

小费

我们鼓励您探索刚刚描述的 SQL 主题。在未来的某个时候,使用 SQL 的能力将是一个有用的选择。你可以通过搜索互联网找到许多免费教程。

删除数据库和表

在学习时,初学者通常需要在创建数据库或表后重新开始,并可能希望删除以前的尝试。当您第一次使用 phpMyAdmin 时,您可能会忘乎所以,创建几个数据库和表。然后你可能会决定清理这些乱七八糟的东西,删除一些。

让我们看看如何删除数据库。如果 phpMyAdmin 尚未打开,请使用前面显示的方法之一启动它。选择数据库选项卡(如图 1-23 所示),然后选择要删除的数据库旁边的框。

img/314857_2_En_1_Fig23_HTML.jpg

图 1-23

删除数据库

选择要删除的数据库后,单击 Drop 图标(在右下角)。将询问您是否确实要删除该数据库;继续并完成删除。与该数据库相关的所有内容都将被删除,包括它的表。如果成功,会有一条消息告诉您一个数据库已被删除。

您可能希望保留一个数据库,但删除它的全部或一个表。在 phpMyAdmin 中,在左侧面板的“树”中选择数据库。在下一个屏幕中,您将看到表格;选择要删除的表旁边的框。图 1-24 显示了 simpledb 数据库的用户表。

img/314857_2_En_1_Fig24_HTML.jpg

图 1-24

在 phpMyAdmin 中删除表

选择要删除的表后,单击拖放图标(位于表名右侧)。系统会询问您是否真的要删除这些表。您可以在删除和取消之间进行选择。

摘要

在这一章中,我们定义了一个数据库,然后看了两个用于开发和测试数据库和 PHP 文件的免费平台。我们希望你成功下载并安装了 XAMPP 或 easyPHP。然后,我们研究了 phpMyAdmin,并学习了如何使用它来创建数据库和表。SQL 被研究作为创建数据库和表的替代方法。我们学习了如何使用用户 ID 和密码来保护 phpMyAdmin 和数据库。我们发现了如何使用 phpMyAdmin 删除数据库和表。在下一章,我们将创建并测试简单的交互式网页。

二、创建与用户互动的网页

本章将演示如何将一个简单的数据库链接到一个网页上,这样网页就可以与用户交互。公众可以访问网站,但不能访问数据库结构或表格中的数据。他们将不被允许查看受保护的页面,例如会员专用页面,因为他们在注册之前没有密码。但是,网站设计者必须为用户提供一种与数据库交互的方式,以便注册成为会员、搜索数据库和更改密码。PHP 语言和 SQL 代码将提供解决方案。

完成本章后,您将能够

  • 为网站创建数据库和模板

  • 理解和使用 PHP 包含函数

  • 了解服务器如何处理 PHP 页面

  • 创建交互式模板页面

  • 创建 mysqli 代码来连接数据库

  • 为组织成员创建注册页面

  • 理解并使用 PHP echo()语句

  • 创建粘性表单

  • 使用简单数组

  • 创建显示成员记录的表单

  • 创建哈希密码的代码

对于我们的交互式 web 页面,我们将使用 simpledb 数据库和以前项目中的 users 表。请注意,本教程既不实用也不安全。它是后续章节中描述的更安全、更雄心勃勃的项目的垫脚石。实际上,您永远不会允许普通成员查看成员列表。该项目的互动要素如下:

  • 用户可以通过在屏幕上显示的表格中插入他们的详细信息来注册成为会员。注册细节将被输入到数据库表中,并可由管理员用来发送定期通讯给成员。

  • 注册用户可以更改他们的密码。

  • 用户可以查看成员列表(仅限于此项目)。在后面的章节中,这个功能将只对网站管理员和会员秘书开放。

使这个例子不适合真实世界的特征如下:

  • 没有规定注册成员随后登录进入一个特殊的部分或页面。这将在第三章中讨论。

  • 用户应该永远不能访问成员的详细信息表。

  • 在这个早期阶段,为了简单起见,提供了对用户信息的有限过滤。因此,该表可能包含错误的数据和伪造的电子邮件地址。

  • 仅在本章中,任何知道成员的电子邮件地址和旧密码的用户都可以更改成员的密码。

所有这些安全问题将在后面的章节中讨论。尽管有缺点,该项目将为您提供编码和测试页面的宝贵实践。您还将学习更多的数据库术语和一些基本的 PHP 代码。

创建用于保存数据库页面的文件夹

在 XAMPP 的 htdocs 文件夹或者 easyPHP 的 eds-www 文件夹中,新建一个名为 simpledb 的子文件夹。本章中创建的所有页面都将放在 simpledb 文件夹中。您可以选择从提供的清单中手工编码文件,或者将书的代码加载到 simpledb 文件夹中。(你可以从 Apress 下载代码。com 。)我们建议您手工编写本章的程序;这些文件很小,创建时间不会太长。如果您键入并测试代码,您将会学到更多并学得更快,尤其是当您犯了错误并学会纠正它们时。

创建临时模板

显然,交互式数据库的某些方面必须是用户可以访问的。这意味着将它整合到现实世界的网页中。我们将把我们的网页命名为template.php(如图 2-1 )。正如您所看到的,有一个主标题、一些正文、左侧的导航侧栏、右侧的信息栏和页面底部的页脚。

img/314857_2_En_2_Fig1_HTML.jpg

图 2-1

模板

在清单 2-1 中,head 部分包含 DOCTYPE、页面标题和到 Bootstrap 样式表的链接。页面的主体包含一些 PHP 代码,这将在清单的最后一步一步解释。

因为文件包含 PHP 代码(不管有多少),所以文件以文件类型保存。php

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File  -->
  <link rel="stylesheet"
  href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
</head>

<body>
<div class="container" style="margin-top:30px">

<!-- Header Section                                                                 #1-->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);padding:20px;">
  <?php include('header-for-template.php'); ?>
</header>

<!-- Body Section                                                                   #2-->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
        <?php include('nav.php'); ?>
      </ul>
  </nav>

<!-- Center Column Content Section -->
  <div class="col-sm-8">
     <h2 class="text-center">This is the Home Page</h2>
        <p>The home page content. The home page content. The home page content. The home page content. <br>
        The home page content. The home page content. The home page content. The home page content. <br>
        The home page content. The home page content. <br>
        The home page content. The home page content. The home page content. </p>
  </div>

<!-- Right-side Column Content Section                                              #3-->
   <aside class="col-sm-2">
       <php include('info-col.php'); ?>
   </aside>
 </div>

<!-- Footer Content Section                                                         #4-->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 2-1Creating a Template for the Project (template.php)

注意

标签让服务器知道 PHP 代码在哪里结束。服务器会将这些标签之间的任何代码传递给 PHP 解释器来执行。标签之外的代码,HTML 和 JavaScript 代码,最终将被发送到用户的浏览器,不做任何处理。

清单 2-1 中显示的 PHP 代码包含 include()函数,现在将对其进行解释。(严格来说,include()函数是一个 PHP 语言构造,但区别很小,为了简单起见,我们将继续称它为函数。)

PHP include()函数简介

你会注意到清单 2-1 中似乎没有足够的代码来创建图 2-1 中显示的页面。原因如下。

在清单 2-1 中的标记之间显示的四段 PHP 代码都将一个额外的文件拖入页面。该页面由五个文件组成:一个主文件和四个外部文件。使用 PHP include()函数将四个外部文件拉入模板页面。

对于更新和维护网站来说,include()函数是一个非常好的省时工具。假设您有一个 40 页的客户端网站,每个页面都需要相同的菜单按钮块。如果您的客户要求您添加或删除一个菜单按钮,通常您将不得不修改 40 个页面来添加或删除每个页面上的按钮。使用 include()函数,您可以设计网站,使每个页面都包含这一行 PHP 代码:。这将把菜单按钮块拉进每个网页。你可以在另一个名为menu.php的文件中设计按钮块。要向所有 40 个页面添加一个新按钮,只需将新按钮添加到名为menu.php的文件中。

注意

PHP 函数是一个微小的程序,它将执行特定的任务。include()函数获取括号中的内容,并将其放入页面中 include()函数所在的位置。PHP 有两个类似的函数:include()和 require()。它们都将文件拖入页面,以便该文件包含在显示的页面中。区别在于他们对丢失或有问题的文件的反应方式。如果要包含的文件丢失或损坏,include()不会暂停页面的执行。将出现警告,但页面将继续加载。相反,如果 require()找不到文件或者文件有问题,就会出现错误。在这种情况下,页面将停止执行。使用 include()来处理任何对网页正常处理来说不是绝对必要的内容。在这个例子中,没有合适的标题,页面仍然可以运行。但是使用 require()来加载数据库的详细信息,因为如果数据库无法打开,这个页面就没什么用了。此外,PHP 还有 include_once()和 require_once()函数。这两个函数将确定该文件之前是否已经被包含。如果文件之前已经被包含,include_once()将不会执行,没有抱怨。require_once()不会再次加载已经加载过的文件。

要放入页面的四个元素是标题、菜单按钮块、右侧的信息面板和页脚。包含的文件可以是任何类型——例如,一个. txt 文件,一个。php 文件,或者一个。html 文件。请注意,您选择的文件类型会影响文件中的代码是否可以从外部查看。HTML 文件可以很容易地在浏览器中查看。但是,不要依赖文件结尾的类型来判断文件是否应该受到保护。确保您的代码位于安全的文件夹中。include()语句可以放在 HTML 页面中的任何地方,只要它被 PHP 标记包围;这些以标签<开头?php 并以标签结束?>。下一节将详细介绍四个包含的外部文件。

你可能也想知道为什么我们不使用内容管理系统(CMS ),比如 WordPress,来组装和显示我们的页面。首先,这不是一本 CMS 书,其次,你实际上是在学习 CMS 系统是如何工作的。它们以相似的方式组合页面。一些 CMS 系统是用 PHP 创建的。

包含的头文件

图 2-2 显示了包含的头文件。

img/314857_2_En_2_Fig2_HTML.jpg

图 2-2

模板的标题

这个标题是临时的。当我们创建模板的交互式版本时,新的标题将包含一个附加菜单,以便用户可以注册,从而将他们的详细信息插入 simpledb 数据库。

警告

现在还不要试图创建四个非交互式页面(page-2.php、【page-3.php、page-4.phppage-5.php),因为在本章的后面,这些页面将需要一个新版本的页眉。

<!-- Header Section                                                                 #1-->
<header class="jumbotron text-center row"
   style="margin-bottom:2px; background:linear-gradient(white, #0073e6); padding:20px;">
   <?php include('header-for-template.php'); ?>
</header>

Listing 2-2aHeader Section of Template File (template.php)

清单 2-2a 中的 PHP 语句将 header-for-template 文件的内容导入页面(代码如清单 2-2b 所示)。此外,引导 CSS 代码被导入到模板文件的 head 部分(参见清单 2-1 的顶部)。使用 Bootstrap,我们可以减少开发的 CSS 数量,并自动创建适应多种屏幕尺寸(包括智能手机)的页面。Bootstrap jumbotron 类产生一个大盒子,用于显示重要信息(如标题)。我们还将文本居中,并将标题声明为一行。引导行由网格控制。每一行都被分成单元格,以帮助页面布局。这类似于电子表格中的单元格和行。我们将确定清单 2-2b 中标题的每一列使用的单元格数量。我们对大屏幕的默认设置做了一些调整,减少了下边距(2px),将背景更改为渐变(白色到蓝色),并更改了填充(20px)。清单 2-2b 包含了临时头的代码。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="font-bold">Header Goes Here</h1>
</div>

Listing 2-2bCode for the Temporary Header (header-for-template.php)

在清单 2-2a 中,header 标签中引用了 class 行。这允许我们用列来设计引导行的样式。在清单 2-2b 中。我们创建两列:一列保存徽标,另一列保存标题文本。徽标列使用两个小单元格(col-sm-2),标题文本使用八个小单元格(col-sm-8)。img 标签还包括一个 img-fluid 类,以及 float-left 类,它允许引导代码在较小的设备上调整图像的大小。标题文本也被加粗(字体加粗)。

这本书假设你有一些 CSS 和 Bootstrap 编程知识。我们将在每章和附录中提供一些链接,以提供额外的资源。然而,一个很好的学习方法是调整现有的代码,看看它如何影响布局。您可能需要花几分钟时间来调整这个示例和下面的示例,看看它们将如何改变 web 页面的布局。

注意

我们选择使用 Bootstrap 来减少我们必须演示的 CSS 代码量。Bootstrap 是一个开源工具包,包括 HTML、CSS 和 JavaScript。这个工具包提供了一个响应式网格系统和预构建的 CSS/JS 组件。使用 Bootstrap,开发人员可以在 HTML、CSS 和 JavaScript 编码上花费更少的时间,而在编程(使用 PHP)上花费更多的时间。

在本书中,我们将使用 Bootstrap 类来提供网页的多平台格式。工具包可以直接在线链接(如我们在示例中所做的那样)或从 Bootstrap 网站下载。您可以使用 CSS 调整许多工具包组件的默认设置。如果您不熟悉 Bootstrap,我们建议您查看并参考以下站点以了解更多信息:

www.getboostrap.com

https://www.w3schools.com/bootstrap/bootstrap_get_started.asp

包含的菜单文件

图 2-3 显示了包含的菜单按钮块。

img/314857_2_En_2_Fig3_HTML.jpg

图 2-3

包含的菜单按钮

清单 2-3a 给出了模板页面中菜单的 HTML 代码。

<!-- Body Section                                                                      #2-->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
       <?php include('nav.php'); ?>
      </ul>
  </nav>

Listing 2-3aNavigation Menu of Template File (template.php)

与页眉类似,页面的主体部分定义了一行。一行有三列。左栏(col-sm-2)为导航菜单保留了两个单元格。菜单将以“药丸”格式显示(见图 2-3 ),并且对于不同的设备尺寸是灵活的。一条 PHP 语句导入 nav.php 的文件,该文件包含链接的实际代码清单(参见清单 2-3b )。

  <li class="nav-item">
          <a class="nav-link active" href="index.php">Home</a>
  </li>
  <li class="nav-item">
          <a class="nav-link" href="page-2.php">Page 2</a>
   <li>
   <li class="nav-item">
          <a class="nav-link" href="page-3.php">Page 3</a>
   </li>
   <li class="nav-item">
          <a class="nav-link" href="page-4.php">Page 4</a>
   </li>
   <li class="nav-item">
          <a class="nav-link" href="page-5.php">Page 5</a>
     </li>

Listing 2-3bThe Navigation Menu (nav.php)

导航部分(菜单)的代码包括一个无序列表,该列表中有指向网站每个未来页面的链接。这是用无序列表创建菜单的常见做法。每个列表项都定义了一个类 nav-item,Bootstrap 使用它来格式化菜单。每个链接( 2-3 )。还有一个使菜单项变灰的 deactive 类。

包含的信息列

包含信息栏位于页面右侧,如图 2-4 所示。

img/314857_2_En_2_Fig4_HTML.jpg

图 2-4

模板中包含的信息列

清单 2-4a 给出了模板文件中信息列的 HTML 代码。

<!-- Right-side Column Content Section                                                 #3-->
   <aside class="col-sm-2">
         <php include('info-col.php'); ?>
   </aside>

Listing 2-4aInformation Column in Template File (template.php)

信息栏保留两个小单元格用于显示(col-sm-2)。PHP include 语句从info-col.php获取信息列的细节(参见清单 2-4b )。

<div>
<h3>This is the information column</h3>
        <p>Web design by <br>A W West and<br>Steve Prettyman</p>
</div>

Listing 2-4bThe Information Column Details (info-col.css)

清单 2-4b 包括在页面右侧显示信息的 HTML 代码。信息移动到右侧,因为在定义信息列之前,菜单和页面的主要部分(稍后讨论)首先用列和单元格定义。

包含的页脚文件

图 2-5 显示了包含的页脚。

img/314857_2_En_2_Fig5_HTML.jpg

图 2-5

模板中包含的页脚

清单 2-5a 给出了页脚的 HTML 代码。

<!-- Footer Content Section                                                            #4-->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>

Listing 2-5aFooter code for Template File (.php)

页脚也被声明为一个超大屏幕和一行。文本居中(文本居中)。此外,超大屏幕的默认设置被调整为减小块大小,以更适合页脚。PHP 代码获取要显示的 HTML(参见清单 2-5b )。

<p class="h6 col-sm-12">Copyright &copy; Adrian West & Steve Prettyman 2018  Designed by
<a href="http://www.colycomputerhelp.co.uk/">Adrian West </a> and
<a href=http://www.littleoceanwaves.com>Steve Prettyman </a>  Valid
<a href="http://jigsaw.w3.org/css-validator/">CSS</a> &amp;
<a href="http://validator.w3.org/">HTML5</a></p>

Listing 2-5bThe Footer (footer.php)

清单 2-5b 使用 Bootstrap 类 h6 来确定用于显示文本的字体属性。段落标记还保留了 12 个小列(页面的完整宽度)来显示页脚信息。

服务器如何处理页面?

当 HTML 文件保存为 PHP 文件时,。php 扩展提醒服务器像平常一样处理 HTML,但是也寻找任何 php 代码。默认情况下,服务器处于 HTML(复制)模式,任何 HTML 代码都照常发送到浏览器。它什么时候找到了<?php 标签,服务器切换到 PHP(解释)模式。任何 PHP 代码都被解释。执行解释的 PHP 代码的结果被返回到页面,而不是实际的 PHP 代码本身。它继续以这种模式运行,直到遇到 PHP 结束标记?>;然后切换回 HTML(复制)模式。这种循环行为一直持续到代码页的末尾。

模板的交互式版本

在这个版本的模板中,我们将通过创建一个带有附加菜单的新头文件来引入交互性。该菜单将允许用户注册并将他们的详细信息输入 simpledb 数据库。它还允许用户更改他们的密码和查看成员表。

图 2-6 显示了新的割台。

img/314857_2_En_2_Fig6_HTML.jpg

图 2-6

标题中会添加一个注册菜单

交互式元素将嵌入标题中;因此,前面的标题现在被修改为包含一个菜单。新的表头将被命名为header.php,代码如清单 2-6 所示。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
         <div class="btn-group-vertical btn-group-sm" role="group" aria-label="Button Group">
  <button type="button" class="btn btn-secondary" onclick="location.href = 'register-page.php'" >
                   Register</button>
  <button type="button" class="btn btn-secondary" onclick="location.href = 'register-view_users-page.php'">
                  View Users</button>
<button type="button" class="btn btn-secondary" onclick="location.href = 'register-password.php'" >
                  New Password</button>
</div>
    </nav>

Listing 2-6Placing a Registration Menu in the New Header (header.php)

在本例中,创建了一个引导垂直按钮组(btn-group-vertical ),其中包含一些小按钮(btn-group-sm)。该组中的每个按钮都用 HTML 按钮标签和 Bootstrap btn 和 btn-secondary 类来定义。点击属性用于调用用户请求的页面。在后面的章节中,我们还会看到使用引导导航条在标题下创建水平按钮栏的例子。

在模板文件中,我们需要用新的头替换以前包含的头。新的模板页面将被命名为index.php,它现在有两块菜单按钮,如图 2-7 所示。

img/314857_2_En_2_Fig7_HTML.jpg

图 2-7

带有两个菜单的新主页模板

旧模板和新索引文件之间代码的唯一区别是新的头引用。清单 2-7 中的代码片段显示了这些变化。完整的代码包含在 index.php 的文件中,可以从网站下载。com 网站。

<!-- Header Section -->
<header class="jumbotron text-center row"
    style="margin-bottom:2px; background:linear-gradient(white, #0073e6); padding:20px;">
    <?php include('header-for-template.php'); ?>
</header>

Listing 2-7Including the New Header in the Home Page (index.php)

现在你可以创建四个普通的网页,使用新的模板(index.php);文件保存四份,分别命名为 page-2.php、**、。稍微改变一下这些页面的内容,使它们不同于index.php,并指出用户正在查看的页面。您可能会考虑将活动的导航项目(按钮)更改为当前正在查看的页面。前面显示的代码将主页导航按钮设置为活动状态。

连接到数据库

在我们可以在数据库中做任何事情之前,我们必须连接到它。这是通过创建一个名为 mysqli_connect.php 的连接文件来实现的。下一个代码片段给出了连接文件的代码。

连接到数据库的代码片段清单(mysqli_connect.php)

<?php
// This file provides the information for accessing the database.and connecting to MySQL.
// First, we define the constants:                                                     #1
Define ('DB_USER', 'horatio'); // or whatever userid you created
Define ('DB_PASSWORD', 'Hmsv1ct0ry'); // or whatever password you created
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'simpledb');

// Next we assign the database connection to a variable that we will call $dbcon:      #2
try
{
      $dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
      mysqli_set_charset($dbcon, 'utf8'); //                                           #4
      // more code will go here later
}
catch(Exception $e) // We finally handle any problems here                             #3
{
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
}
catch(Error $e)
{
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
}
?>

将文件另存为 mysqli_connect.php ,并将其放在 XAMPP htdocs 或 easyPHP eds-www 文件夹中。

注意

当数据库设置在远程主机上时,为了安全起见,该文件被放置在根文件夹的上一级。不保护该文件会导致 DOS 攻击。将该文件放在 web 服务器的公共区域将允许任何人使用该代码访问数据库。将文件放在程序代码的上一层,放在 web 服务器保护的区域中(或另一个安全文件夹中)。使用类似 require('的 require 语句../MySQL connect . PHP’);访问您的连接信息。本书中的示例从与代码文件相同的位置访问连接文件。这样做是为了便于测试,但不应该在公开可访问的 web 服务器上的真实环境中进行。

在本书中,catch 消息用于调试。实时网站不应该向用户显示错误消息。应该向用户显示一条通用消息(如前面的清单所示)。比起显示错误信息的网站,用户更愿意回到当前“繁忙”的网站。这些错误消息也可能是一个安全漏洞,因为可能会显示可能导致错误的代码行。

代码的解释

mysqli_connect.php 文件包含一些您可能不熟悉的代码约定,所以让我们在这里简单地浏览一下。

单行注释以双正斜杠或哈希符号开头,例如:

// more code will go here later
# more code will go here later

常量是使用关键字DEFINE创建的固定定义。

DEFINE ('DB_USER', 'horatio');

$dbcon这样的变量是用于包含可以改变的信息的记忆存储设备;它们前面有一个美元符号。使用以下格式创建变量:\(var = some_information。等号叫做*赋值运算符*;它将等号右边的值赋给左边的变量。该示例将一些信息分配给变量\)var。

我们现在将使用行号作为参考来检查代码。

// First, we define the constants:                                                     #1
Define ('DB_USER', 'horatio'); // or whatever userid you created
Define ('DB_PASSWORD', 'Hmsv1ct0ry'); // or whatever password you created
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'simpledb');

作为惯例,而不是规则,程序员创建的常量名称都是大写字母。这有助于识别哪些项是常量(程序运行时不能更改),哪些项是变量(程序运行时可以更改)。在 PHP 中,如果一个项目的第一个字符是美元符号,我们也可以知道它是一个变量。将定义代码行放在程序代码的顶部附近是一个很好的做法。这使得您很容易找到何时必须更改某个值。例如,如果您需要更改税率。

注意

如果没有为 simpledb 数据库创建用户 id 和密码,用户 ID 将是 root,密码将是" "(两个引号,中间没有空格)。

// Next we assign the database connection to a variable that we will call $dbcon:      #2
try
{
      $dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
      // more code will go here later
}

美元符号后面的文本可以是与存储器中保存的信息相关的任何内容。例如,清单中用于连接数据库的变量名为\(dbcon。这被选择来表示到数据库的连接;它可以是\)connect_db 或任何其他表示数据库连接的文本。一些程序员喜欢使用 camel case (\(dbCon),其中第一个单词是小写的,后面的任何单词都以大写字母开头。其他人保持所有单词小写,有些人甚至在单词之间使用下划线(_)(\) db _ con)。你使用什么风格并不重要。保持一致,用相同的风格命名变量。

我们使用之前定义的主机、用户名、密码和数据库名称来连接数据库。

try 块(在{ }之间的所有内容都被认为是在一个块中)将把任何问题(错误或异常)都“抛出”到 catch 块中,供程序处理。

catch(Exception $e) // We finally handle any problems here                             #3
{
      //print "An Exception occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
}
catch(Error $e)
{
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
}

出于测试目的,有时我们会显示可能出现的错误和异常消息。在生产中,我们会将打印语句替换为给用户的通用消息,例如“系统当前不可用,请稍后再试。”允许用户看到错误信息会给他们一种感觉,你的网站设计很差,很脆弱,经常会崩溃。在网站上看到错误信息的用户通常会离开网站,并且不再返回。然而,在今天的环境中,常见的“系统当前繁忙或不可用”类型的消息并不罕见。许多网站现在限制用户数量,以防止黑客试图淹没和破坏网站。

让用户看到任何错误消息也是一个主要的安全漏洞。这些消息可能会泄露提供项目实际位置的代码,例如数据库本身。这些信息以及时间戳应该被传递到一个日志文件中,以帮助系统管理员。

您可能会在互联网或较旧的书籍中看到使用或 die 的 PHP 数据库代码的示例代码。从一开始,这就是 PHP 的标准。然而,随着 PHP 7 的发布,设计者鼓励所有开发人员使用行业标准的 try/catch 格式。此外,当使用或 die 时,您无法捕获所有异常和错误。使用 try/catch,您现在可以这样做。在 PHP 7 中,开发人员被鼓励使用 mysqli(而不是 mysql)方法来连接和访问数据库。这被认为是最安全的访问方法。

我们已经将 HTML 编码的语言从默认的改为 utf-8,如清单 2-8 所示。到数据库的连接还必须包括指示正确编码语言的代码。HTML 页面上使用的编码语言类型必须与数据库表中使用的编码语言相匹配。不一致的匹配可能会导致问题,包括无效的搜索结果。

mysqli_set_charset($dbcon, 'utf8');                                                    #4

(请注意,这种格式不同于 HTML 文档中的代码集,因为它不像 utf-8 那样包含连字符。)

接下来,我们需要一些页面来调用标题的新菜单。这些页面将包含互动功能。这些页面将允许用户注册,允许用户查看已注册人员的表格,并允许更改密码。

注册页面

警告

当您最终将数据库迁移到远程主机时,请确保遵守您所在地区或国家的数据保护法,并在注册页面上明确声明用户的个人详细信息不会被共享或出售给其他组织。保护数据的规则因国家而异,您必须阅读并遵守这些规则。通常,组织内任何可以访问用户详细信息的人都必须签署一份文件,同意永远不共享个人信息。管理数据保护法的政府机构可能需要缴纳年度注册费。这些预防措施不适用于使用本书中描述的虚构数据的实验数据库。

注册页面允许用户将他们的个人信息直接输入到数据库的表格中。图 2-8 显示了该界面。

img/314857_2_En_2_Fig8_HTML.jpg

图 2-8

注册页面

当用户点击新标题上的注册菜单按钮时,显示如图 2-8 所示的页面。如果用户正确地填写了表单,然后单击 Register 按钮(在输入字段下面),用户的输入将被输入到数据库的 users 表中。然后显示“谢谢”页面。

请注意,注册页面上的注册和新密码按钮现在是多余的,因为用户已经访问了注册页面。显然,用户还没有要更改的密码。多余的按钮将留在本章所有示例的位置,以避免使说明复杂化。多余的按钮将在接下来的两章中删除或更改。

现在我们将检查整个注册页面的代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
<script src="verify.js"></script>
</head>
<body>
    <div class="container" style="margin-top:30px">
    <!-- Header Section -->
    <header class="jumbotron text-center row"
         style="margin-bottom:2px; background:linear-gradient(white, #0073e6); padding:20px;">
         <?php include('header.php'); ?>
    </header>
    <!-- Body Section -->
        <div class="row" style="padding-left: 0px;">
    <!-- Left-side Column Menu Section -->
     <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
          <?php include('nav.php'); ?>
      </ul>
    </nav>

    <!-- Validate Input
   <?php
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {                                       //#1
     require('process-register-page.php');
    } // End of the main Submit conditional.
   ?>
   <div class="col-sm-8">
      <h2 class="h2 text-center">Register</h2>

  <form action="register-page.php" method="post" onsubmit="return checked(); >     <!-- #2 -->
     <div class="form-group row">

<label for="first_name" class="col-sm-4 col-form-label">First Name:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="first_name" name="first_name"
          placeholder="First Name" maxlength="30" required
          value="<?php if (isset($_POST['first_name'])) echo $_POST['first_name']; ?>" >
    </div>
  </div>

  <div class="form-group row">
    <label for="last_name" class="col-sm-4 col-form-label">Last Name:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="last_name" name="last_name"
          placeholder="Last Name" maxlength="40" required
          value="<?php if (isset($_POST['last_name'])) echo $_POST['last_name']; ?>">
    </div>
  </div>

<div class="form-group row">
    <label for="email" class="col-sm-4 col-form-label">E-mail:</label>
    <div class="col-sm-8">
      <input type="email" class="form-control" id="email" name="email"
          placeholder="E-mail" maxlength="60" required
          value="<?php if (isset($_POST['email'])) echo $_POST['email']; ?>">
    </div>
  </div>

<div class="form-group row">
    <label for="password1" class="col-sm-4 col-form-label">Password:</label>
    <div class="col-sm-8">
      <input type="password" class="form-control" id="password1" name="password1"
          placeholder="Password" minlength="8" maxlength="12"
          required value="<?php if (isset($_POST['password1'])) echo $_POST['password1']; ?>">
          <span id="message">Between 8 and 12 characters.</span>
  </div>

<div class="form-group row">
    <label for="password2" class="col-sm-4 col-form-label">Confirm Password:</label>
    <div class="col-sm-8">
      <input type="password" class="form-control" id="password2" name="password2"
          placeholder="Confirm Password" minlength="8" maxlength="12" required
          value="<?php if (isset($_POST['password2'])) echo $_POST['password2']; ?>">
    </div>
  </div>

<div class="form-group row">
    <div class="col-sm-12">
       <input id="submit" class="btn btn-primary" type="submit" name="submit" value="Register">
    </div>
 </div>
</form>
</div>

!-- Right-side Column Content Section                                            #3 -->
<?php
if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
                style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('footer.php');
 ?>
</div>
</body>
</html>

Listing 2-8aCreating

the Registration Page (register-page.php)

代码的解释

代码的基本结构与索引文件相同。差异出现在主体中,特别是现在包含供用户注册的表单的中间列。

<?php
 if ($_SERVER['REQUEST_METHOD'] == 'POST') {                                     //#1
  require('process-register-page.php');
 } // End of the main Submit conditional.
?>

在 PHP 中,可以通过表单提交属性将信息传递给一个单独的 PHP 程序来处理表单,也可以通过在表单所在的文件中包含 PHP 代码来处理表单。在这个例子中,从技术上讲,我们将 PHP 代码包含在同一个文件中,因为我们从 process-register-page 文件中提取代码。我们也知道这一点,因为表单的 action 属性调用的是文件本身(form action="register-page.php "),而不是另一个文件(参见下面的部分清单)。如果点击了 submit 按钮,就会执行进程寄存器文件中的 PHP 代码。如果用户没有单击 submit 按钮(可能是第一次显示页面),就会显示表单。让我们来看看表单本身。

<form action="register-page.php" method="post" onsubmit="return checked();>     <!-- #2 -->
     <div class="form-group row">

<label for="first_name" class="col-sm-4 col-form-label">First Name:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="first_name" name="first_name"
          placeholder="First Name" maxlength="30" required
          value="<?php if (isset($_POST['first_name'])) echo $_POST['first_name']; ?>" >
    </div>
  </div>

当用户单击提交按钮时,注册页面会提交给自己。提交时,表单调用 JavaScript 函数 checked()来验证密码。此函数的代码如下所示:

function checked() {
  if (document.getElementById('password1').value ==
    document.getElementById('password2').value) {
    document.getElementById('message').style.color = 'green';
    document.getElementById('message').innerHTML = 'Passwords match';
        return true;
  } else {
    document.getElementById('message').style.color = 'red';
    document.getElementById('message').innerHTML = 'Passwords do not match';
        return false;
  }
}

这段代码是从 verify.js 程序和引导 CSS 代码一起导入的。如果密码匹配,则提交代码。如果密码不匹配,将显示一条错误消息,允许用户更正问题。

我们接下来将看到的 PHP 代码将验证用户提供的信息,并将其插入数据库。表单的每个元素(比如 first_name textbox)中都会发生很多事情。first_name 文本框附有一个标签,该标签使用了四个小的 Boostrap 网格单元。文本框使用八个小网格单元。Boostrap 使用 form-control 类格式化表单和文本框。用户可以输入的名字的最大长度是 30 个字符。required 属性告诉 HTML,在提交信息之前,用户必须在 textbox 中输入信息。

标记为#2 的一行 PHP 代码创建了一个 sticky 元素,保存用户输入的任何内容。这允许页面在重新加载时保留任何条目。每次用户单击提交按钮,页面都会被重新加载。通常这将清除所有条目,因为 HTML 页面不保留信息(断开的数据)。当用户提交表单时,信息通过 POST 方法传递给服务器。我们可以使用\(_POST 方法和元素名称(\)_POST['first_name'])检索表单传递的任何元素。PHP 代码使用 isset 来确定 first_name 文本框中是否有条目。如果有条目,传递的值将被粘回到文本框中。这允许用户看到他们最初输入的内容,并对任何错误消息做出响应和纠正问题,而不必完全重新输入条目。其他文本框的工作方式类似。

注意

将$_POST 中的信息直接显示在网页上可能会带来风险,黑客可以利用它来操纵网页、尝试 CSRF/XSS 攻击以及操纵数据库中的数据。随着章节的进展,数据卫生和安全性将会提高,以减少安全漏洞的机会。本章中的代码不适合在实时网站上使用。

!-- Right-side Column Content Section                                                 #3 -->
<?php
if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
                style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('footer.php');

如果输入的信息未通过验证,将会产生错误消息。正如您将在即将解释的 PHP 代码中看到的,大多数错误消息都放在变量$errorstring 中。如果此变量存在,则存在一个或多个错误。当这种情况发生时,右列(参见前面代码中的#3)不会显示,以便为错误消息提供空间。页脚的列大小也略有调整。

现在让我们看看验证用户提供的信息的 PHP 代码。

<?php
// This script is a query that INSERTs a record in the users table.
// Check that form has been submitted:
        $errors = array(); // Initialize an error array.                                #1
       // Check for a first name:                                                       #2
       $first_name = trim($_POST['first_name']);
       if (empty($first_name)) {
              $errors[] = 'You forgot to enter your first name.';
       }
                // Check for a last name:
       $last_name = trim($_POST['last_name']);
       if (empty($last_name)) {
              $errors[] = 'You forgot to enter your last name.';
       }
       // Check for an email address:
       $email = trim($_POST['email']);
       if (empty($email)) {
              $errors[] = 'You forgot to enter your email address.';
       }
       // Check for a password and match against the confirmed password:               #3
       $password1 = trim($_POST['password1']);
       $password2 = trim($_POST['password2']);
       if (!empty($password1)) {
              if ($password1 !== $password2) {
                       $errors[] = 'Your two passwords did not match.';
               }
        } else {
               $errors[] = 'You forgot to enter your password.';
        }       if (empty($errors)) { // If everything's OK.                            #4
try {
        // Register the user in the database...
        // Hash password current 60 characters but can increase
            $hashed_passcode = password_hash($password1, PASSWORD_DEFAULT);           //#5
             require ('mysqli_connect.php'); // Connect to the db.                    //#6
        // Make the query:                                                              #7
        $query = "INSERT INTO users (userid, first_name, last_name, email, password, registration_date) ";
                $query .="VALUES(' ', ?, ?, ?, ?, NOW() )";                           //#8
        $q = mysqli_stmt_init($dbcon);                                                //#9
        mysqli_stmt_prepare($q, $query);
        // use prepared statement to ensure that only text is inserted
        // bind fields to SQL Statement
        mysqli_stmt_bind_param($q, 'ssss', $first_name, $last_name, $email, $hashed_passcode);
     // execute query
        mysqli_stmt_execute($q);
        if (mysqli_stmt_affected_rows($q) == 1) { // One record inserted               #10
                header ("location: register-thanks.php");
                exit();
                } else { // If it did not run OK.
                // Public message:
                                $errorstring = "<p class='text-center col-sm-8' style='color:red'>";
                        $errorstring .= "System Error<br />You could not be registered due ";
                        $errorstring .= "to a system error. We apologize for any inconvenience.</p>";
                        echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
                // Debugging message below do not use in production
                //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $query . '</p>';
                               mysqli_close($dbcon); // Close the database connection.
                // include footer then close program to stop execution
                        echo '<footer class="jumbotron text-center col-sm-12"
                                       style="padding-bottom:1px; padding-top:8px;">
                                               include("footer.php");
                                              </footer>';
                             exit();
       }
   }
   catch(Exception $e) // We finally handle any problems here                          #11
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
        } else { // Report the errors.                                                 #12
               $errorstring = "Error! The following error(s) occurred:<br>";
               foreach ($errors as $msg) { // Print each error.
                        $errorstring .= " - $msg<br>\n";
               }
               $errorstring .= "Please try again.<br>";
               echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
               }// End of if (empty($errors)) IF.
?>

Listing 2-8bValidating

the Registration Page (process-register-page.php)

注意

在这一点上,初学者可能会感到困惑,因为他们更熟悉从上到下遍历页面的代码。在前面的交互式代码示例中,初学者希望表单字段位于代码页的顶部。事实上,它们在清单中排在最后(在通过 require 方法导入的 PHP 代码之后)。有一个主 if 语句(if($ _ SERVER[' REQUEST _ METHOD ']= ' POST '){)控制着程序的流程。如果用户已经访问了页面并点击了提交按钮,那么 If 语句为真,PHP 代码被执行(从包含的文件;上例中的 process_register_page.php)。如果这是用户第一次访问该页面,则 If 语句为 false。因此,执行语句 else 部分中的 HTML 代码。在提交表单和执行 PHP 代码之前,HTML5 代码还会对用户输入的信息进行一些有限的验证。此外,PHP 代码将验证从 web 页面收到的是“好的”、经过验证的和经过净化的信息。

看起来我们重复核实了两次信息。然而,使用 HTML5(可能还有一些 JavaScript)来首次尝试验证信息可以减少服务器调用并加快验证过程。我们仍然需要在服务器上再次验证信息,因为在信息到达之前,它可能被有意或无意地操纵。

代码的解释

清单中的注释解释了许多 PHP 语句,但是有些项目需要进一步注释。PHP 代码可能看起来非常复杂,但是我们将把它分成更小的部分来帮助你理解它。让我们现在开始吧。

$errors = array(); // Initialize an error array.                                       #1

数组是一种将多项相似信息存储到一个存储位置的方法。在我们的例子中,我们使用数组来存储任何错误消息。前一行初始化数组,并将其命名为\(errors。从技术上讲,\)errors 现在指向内存中为数组保留的区域。本章末尾和附录中提供了一些有关数组及其用法的附加信息。

// Check for a first name:                                                             #2
$first_name = trim($first_name);
if (empty($first_name)) {
       $errors[] = 'You forgot to enter your first name.';
}

像$_POST['first_name']这样的全局变量中的元素总是用方括号括起来。这是超级全局变量的一个特性。从技术上讲,从我们的表单传递到 PHP 代码的所有内容都被传递到一个 post 数组中(这就是我们使用方括号而不是圆括号的原因)。然后,我们使用 POST 超级全局语句从数组中检索我们想要的内容。

\(first_name = trim 开头的代码是一条指令,用于从用户的 first_name 条目的开头和结尾修剪(删除)任何空格或其他空白。输入数据开头和结尾的空格是不需要的字符。然后将修剪后的名字赋给变量\)first_name。为了增强安全性,我们应该额外删除无效字符。然而,就目前而言,让我们尽量保持简单。前面显示的格式也用于姓氏和电子邮件。

注意

任何采用\(_POST[ ]格式的变量,如\)_POST['first_name'],都是一个全局变量,可通过网站上的页面访问。普通变量的格式为\(first_name,只能通过加载出现它们的页面来访问它们。\)_POST 将通过 POST 数组提取从 HTML 表单传递的信息,当用户单击 submit 按钮时,会自动填充该数组。方括号中包含的变量名(first_name)必须与 HTML 输入语句的 name 参数完全匹配。

// Check for a password and match against the confirmed password:                      #3
$password1 = trim($_POST['password1']);
$password2 = trim($_POST['password2']);
if (!empty($password1)) {
         if ($password1 !== $password2) {
                  $errors[] = 'Your two password did not match.';
         }
} else {
         $errors[] = 'You forgot to enter your password.';
}

两个新密码都去掉了不必要的空格。如果密码 1 不为空。空),则执行两个新密码的比较。那个!==(一!和两个=)符号确定表单中输入的密码是否相同。这种不等于的格式确保了大写和小写是相同的。它还确保数据类型(字符串与整数相比)是相同的。如果密码不同,将在错误数组中显示一条错误消息。

if (empty($errors)) { // If everything's OK.                                           #4

这段代码位于成功部分。当所有字段都已正确填写时,该部分开始注册过程——在这种情况下,$errors 数组为空。这一行是说“因为没有错误,我们可以连接到数据库并插入用户的数据。”

$hashed_passcode = password_hash($password, PASSWORD_DEFAULT);                       //#5

我们必须确保所有密码尽可能安全。前面的代码使用 PHP 密码哈希方法将用户输入的密码转换为安全密码。PASSWORD_DEFAULT 将总是包含最新的 PHP 散列码。在数据库表中,我们将密码字段设置为包含 60 个字符。将来,新的散列技术可能会要求您增加该字段的大小。

require ('mysqli_connect.php'); // Connect to the db.                                //#6

在这一行中,数据库连接是用 require()而不是 include()建立的,这样,如果建立了连接,脚本就会运行,但是如果没有连接,脚本就不会运行。这是因为如果数据库不可访问(因为无法将数据插入到表中),那么继续操作就没有意义。当需要包含一些重要的东西时,我们使用 require()。我们本可以在这里使用 require_once,因为我们只想将这段代码引入一次,以建立到数据库的连接。

// Make the query:                                                                    #7
$query = "INSERT INTO users (userid, first_name, last_name, email, password, registration_date) ";

如果成功连接到数据库,这一行就准备将数据输入到名为 users 的表中。单词 query 有时表示“查询”,但更多时候表示“做点什么”。在这种情况下,它意味着“使用以下数据插入新记录…”这一行是“插入到名为 users 的表中,在标有useridfirst_name等的列标题下……”该查询被分配给一个变量$query。第 7 行和第 8 行实际上是 SQL 准备的查询。这展示了 HTML、PHP 和 SQL 是如何很好地协同工作的。

$query .="VALUES(' ', ?, ?, ?, ?, NOW() )";                                          //#8

这段 SQL 代码为要输入的数据提供了一个存放位置。我们使用预准备语句,因为它永远不会执行已经绑定到位置的数据。标记存在)。这使得黑客无法在我们的 SQL 字符串中插入额外的 SQL 语句来破坏我们的数据。请注意,第一个值故意为空(用引号括起来,中间有一个空格),因为它是由 MySQL 或 MariaDB 数据库管理系统自动递增并输入的字段,而不是由用户输入的。NOW 函数自动将数据和时间放入数据库表的 registration_date 列中。

$q = mysqli_stmt_init($dbcon);                                                       //#9
mysqli_stmt_prepare($q, $query);
// use prepared statement to ensure that only text is inserted
// bind fields to SQL Statement
mysqli_stmt_bind_param($q, 'ssss', $first_name, $last_name, $email, $hashed_passcode);
// param must include four variables to match the four s parameters
// execute query
mysqli_stmt_execute($q);

来自之前包含的文件( mysqli_connect.php )的连接字符串被附加到 mysqli,并且可以用\(q 变量引用。然后\)query 字符串被声明为一个准备好的语句(表示变量将被插入到代码中),并与数据库连接字符串相关联。变量(\(first_name,\)last_name,\(email,\)hashed_passcode)附加到准备好的 SQL 语句中。注意,s 符号表示每个变量都包含一个字符串。要表示数字信息,可以使用 I(整数)或 d(双精度)。b 也可用于 BLOB。然后对数据库表执行该语句。

if (mysqli_stmt_affected_rows($q) == 1) { // One record inserted                      #10
       header ("location: register-thanks.php");
       exit();

如果操作成功,数据库将被关闭。那么 header 方法会用一个“谢谢”页面替换当前的注册页面。header 方法产生一个请求页面的 HTML Get 消息。exit 命令将关闭当前页面,因为它不再被使用。

catch(Exception $e) // We finally handle any problems here                            #11
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }

如果在执行 SQL 命令时出现错误或其他错误或异常,代码流将跳转到 catch 块来处理问题。如上所述,对于测试,我们可以显示我们的错误,但是当我们切换到生产时,我们将显示一个通用的“系统当前不可用”消息,并将问题记录在错误日志中。

} else { // Report the errors.                                                        #12

               $errorstring = "Error! The following error(s) occurred:<br>";
               foreach ($errors as $msg) { // Print each error.
                       $errorstring .= " - $msg<br>\n";
               }
               $errorstring .= "Please try again.<br>";
               echo "<p class='text-center col-sm-2' style='color:red'>$errorstring;</p>";
               }// End of if (empty($errors)) IF.

如果有用户错误(无效条目),消息将存在于\(error 数组中。当数组中有条目时,将实现 else 结构。foreach 循环遍历\)errors 数组,如果发现任何错误消息,就会显示(回显)在屏幕上(foreach 是一个不带空格的单词)。

在 HTML 代码中检查$errorstring 变量,以确定是否显示右列。如果有错误,右列不会显示,以便为错误消息留出空间。

您可能想知道为什么我们要创建 PHP 代码来进行与所示 HTML5 代码相似的验证。PHP 代码在服务器上被解释和执行。HTML 代码在浏览器中执行。即使代码在浏览器中得到验证,它也可能在到服务器 PHP 程序的路径上被破坏。您应该使用 HTML(和 JavaScript)进行验证,以便更快地验证和响应用户。它还减少了服务器调用。

PHP 关键字 echo

在前面的代码中,关键字 echo 出现在许多地方。这是 PHP 告诉浏览器“在屏幕上显示一些东西”的方式。一些设计师使用可选的关键字 print,但本书通常会使用 echo,因为我们发现初学者会将它与将东西发送到喷墨或激光打印机的命令混淆;硬拷贝打印是 PHP 做不到的。

注意

您想要的任何 HTML 代码都可以放在 echo 语句中。echo 将内容写入 HTML 文档(虚拟地,而不是字面地),以便可以在浏览器中显示。

当需要换行时,初学者可能会对 echo 的行为感到困惑。例如,以下代码不显示任何行间距:

echo "I found that PHP ";
echo "was much easier to learn than Perl";

浏览器显示如下:

我发现 PHP 比 Perl 容易学得多

要向下推第二行,必须插入换行符标记
,如下所示:

echo "I found that PHP<br>";
echo "was much easier to learn than Perl";

浏览器显示如下:

我发现 PHP 比 Perl 容易学得多

“谢谢”页面

图 2-9 显示了“感谢”页面。

img/314857_2_En_2_Fig9_HTML.jpg

图 2-9

“谢谢”页面

注册页面使用 PHP header 语句调用“谢谢”页面。您可以在注册页面代码的成功部分找到这一点。为方便起见,我们在此重复一遍:

$dbcon->close();
header ("location: register-thanks.php");
exit();

清单 2-9 显示了完整的“谢谢”页面代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6); padding:20px;">
  <?php include('header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
         <?php include('nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
  <div class="col-sm-8 text-center">
      <h2>Thank you for registering</h2>
      On the Home Page, you will now be able to login and add new quotes to the message board.
<!-- login does not yet work, nut will in the next chapter -->
  </div>
<!-- Right-side Column Content Section -->
      <aside class="col-sm-2">
           <?php include('info-col.php'); ?>
      </aside>
  </div>
<!-- Footer Content Section -->
     <footer class="jumbotron text-center row"
          style="padding-bottom:1px; padding-top:8px;">
          <?php include('footer.php'); ?>
     </footer>
</div>
</body>
</html>

Listing 2-9Creating the “Thank You” Page (register-thanks.php)

如您所见,该页面是根据模板创建的,只做了一些更改。这保持了网站应有的一致性。我们现在来看看当服务器收到无效信息时会发生什么。我们将使用一个数组来显示错误消息。

显示数组中收集的错误信息

如果服务器上的所有字段都收到无效信息(空值),将会产生多个错误。相应的错误消息被插入到错误数组中。然后显示这些信息,以便用户了解情况。如果只显示第一个错误,然后在第二次单击 Register 按钮后,又显示第二个错误,以此类推,那就太烦人了。

图 2-10 显示了出现这种情况时显示的错误示例。

img/314857_2_En_2_Fig10_HTML.jpg

图 2-10

用户未能输入任何数据时显示的错误

错误显示在注册页面上。如前所述,当出现用户错误时,右栏被删除,以提供一个显示消息的区域。错误可能表明浏览器和服务器之间的数据以某种方式被破坏。如果用户输入不正确的信息并点击提交按钮,HTML5 和 JavaScript 代码将处理大多数验证情况并显示错误消息。如果您想测试更多的服务器错误消息,请从每个 HTML 文本框中删除必需的属性。

散列密码

所有密码都必须经过哈希处理,以防黑客发现。从 PHP 5.5 开始,开发人员创建了一种新的密码散列(编码)方法。这种方法被认为是安全的,是散列密码的最佳方法之一。格式如下:

$hashedPassword = password_hash($password1, PASSWORD_DEFAULT);

在本例中,$password1 是密码的未哈希版本。PASSWORD_DEFAULT 是一个 PHP 常量。随着安全性的变化,PHP 开发人员计划保持编码格式不变,并改变 PHP 常量来表示任何新的散列方案。password_hash 方法将用户输入的密码转换为安全的散列密码,然后可以保存在数据库表中。我们将在后面发现,我们必须使用 password_verify 方法来确定用户输入的密码是否与存储在数据库中的散列密码相匹配。

查看成员记录

当用户注册后,网站管理员可以使用 phpMyAdmin 来查看该表及其条目。我们假设一个用户在 2018 年 4 月 26 日注册,他们输入了以下信息:

名字:史蒂夫姓氏:约翰逊,电子邮件:sjohnson@sjohnson.com,密码:aaaaaaaa

phpMyAdmin 将允许您查看用户表中的每条记录。访问 phpMyAdmin,选择 simpledb 数据库,单击 users 表,然后单击 Browse 选项卡。图 2-11 显示了斯蒂夫·约翰森的参赛作品。

img/314857_2_En_2_Fig11_HTML.jpg

图 2-11

在 phpMyAdmin 中查看记录

请注意,密码 aaaaaaaa 被散列为$ 2y $ 10 $ lEmRKPYfu/nb 6 ect BMP 7 youizezdyucnzkrmebnq 6 nrhdkjhdegmk。

通常,无法访问 phpMyAdmin 的网站设计人员和数据库管理员可能还需要查看由所有注册用户创建的数据库条目表。下一个示例提供了这样做的信息。

“查看用户”页面

点击查看用户按钮,显示注册用户列表,如图 2-12 所示。

img/314857_2_En_2_Fig12_HTML.jpg

图 2-12

将显示一个用户表

当用户单击右上角菜单上的“查看用户”按钮时,将显示该表。清单 2-10 显示了显示用户表的代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6); padding:20px;">
  <?php include('header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
  <div class="col-sm-8">
  <h2 class="text-center">These are the registered users</h2>
<p>
<?php
try {
// This script retrieves all the records from the users table.
require('mysqli_connect.php'); // Connect to the database.
// Make the query:
// Nothing passed from user safe query                                                 #1
$query = "SELECT CONCAT(last_name, ', ', first_name) AS name, ";
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y') AS ";
$query .= "regdat FROM users ORDER BY registration_date ASC";
$result = mysqli_query ($dbcon, $query); // Run the query.
if ($result) { // If it ran OK, display the records.
// Table header.                                                                       #2
echo '<table class="table table-striped">
<tr><th scope="col">Name</th><th scope="col">Date Registered</th></tr>';
// Fetch and print all the records:                                                    #3
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
echo '<tr><td>' . $row['name'] . '</td><td>' . $row['regdat'] . '</td></tr>'; }
    echo '</table>'; // Close the table so that it is ready for displaying.
    mysqli_free_result ($result); // Free up the resources.
} else { // If it did not run OK.
// Error message:
echo '<p class="error">The current users could not be retrieved. We apologize';
echo ' for any inconvenience.</p>';
// Debug message:
// echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
exit;
} // End of if ($result)
mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
?>

  </div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
      <?php include('info-col.php'); ?>
        </aside>
  </div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 2-12Displaying a Table of Registered Members on the Screen (register-view-users.php)

代码的解释

您之前已经看到了大部分代码,但下面是对新项目的解释:

// Make the query:
// Nothing passed from user safe query                                                 #1
$query = "SELECT CONCAT(last_name, ', ', first_name) AS name, ";
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y') AS ";
$query .= "regdat FROM users ORDER BY registration_date ASC";
$result = mysqli_query ($dbcon, $query); // Run the query.
if ($result) { // If it ran OK, display the records.

SQL 查询选择姓,然后是逗号,然后是名,并将其串在一起(串联),同时选择注册数据。它将这些文件的临时标题设置为 name 和 regdat。它以月-日-年的格式重新排列注册日期。它使用 select 查询从 users 表中提取信息。最后,它要求每一行信息都按照升序(ASC)日期顺序显示(最早的排在最前面)。要首先显示最新登记的记录(降序),可以使用 DESC 而不是 ASC。由于查询没有从用户那里收集任何信息,我们不需要创建一个准备好的语句,传递注册页面中显示的变量。这个查询字符串不能被破解。

// Table header.                                                                       #2
echo '<table class="table table-striped">
<tr><th scope="col">Name</th><th scope="col">Date Registered</th></tr>';

此代码块使用引导类表显示(回显)该表,并剥离表以格式化显示。列标题 Name 和 Date Registered 也被声明为显示的表的第一行。

// Fetch and print all the records:                                                    #3
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
echo '<tr><td>' . $row['name'] . '</td><td>' . $row['regdat'] . '</td></tr>'; }

这段代码循环遍历这些行,并在数据库表中的行数据可用时显示它们。MYSQLI_ASSOC 创建一个关联数组。PHP 关联数组使用关键字(单词)作为索引。例如,$row['name']包含从 users 表中提取的串联的名字和姓氏(参见#1)。

用户有时想要更改他们的密码。下一节将演示如何做到这一点。

“更改密码”页面

当用户点击标题右上角菜单上的新密码按钮时,出现如图 2-13 所示的表单。

img/314857_2_En_2_Fig13_HTML.jpg

图 2-13

更改密码表单

只有当用户知道他们当前的密码和电子邮件地址时,该表单才适用。如果他们忘记了密码,就需要一种不同的方法,这将在后面的章节中讨论。然而,如果用户不喜欢他们最初选择的密码,这个“新密码”页面是有用的。清单 2-13a 给出了修改密码表单的 HTML 代码。

<!DOCTYPE html>
<html lang="en">
<head>
 <title>Template for an interactive web page</title>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 <!-- Bootstrap CSS File -->
 <link rel="stylesheet"
 href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
 integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
 crossorigin="anonymous">
<script src="verify.js"></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6); padding:20px;">
 <?php include('header.php'); ?>
</header>
<!-- Body Section -->
 <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
     <ul class="nav nav-pills flex-column">
        <?php include('nav.php'); ?>
      </ul>
  </nav>
 <!-- Validate Input -->
   <?php
  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
   require('process-change-password.php');                                           //#1
  } // End of the main Submit conditional.
  ?>
<div class="col-sm-8">
<h2 class="h2 text-center">Change Password</h2>
<form action="change-password.php" method="post" name="regform"
 id="regform" onsubmit="return checked();">
<div class="form-group row">
    <label for="email" class="col-sm-4 col-form-label">E-mail:</label>
        <div class="col-sm-8">
        <input type="email" class="form-control" id="email" name="email"
        placeholder="E-mail" maxlength="60" required
        value="<?php if (isset($_POST['email'])) echo $_POST['email']; ?>">
        </div>
 </div>
 <div class="form-group row">
        <label for="password" class="col-sm-4 col-form-label">Current Password:</label>
        <div class="col-sm-8">
        <input type="password" class="form-control" id="password" name="password"
        placeholder="Password" minlength="8" maxlength="12"
        required value="<?php if (isset($_POST['password'])) echo $_POST['password']; ?>">
        </div>
 </div>
<div class="form-group row">
        <label for="password1" class="col-sm-4 col-form-label">New Password:</label>
        <div class="col-sm-8">
         <input type="password" class="form-control" id="password1" name="password1"
          placeholder="Password" minlength="8" maxlength="12"
          required value="<?php if (isset($_POST['password1'])) echo $_POST['password1']; ?>">
          <span id="message">Between 8 and 12 characters.</span>
</div>
<div class="form-group row">
         <label for="password2" class="col-sm-4 col-form-label">Confirm Password:</label>
         <div class="col-sm-8">
                 <input type="password" class="form-control" id="password2" name="password2"
          placeholder="Confirm Password" minlength="8" maxlength="12" required
          value="<?php if (isset($_POST['password2'])) echo $_POST['password2']; ?>">
        </div>
 </div>
<div class="form-group row">
        <div class="col-sm-12">
        <input id="submit" class="btn btn-primary" type="submit" name="submit"
        value="Change Password">
        </div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
<?php
 if(isset($errorstring)) {
         echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<aside class="col-sm-2">';
        include('info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('footer.php');
 ?>
</footer>
</div>
</body>
</html>

Listing 2-13aCreating a Page to Allow Users to Change a Password (change-password.php)

代码的解释

本节给出了代码的解释。

  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
   require('process-change-password.php');                                           //#1
  } // End of the main Submit conditional.

更改密码页面的 HTML 类似于注册页面的 HTML。主要区别是 PHP 代码现在是从 process-change-password 文件导入的。让我们看看清单 2-13b 中这个文件的内容。

<?php
// This script is a query that UPDATES the password in the users table.
// Check that form has been submitted:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
require ('mysqli_connect.php'); // Connect to the db.
$errors = array(); // Initialize an error array.
// Check for an email address:
$email = trim($_POST['email']);
if (empty($email)) {
        $errors[] = 'You forgot to enter your email address.';
}
// Check for a password and match against the confirmed password:
$password = trim($_POST['password']);
if (empty($password)) {
        $errors[] = 'You forgot to enter your old password.';
}
// Prepare and check new password                                                      #1
$new_password = trim($_POST['password1']);
$verify_password = trim($_POST['password2']);
if (!empty($new_password)) {
        if (($new_password != $verify_password) ||
        ( $password == $new_password ))
        {
$errors[] = 'Your new password did not match the confirmed password and/or ';
$errors[] = 'Your old password is the same as your new password.';
        }
} else {
        $errors[] = 'You did not enter a new password.';
}if (empty($errors)) { // If everything's OK.
try {
 // Check that the user has entered the right email address/password combination:      #2
 $query = "SELECT userid, password FROM users WHERE ( email=? )";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
 // use prepared statement to ensure that only text is inserted
 // bind fields to SQL Statement
mysqli_stmt_bind_param($q, 's', $email);
 // execute query
 mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
if ((mysqli_num_rows($result) == 1)                                                  //#3
          && (password_verify($password, $row['password'])))
          {    // Found one record
         // Change the password in the database...
         // Hash password current 60 characters but can increase
         $hashed_passcode = password_hash($new_password, PASSWORD_DEFAULT);
         // Make the query:
         $query = "UPDATE users SET password=? WHERE email=?";
         $q = mysqli_stmt_init($dbcon);
         mysqli_stmt_prepare($q, $query);
         // use prepared statement to ensure that only text is inserted
         // bind fields to SQL Statement
         mysqli_stmt_bind_param($q, 'ss', $hashed_passcode, $email);
         // execute query
         mysqli_stmt_execute($q);
         if (mysqli_stmt_affected_rows($q) == 1) {   // one row updated                #4
                       // Thank you
                      header ("location: password-thanks.php");
                                exit();
        } else { // If it did not run OK.                                               #5
               // Public message:
               $errorstring = "System Error! <br /> You could not change password due ";
               $errorstring .= "to a system error. We apologize for any inconvenience.</p>";
               echo "<p class='text-center col-sm-2' style='color:red'>$errorstring</p>";
               // Debugging message below do not use in production
               //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $query . '</p>';
               // include footer then close program to stop execution
               echo '<footer class="jumbotron text-center col-sm-12"
                       style="padding-bottom:1px; padding-top:8px;">
                       include("footer.php");
                       </footer>';
               exit();
        }
         } else { // Invalid email address/password combination.
               $errorstring = 'Error! <br /> ';
                       $errorstring .= 'The email address and/or password do not match those on file.';
               $errorstring .= " Please try again.";
               echo "<p class='text-center col-sm-2' style='color:red'>$errorstring</p>";
} }
   catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
   } else { // Report the errors.                                                      #6
        //header ("location: register-page.php");
        $errorstring = "Error! The following error(s) occurred:<br>";
        foreach ($errors as $msg) { // Print each error.
                $errorstring .= " - $msg<br>\n";
        }
        $errorstring .= "Please try again.<br>";
        echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
        }// End of if (empty($errors)) IF.
} // End of the main Submit conditional.
?>

Listing 2-13bProcessing the Changed Password (process-change-password.php)

代码的解释

本节解释代码。

// Prepare and check new password                                                      #1
$new_password = trim($_POST['password1']);
$verify_password = trim($_POST['password2']);
if (!empty($new_password)) {
        if (($new_password != $verify_password) ||
        ( $password == $new_password ))
        {
$errors[] = 'Your new password did not match the confirmed password and/or ';
$errors[] = 'Your old password is the same as your new password.';
       }
} else {
       $errors[] = 'You did not enter a new password.';

前两行代码删除字符串中的所有空格。在后面的章节中,我们将验证密码的格式是否正确。但是,目前我们使用的是准备好的语句;因此,用户试图插入文本框的任何代码都不会被执行。如果两个新密码匹配,新密码将放在$password 中。如果它们不匹配,一个错误被发送回网页。

(
// Check that the user has entered the right email address/password combination:       #2
 $query = "SELECT userid, password FROM users WHERE ( email=? )";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
 // use prepared statement to ensure that only text is inserted
 // bind fields to SQL Statement
mysqli_stmt_bind_param($q, 's', $email);
 // execute query
 mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);

如果所有验证都成功,我们必须确保用户提供了现有的电子邮件和密码。前面代码中的第 2 行创建了一个准备好的 select 语句,该语句使用提供的电子邮件从 users 表中提取用户名和密码。如果 SQL 语句返回一个结果,我们就验证了该电子邮件存在于数据库中。

if ((mysqli_num_rows($result) == 1)                                                  //#3
          && (password_verify($password, $row['password'])))
          {    // Found one record
        // Change the password in the database...
         // Hash password current 60 characters but can increase
         $hashed_passcode = password_hash($new_password, PASSWORD_DEFAULT);
        // Make the query:
        $query = "UPDATE users SET password=? WHERE email=?";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // use prepared statement to ensure that only text is inserted
        // bind fields to SQL Statement
        mysqli_stmt_bind_param($q, 'ss', $hashed_passcode, $email);
        // execute query
        mysqli_stmt_execute($q);

如果我们从数据库表中得到一个结果,我们还需要验证密码是否正确。if 语句使用 password_verify 函数将旧密码与表中的密码进行比较。这是必要的,因为存储在表中的密码是散列的,而用户提供的密码不是散列的。如果匹配,则对新密码进行哈希运算。创建一个准备好的 SQL 更新查询来更改 users 表中的密码。然后执行该查询。

if (mysqli_stmt_affected_rows($q) == 1) {    // one row updated                        #4
                       // Thank you
                       header ("location: password-thanks.php");
                                exit();

如果 SQL 更新查询成功,将显示“谢谢”页面。header 函数使用 HTML Get 命令调用密码感谢页面。使用 exit 方法关闭当前页面。

} else { // If it did not run OK.                                                      #5
               // Public message:
               $errorstring = "System Error! <br /> You could not change password due ";
               $errorstring .= "to a system error. We apologize for any inconvenience.</p>";
               echo "<p class='text-center col-sm-2' style='color:red'>$errorstring</p>";
               // Debugging message below do not use in production
               //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $query . '</p>';
               // include footer then close program to stop execution
               echo '<footer class="jumbotron text-center col-sm-12"
                       style="padding-bottom:1px; padding-top:8px;">
                       include("footer.php");
                       </footer>';
               exit();

如果没有行被更改,则查询不会成功执行。else 语句捕获这种情况,并显示一条系统错误语句。因为这是一个严重的错误,所以为页面提供了一个页脚,并关闭了代码(exit())。这将关闭对数据库的任何访问,并且不会让任何其他代码执行。注释中列出了调试代码,这些代码将提供有关该问题的更多信息。此代码不应在实时代码中激活。提供的错误信息可能会显示代码和数据库连接信息,这将是一个严重的安全违规。

} else { // Report the errors.                                                         #6
        //header ("location: register-page.php");
        $errorstring = "Error! The following error(s) occurred:<br>";
        foreach ($errors as $msg) { // Print each error.
                $errorstring .= " - $msg<br>\n";
        }
        $errorstring .= "Please try again.<br>";
        echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
        }// End of if (empty($errors)) IF.

代码的最后一部分类似于注册页面。它显示用户输入无效信息时导致的任何错误消息。这些错误与表单显示在同一个页面上,以便用户进行更正并重新提交信息。

确认密码更改成功

如果密码更改成功,显示如图 2-14 所示的页面。

图 2-14 所示页面与注册“感谢”页面相似。您可以从本章的下载文件中查看代码。

img/314857_2_En_2_Fig14_HTML.jpg

图 2-14

密码已被更改

测试教程的页面

要查看交互式页面的工作情况,请双击桌面上的 XAMPP 或 easyPHP 图标。当控制面板出现时,检查 Apache 和 MySQL/MariaDB 是否正在运行。

在浏览器的地址栏中输入http://localhost/simpledb/index.php,然后点击注册按钮,这样就可以输入一些用户。

为了节省你的时间和虚构人物的努力,使用表 2-1 中提供的建议。

表 2-1

输入成员详细信息的建议

|

名字

|

电子邮件

|

密码

|
| --- | --- | --- |
| 麦克·罗斯 | 米克尔@myisp.com | W1llgat3s |
| 橄榄枝 | obranch@myisp.com.uk | 第 1 年第 0 季第 3 集 |
| 弗兰克·弗雷泽 | finsin @ my ISP . net | P3 PFM 300 型核潜艇 |
| 周年纪念日 | aversary@myisp.com | B1 港币 3yg1rl |
| 特里·菲德 | tfide @ my ISP . com | 刀疤 3dst1ff |
| 玫瑰(灌木)丛 | rbush@myisp.co.uk | R3 db 100 ms |

现在用户表中有了更多的数据,运行 XAMPP 或 EasyPHP,使用浏览器单击菜单项显示注册用户表。

小费

毫无疑问,当您创建代码并测试它时,您会遇到错误消息。如需帮助,请参考第十二章中的故障排除提示。

在本章的前面,我们承诺了更多关于 PHP 数组使用的信息。下一节将帮助您理解这个最有用的特性的其他方面。

关于数组的更多信息

本书通篇都在使用数组,如果你访问论坛寻求 PHP 使用建议,你会遇到许多数组。

数组是内存中存放多个相似项目的地方(比如下面显示的谷物)。这些项目以相同的变量名(谷物)组合在一起。要引用数组中的单个元素,我们必须使用一个键(下标)。在 PHP 中,下标可以是一个数字(\(谷类[1])或者一个字符串(比如\)row['name'])。我们将在整本书中使用这两种格式。

数组可以通过几种方式填充元素。我们创建了一个名为$谷类的数组,如下所示:

<?php
$cereals = array();
?>

可以将一些元素插入到数组中,如下所示(注意,第一个数组元素通常为零):

<?php
$cereals = array[];
$cereals[0] = "oats";
$cereals[1] = "barley";
$cereals[2] = "corn";
$cereals[3] = "wheat";
?>

若要显示该数组的元素,请在结束标记前插入以下代码。>:

echo "$cereals[0] " . "$cereals[1]  " . "$cereals[2]  " .  "$cereals[3]";?>

显示屏将显示以下内容:

燕麦大麦玉米小麦

echo 语句使用连接符号将字符串连接在一起。).但是,在 PHP 中,我们也可以将程序变量(如$name)放在引号内,如下所示:

$first_name = "Fred";
$middle_name = "Adam";
$last_name = "Smith";

echo "$first_name $middle_name $last_name";

PHP 还允许我们在不提供索引号的情况下将项目插入到一个数字数组中。

$error[] = "One error";
$error[] = "Another error";

PHP 将自动用 0 索引第一项,用 1 索引第二项。因此,我们可以显示如下值:

echo "$error[0] $error[1]";

关于数组还有很多需要学习的地方。然而,这些少量的知识应该可以帮助您理解我们在从数据库表中提取信息以及加载和显示错误消息时是如何使用数组的。

警告

本章中输入数据库表格的数据没有被完全过滤。它们被有意地去掉了大部分过滤器和清洁剂,因此基本的过程是整洁的,清晰可见的。越来越多的安全和过滤功能将在后面的章节中添加。

摘要

在这一章中,你创建了你的第一个交互式页面并测试了它们。您学到了更多关于 PHP 的知识——特别是,您应该已经掌握了在代码中寻找和识别逻辑模式的思想。您学习了如何使用几个 PHP 函数:include()、required()和 echo()。你也学到了很多 mysqli 函数。您发现了 PHP 条件对于创建交互式数据库页面的重要性。您还了解了如何在将数据发送到数据库表之前检查用户是否输入了信息。在下一章,我们将增加一些额外的安全性,并演示用户和管理员如何登录和注销网站上的个人页面。您还将学习如何删除或替换标题菜单中的冗余链接。

三、为成员和管理员创建登录/注销功能

在第二章中,我们使用数据库和表格创建了第一个交互式页面。到现在,你可能已经意识到我们创造的东西并不是很实用;但是,您学习了如何将交互性嵌入到真实的页面中。更实际的应用是允许注册用户登录和注销个人页面。当用户登录时,他们应该能够访问网站提供的额外功能。这可能是一个为会员提供特殊服务的页面,也可能是在博客中添加评论、访问会议记录或查看会员特殊活动日期的功能。

完成本章后,您应该能够

  • 创建新的数据库和表

  • 删除或替换标题中的菜单按钮

  • 处理 HTML 文本框中不需要的字符

  • 区分两种类型的成员资格或用户级别

  • 创建用户级别以限制对个人页面的访问

  • 创建登录和注销页面

  • 创建成员专用页面

  • 规划管理员的角色

  • 测试登录和注销的能力

在前面的教程中,任何用户都可以查看成员表,但是成员们不会高兴地知道他们的个人详细信息对每个人都可用。我们现在必须防止这种情况,只允许管理员(如会员秘书)和网站管理员查看会员表。

管理员是一类特殊的用户:他们应该能够查看成员表,修改或删除记录。精通计算机的管理员可以使用 phpMyAdmin,但许多管理员将是只具备基本计算机技能的会员秘书。他们需要一个简单的界面来查看、修改和删除记录。本章将教你如何为管理员创建和实现一个用户友好的界面。第章 4 、章 5 和章 6 将逐步完善这个接口。

要登录网站上的私人页面,注册成员和管理员必须输入只有他们知道的信息,例如,用户的电子邮件地址和密码或用户名和密码。登录页面将自动检查登录详细信息是否与数据库中保存的用户信息相匹配。如果登录细节得到验证,则允许用户访问私人网页。

创建 logindb 数据库和用户表

本章中的教程基于第二章中创建的页面。我们将通过为数据库和包含 PHP 和 HTML 页面的文件夹使用一个新名称来阐明文件结构。这是必要的,因为我们发现,如果学生一次又一次地修改,他们会陷入可怕的混乱。因此,作为一般规则,我们将继续为本书的每个新章节使用单独的文件夹和新的数据库名称。但是,一个表可以和前面的教程同名(例如,用户)。如果表在不同的数据库中,这是可以接受的。

我们开始吧。创建一个新文件夹和一个新数据库,如下所示:

  1. 在 XAMPP htdocs 或 easyPHP eds-www 文件夹中,新建一个名为 login 的文件夹。

  2. 出版社的网页上下载第 3 章的 PHP 文件。并将它们放入 XAMPP htdocs 或 easyPHP eds-www 的新登录文件夹中。或者,您可以在阅读本章时,通过在 HTML 编辑器中键入每个文件来练习创建 PHP 和 HTML 清单。将它们保存在您的登录文件夹中,以便您可以运行它们。

  3. 打开 phpmyadmin。

  4. 单击数据库选项卡。

  5. 创建一个名为 logindb 的新数据库。如第二章所示,将编码设置为 utf8-general-ci。

  6. 按照第二章的说明为数据库添加用户、用户 ID 和密码。使用以下属性:

    用户名:威廉

    主机:本地主机

    密码 : Cat0nlap

    数据库名称:logindb

  7. login 文件夹中,创建 mysqli_connect.php 文件,以便它连接到 logindb,如下面的清单所示:

列出连接到新数据库所需的代码片段(msqli_connect.php)

<?php
// Create a connection to the logindb database.
// Set the encoding and the access details as constants:
Define ('DB_USER', 'william');
Define ('DB_PASSWORD', 'Cat0nlap');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'logindb');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
mysqli_set_charset($dbcon, 'utf8');
?>

在实际环境中,这个连接文件将保存在一个安全的文件夹中。

注意

抵制从书的 PDF 版本中复制和粘贴代码的冲动。在编辑过程中,某些符号更改可能会导致示例代码无法执行。最好的方法是从进程下载代码。com 网站。如果您确实从 PDF 版本中复制了代码,请将其放入 Microsoft 记事本中,然后保存。这确实修复了一些可能对 PHP 无效的引号字符。

表 3-1

用户表的属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 用户 id | 中位 | six | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 注册日期 | DATETIME |   | 没有人 |   | -是吗 |   | -是吗 |

  1. 在数据库 logindb 中,创建一个名为 users 的新表,该表有六列。其布局和属性与第二章中的用户表相同。详见表 3-1 。如第二章所述,phpMyAdmin 的某些版本不要求输入主索引(user_id)的长度(6)和属性(无符号)。user_id 字段是一个 autonum 字段,数据库系统在其中生成号码。按照第二章中设置前一个数据库的相同方式设置该表。

记得调整密码字段的大小,以符合最新的哈希标准。您可以在这里找到当前的 PHP 哈希标准:

http://php.net/manual/en/function.password-hash.php

现在我们将整理标题菜单。

移除或替换标题中多余的菜单按钮

如第二章所述,一些标题菜单按钮已经变得多余,需要一些新的按钮。在本节中,标题菜单将被修改。我们必须阻止公众和普通成员查看成员表。为了实现这一点,现在,我们将从除管理页面上的标题之外的所有标题中删除显示成员表的链接。我们也将很快要求管理员登录访问他们的页面。我们还需要删除成员页面上的登录链接,因为该成员已经登录。我们必须添加一个注销链接。注册链接也是多余的,因为成员和管理员已经注册;因此,菜单按钮也将被删除。

小费

当你全神贯注于数据库和网站页面的编码时,多余的按钮很容易被忽略。试着培养检查每一个新的或修改过的页面的标题的习惯,以确保没有多余或丢失的按钮。

在使用许多页面的“真实世界”网站中,我们可能会将实际的菜单页面链接存储在一个数据库表中,并使用基于登录用户级别(如管理员或用户)的代码来确定提供哪些链接。然而,对于较小的网站(比如这个演示),创建一些额外的标题菜单也是一样快(或者更快)。

向主页标题添加登录按钮

我们现在将在主页的标题菜单中添加一个新按钮,以便成员可以登录。我们还将删除“查看用户”按钮,因为我们希望进行安排,以便只有管理员能够查看成员表(我们将很快提供额外的限制)。图 3-1 显示了修改后的割台。

img/314857_2_En_3_Fig1_HTML.jpg

图 3-1

主页的修订标题。“新密码”按钮已被删除,“查看用户”按钮被“登录”按钮所取代。

删除了“查看用户”和“新密码”按钮,并添加了“登录”按钮。修改后的标题将自动包含在非专用页面(第 2 页、第 3 页等)中,因为标题文件名没有改变。

清单 3-1 显示了新标题的代码。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group"
       aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
    onclick="location.href = 'login.php'" >Login</button>
  <button type="button" class="btn btn-secondary"
    onclick="location.href = 'register-page.php'">Register</button>
</div>
     </nav>

Listing 3-1Revising the Home Page Header Menu (header.php)

在运行 XAMPP 或 easyPHP 的情况下,在浏览器中输入http://localhost/log in/index . PHP查看带有新登录按钮的主页,如图 3-1 所示。

删除注册和新密码标题中多余的按钮

注册页面和新密码页面需要一个新的标题,因为以前的标题有多余的菜单按钮。

在注册页面上,用户没有密码,因此无法登录;因此,登录和新密码按钮将被删除。注册按钮是多余的,因为用户已经访问了注册页面。现在,我们将从注册页面的标题中删除多余的按钮,并替换为更有用的按钮。图 3-2 显示了新的割台。

img/314857_2_En_3_Fig2_HTML.jpg

图 3-2

注册页面和新密码页面的新标题

多余的按钮被两个有意义的链接取代,即删除条目和取消。“删除条目”按钮重新加载注册页面,然后显示空字段。在与不熟悉电脑的用户测试网站后,我们选择了 Er 作为条目,而不是清除或删除所有条目。他们被“清除”和“擦除所有”的措词弄糊涂了,但他们立即明白了“擦除”条目。取消按钮使用户返回主页。清单 3-2a 给出了注册和新密码页面的修改标题代码。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group"
        aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
    onclick="location.href = 'register-page'" >Erase Entries</button>
  <button type="button" class="btn btn-secondary"
    onclick="location.href = 'index.php'">Cancel</button>
</div>
    </nav>

Listing 3-2aReplacing Redundant Buttons with Meaningful Buttons (register-header.php)

本章和后续章节的注册页面将链接到新的标题,如清单 3-2b 中的下一段代码所示。

修改后的注册页面

其余代码与第二章中的注册页面相同。修订后的代码包含在章节 3 的可下载文件中。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport"
     content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
   href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
   integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
   <script src="verify.js"></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style=
  "margin-bottom:2px; background:linear-gradient(white, #0073e6);
   padding:20px;">
  <?php include('register-header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
              <?php include('nav.php'); ?>
  </ul>
       </nav>

Listing 3-2bIncluding the New Header in the Registration Page (register-page.php)

新标题现在将应用于新密码页面。

新密码页面的新标题

新密码页面将需要相同的新标题。(如图 3-2 所示。)

本章和后续章节的新密码页面将链接到新标题,如清单 3-2c 中的下一段代码所示。

“删除条目”按钮重新加载“新密码”页面并显示空字段。取消按钮使用户返回主页。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
       <div class="btn-group-vertical btn-group-sm"
       role="group" aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
       onclick="location.href = 'change-password.php'" >
       Erase Entries</button>
  <button type="button" class="btn btn-secondary"
       onclick="location.href = 'index.php'">Cancel</button>
</div>
    </nav>

Listing 3-2cReplacing Redundant Buttons with Meaningful Buttons (password-header.php)

头部包含的文件在注册密码文件(change-password.php)中进行了修改,如以下代码片段所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport"
    content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
  <script src="verify.js"></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
  padding:20px;">
  <?php include('password-header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
              <?php include('nav.php'); ?>
      </ul>
  </nav>

其余的代码放在这里,从第章到第章没有变化。

成员页面的新标题菜单

对于成员页面,取消了注册按钮和查看成员按钮,增加了注销按钮,如图 3-3 所示。

img/314857_2_En_3_Fig3_HTML.jpg

图 3-3

成员页面的修改标题菜单

请注意,在图 3-3 中,当成员登录到成员页面时,登录菜单按钮会变为注销按钮。有几种聪明的方法可以实现这一点,但是最简单的方法是在成员页面中加载一个新的标题。这是有效的,因为有时特殊成员页面也需要稍微改变标题图像或文本。另请注意,多余的注册和新密码按钮已从标题菜单中删除。

新文件被命名为members-header.php。新的注销按钮有一个链接,将用户带到名为 logout.php的页面。这反过来会将用户发送到主页。为什么链接不直接到主页?因为 logout.php 的中间页面包含了一些在访问主页之前关闭会话的代码。会话是一种设备,将在下一节中解释。新标题的注销链接显示在下面的代码片段中。**

清单 3-3 显示了剩下的两个链接的代码。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group"
     aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
     onclick="location.href = 'logout.php'" >Logout</button>
  <button type="button" class="btn btn-secondary"
     onclick="location.href = 'change-password.php'" >New Password</button>
</div>
    </nav>

Listing 3-3Creating the New Header for the Members page (members-header.php)

如果该网站有一个以上的网页是专为会员,你可以添加更多的按钮,链接到文件members-header.php;或者,您可以将链接放在成员页面的正文中。

修改“谢谢”页面的标题

当用户到达“谢谢”页面时,他们已经注册,不再需要注册按钮,也不需要任何其他按钮。“谢谢”页面标题只需要一个将用户重定向到主页的按钮。图 3-4 显示了带有主页按钮的页眉。

img/314857_2_En_3_Fig4_HTML.jpg

图 3-4

“谢谢”页面的修订标题

标题中的主页菜单按钮将用户重定向到主页(index.php),如清单 3-4a 所示。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group"
     aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
       onclick="location.href = 'index.php'" >Home Page</button>
</div>
    </nav>

Listing 3-4aCreating the Code for the Revised “Thank You” Header (thanks-header.php)

“谢谢”页面现在将被修改,以包含修改后的标题。清单 3-4b 显示了“谢谢”页面中更改后的 include 声明。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Register Thanks</title>
  <meta charset="utf-8">
  <meta name="viewport"
    content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
    href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
 "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
  padding:20px;">
  <?php include('thanks-header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
              <?php include('nav.php'); ?>
      </ul>
  </nav>

Listing 3-4bCreating the Revised “Thank You” Page (register-thanks.php)

其余代码与章节 2 中的代码相同。

注册页面和不需要的字符

我们现在将开始验证用户是否提供了有效信息。每当文本框用于用户输入时,它都可能允许用户输入不需要的字符,这可能是试图提供 SQL 注入(用户试图访问您的数据库)或意外的用户输入错误。我们将使用 PHP 输入过滤函数来净化输入。这里显示了一个示例:

$first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);

我们现在将该功能添加到我们的注册页面。现在,让我们添加更新后的register-header.php文件,该文件只包含两个按钮:删除条目和取消。

图 3-5 显示了带有新标题的注册页面。

img/314857_2_En_3_Fig5_HTML.jpg

图 3-5

带有新标题的注册页面

清单 3-5 显示了修改后的 PHP 代码,用于过滤 process-register-page.php 的条目。

<?php
// This script is a query that INSERTs a record in the users table.
// Check that form has been submitted:
try {                                                                        #1
       $errors = array(); // Initialize an error array.
       // Check for a first name:
      $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
       if (empty($first_name)) {
               $errors[] = 'You forgot to enter your first name.';
        }
        // Check for a last name:
      $last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
        if (empty($last_name)) {
               $errors[] = 'You forgot to enter your last name.';
        }
        // Check for an email address:                                       #2
            $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
               $errors[] = 'You forgot to enter your email address';
               $errors[] = ' or the e-mail format is incorrect.';
        }
        // Check for a password and match against the confirmed password:
        $password1 = filter_var( $_POST['password1'], FILTER_SANITIZE_STRING);
        $password2 = filter_var( $_POST['password2'], FILTER_SANITIZE_STRING);
        if (!empty($password1)) {
               if ($password1 !== $password2) {
                       $errors[] = 'Your two password did not match.';
               }
        } else {
               $errors[] = 'You forgot to enter your password.';
        }
        if (empty($errors)) { // If everything's OK.
        // Register the user in the database...
        // Hash password current 60 characters but can increase
            $hashed_passcode = password_hash($password1, PASSWORD_DEFAULT);
               require ('mysqli_connect.php'); // Connect to the db.
               // Make the query:
               $query = "INSERT INTO users (userid, first_name, last_name, ";
               $query .= "email, password, registration_date) ";
               $query .="VALUES(' ', ?, ?, ?, ?, NOW() )";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // use prepared statement to ensure that only text is inserted
        // bind fields to SQL Statement
        mysqli_stmt_bind_param($q, 'ssss', $first_name, $last_name, $email, $hashed_passcode);
        // execute query
        mysqli_stmt_execute($q);
        if (mysqli_stmt_affected_rows($q) == 1) {    // One record inserted
               header ("location: register-thanks.php");
               exit();
         } else { // If it did not run OK.
               // Public message:
               $errorstring =
               "<p class='text-center col-sm-8' style='color:red'>";
               $errorstring .=
               "System Error<br />You could not be registered due ";
               $errorstring .=
               "to a system error. We apologize for any inconvenience.</p>";
               echo "<p class=' text-center col-sm-2'
               style='color:red'>$errorstring</p>";
               // Debugging message below do not use in production
               //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' .
                  $query . '</p>';
               mysqli_close($dbcon); // Close the database connection.
               // include footer then close program to stop execution
               echo '<footer class="jumbotron text-center col-sm-12"
                   style="padding-bottom:1px; padding-top:8px;">
             include("footer.php");
             </footer>';
               exit();
               }
        } else { // Report the errors.
               $errorstring =
                 "Error! <br /> The following error(s) occurred:<br>";
           foreach ($errors as $msg) { // Print each error.
                       $errorstring .= " - $msg<br>\n";
           }
           $errorstring .= "Please try again.<br>";
           echo "<p class=' text-center col-sm-2'
               style='color:red'>$errorstring</p>";
           }// End of if (empty($errors)) IF.
           }
   catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
?>

Listing 3-5Creating the Amended Registration Page (process-register-page.php)

代码的解释

本节解释代码。

try {                                                                     #1
        $errors = array(); // Initialize an error array.
        // Check for a first name:
        $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
        if (empty($first_name)) {
                $errors[] = 'You forgot to enter your first name.';
        }

具有 FILTER_SANITIZE_STRING 属性的 filter_var 函数将从用户输入的值中删除多余的空格和 HTML 标记。除了使用预准备语句之外,这将大大降低用户使用 SQL 注入访问数据库或在数据库中输入无效信息的能力。该代码仍然允许输入非字母字符。我们将在后面的章节中删除这些字符。

// Check for an email address:                                             #2
            $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
               $errors[] = 'You forgot to enter your email address';
               $errors[] = ' or the e-mail format is incorrect.';
        }

filter_var 函数具有 FILTER_SANITIZE_EMAIL 属性,该属性将删除任何无效的电子邮件字符。此外,如果输入的电子邮件格式正确,属性 FILTER_VALIDATE_EMAIL 将返回 true 或 false。在这段代码中,我们可以使用这个属性来验证电子邮件。但是,因为我们想将表单中的电子邮件保存到$email 中,所以我们也对它进行了清理。还有一些附加属性可用于整理和过滤用户输入。有关更多信息,请访问此页面:

www.php.net/manual/en/function.filter-input.php

注册一些成员

在这一点上,你应该在数据库中注册一些虚构的人,这样你在本章的后面就有东西可以玩了。为了节省您的时间,我们重复了第二章的成员名单,他们显示在表 3-2 中。

表 3-2

注册一些成员

|

名字

|

电子邮件

|

密码

|
| --- | --- | --- |
| 麦克·罗斯 | 米克尔@myisp.com | W1llgat3s |
| 橄榄枝 | obranch@myisp.com.uk | 第 1 年第 0 季第 3 集 |
| 弗兰克·弗雷泽 | finsin @ my ISP . net | P3 PFM 300 型核潜艇 |
| 周年纪念日 | aversary@myisp.com | B1 港币 3yg1rl |
| 特里·菲德 | tfide @ my ISP . com | 刀疤 3dst1ff |
| 玫瑰(灌木)丛 | rbush@myisp.co.uk | R3 db 100 ms |

为了维护个人页面的安全性,我们使用了一种叫做 sessions 的设备。一个会话通常包括一个完整的过程(如银行交易)。例如,如果一个人想把钱从储蓄账户转到支票账户,就会发生多个动作。首先从储蓄账户中取出钱,然后存入支票账户。如果动作不存款会怎么样?如果这两个动作没有绑定在一起(通过会话),钱就没了。但是,如果会话存在且操作未完成,则可以回滚。最糟糕的情况是钱永远不会被取出并存入银行。因此,钱没有丢失。

在网页中,会话还可以用于将页面内容关联在一起。这允许用户一次登录整个网站。会话可以确定用户已经登录,并为他们分配适当的访问权限。如果会话不存在,用户可能需要登录每个页面。其他数据也可以存储在一个会话中,以允许同一网站中的多个页面共享信息。

会话在服务器中处于活动状态,直到会话关闭或超时。它会在服务器管理员设置的一段时间(通常为 20 分钟)后超时。如果提供了关闭会话(注销)的代码,如果用户浏览了远离原始网站的多个页面,或者如果用户关闭了浏览器,会话也会关闭。然而,开发者不应该依赖于用户关闭浏览器或者滚动到离网站足够远的地方。应该总是有方法关闭会话。

区分两种类型的成员

前一章中的 simpledb 数据库有一个安全问题;任何未注册的用户都可以通过访问网站来查看成员列表。我们现在将确保成员表只能被开发人员和成员秘书(管理员)查看,而不能被整个世界查看。一种解决方案是指导会员秘书如何安装和使用 phpMyAdmin 然而,我们会假设我们的会员秘书不太懂计算机,也不想学习 phpMyAdmin。

我们的解决方案是限制对 view_table.php 页面和所有其他管理员页面的访问,这样只有会员秘书可以查看它们。这将通过为管理员使用会话和不同的 user_level 编号来实现。将为管理员提供一个用户友好的界面,以便他们可以搜索和修改会员记录。

总而言之,我们区分会员类型的规则如下:

  • 非会员将无法查看私人页面,因为用户只有注册后才能登录。

  • 注册会员将能够访问会员页面,因为他们可以登录。这样会启动一个会话,允许他们打开成员页面。

  • 管理员是唯一能够访问管理页面的人。当他们登录时,登录操作会启动一个会话,在他们可以打开管理员页面之前检查 user_level。user_level 不同于普通会员的用户级别。

在设计登录页面之前,我们还必须创建一种方法来区分普通注册成员和同时也是管理员的成员。管理员将拥有额外的特权。在下一个教程中,您将学习如何向现有的数据库表中添加标题为 user_level 的新列。这一新栏目将使我们能够区分会员类型。

创建用户级别以限制对个人页面的访问

为了限制对视图表页面的访问,我们将向 users 表添加一个名为 user_level 的列。在本专栏中,我们将为管理员指定一个用户级别编号 1。注意:如果您提供通过网页设置该值的能力,我们建议您使用一个不太明显的值(如 999)来表示管理级别。该号码与会员秘书的登录信息有关,与其他人无关。

访问 phpMyAdmin 并单击数据库 logindb。然后单击用户表。单击“结构”选项卡。查看记录下方,在加 1 列字样旁找到加符号,如图 3-6 所示。

img/314857_2_En_3_Fig6_HTML.jpg

图 3-6

添加符号出现在该屏幕的底部

接下来,在单词的右侧,添加一列,使用下拉菜单选择 registration_date。或者,选择表格末端标有的单选按钮,然后点击 Go 按钮。

您将被带到图 3-7 所示的屏幕。

img/314857_2_En_3_Fig7_HTML.jpg

图 3-7

为新的 user_level 列创建标题和属性

插入新列名及其属性,如下所示:

  • 名称:用户级别

  • 类型 : TINYINT

  • 长度/数值 : 1

  • 默认:无

  • 属性:无符号

当您对属性感到满意时,单击保存按钮。将创建新列。

下一步是启动 XAMPP 或 easyPHP,通过在浏览器的地址栏中输入http://localhost/login/index.php来访问页面。当索引页面出现时,点击标题菜单上的注册按钮,将该用户注册为普通会员,如下所示:

  • 名字:詹姆斯

  • 姓氏:史密斯

  • 电子邮件:jsmith@myisp.co.uk

  • 密码:blacks m1

当使用正确的电子邮件地址和密码时,詹姆斯·史密斯可以查看成员的特殊页面,但不能查看或修改成员列表。

我们现在将任命詹姆斯·史密斯为会员秘书,有权管理会员名单。为了安全起见,需要第二个名字和一个伪电子邮件地址和密码来访问管理部分;因此,我们需要一个额外的注册身份。第二个电子邮件地址很重要,因为办公室同事可能知道 James 的个人电子邮件地址。必须尽一切努力对管理员的登录信息保密。电子邮件地址应该是虚构的,但必须符合公认的电子邮件格式。现在使用假名(“Jack”)、新的电子邮件地址和新的密码再次注册会员秘书,如下所示:

  • 名字:杰克

  • 姓氏:史密斯

  • 电子邮件:jsmith@outcook.com

  • 密码 : D0gsb0dy

现在使用 phpMyAdmin 来访问数据库 logindb 和 users 表。点击浏览选项卡,找到管理员杰克·史密斯的记录,如图 3-8 所示。如果您单击编辑链接,您将能够将他的 user_level 字段从 0 更改为 1。单击“执行”按钮保存更改

img/314857_2_En_3_Fig8_HTML.jpg

图 3-8

找到杰克·史密斯的记录,并将 user_level 更改为 1

当詹姆斯·史密斯使用个人电子邮件地址和原始密码登录时,会员页面将像其他会员一样显示。这是因为原始密码的 user_level 为零,与所有其他注册成员相同。但是,当 James 登录 Jack 电子邮件地址和管理员密码时,将显示管理页面,因为 Jack 电子邮件地址的 user_level 是 1。

现实世界中的管理员会确保没有人能够发现替代的电子邮件地址和新密码。与管理员联系的人将使用原始姓名和个人电子邮件地址。

小费

如果詹姆斯·史密斯辞职,新的管理员被任命,网站管理员将删除杰克·史密斯的记录。新的管理员假名和新密码将被分配给 James 的替代者。当管理员用新密码登录时,新的会员秘书将被分配一个替代(假)电子邮件地址。然后,网站管理员将会员秘书的用户级别设置为 1。

现在我们将创建登录页面并引入一些新的 PHP 语句。

登录

从用户的角度来看,登录或退出网站的能力是没有意义的,除非它能带来一些好处,比如为成员提供一个特殊的页面或者允许在博客中发表评论。在接下来的部分中,我们将创建与注册用户交互的新页面。这将是一个登录页面,一个成员专用页面,和一个管理员页面。同一个登录页面用于登录成员页面或管理员页面。如果您认为登录过程完全保护了管理页面和成员页面,这是可以理解的。然而,想象一下下面的场景。

一个成员登录了一个私人页面,然后被叫去接电话。有人可以查看会员的计算机,并在浏览器的地址栏中读取个人页面的 URL。如果那个人在他们自己的电脑上输入网址,他们就可以访问个人网页。显然,必须防止这种情况。这是通过本章稍后描述的会话来实现的。

首先,我们必须为登录页面创建一个标题。

登录页面的标题

图 3-9 显示了带有三个菜单按钮的登录标题。

img/314857_2_En_3_Fig9_HTML.jpg

图 3-9

登录页面标题

清单 3-6 显示了登录头的代码。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group" aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
     onclick="location.href = 'login.php'" >Erase Entries</button>
  <button type="button" class="btn btn-secondary"
     onclick="location.href = 'register-page.php'">Register</button>
  <button type="button" class="btn btn-secondary"
     onclick="location.href = 'index.php'">Cancel</button>
</div>
    </nav>

Listing 3-6Creating the Header for the Login Page (login-header.php)

现在我们需要看看限制访问成员表的过程。我们将阻止普通用户和注册成员查看该表,但我们将允许管理员查看该表并修改记录。

图 3-10 显示了登录页面的外观。

img/314857_2_En_3_Fig10_HTML.jpg

图 3-10

登录页面

请注意,多余的按钮已从本页的标题中删除。

登录页面

现在我们有了一个 user_level 列,我们可以创建包含两个条件的登录页面。这些条件将识别管理员的用户级别(用户级别 1)和普通成员的用户级别(用户级别 0)。当真正的管理员登录时,他们将看到管理页面,其中包含新的菜单按钮。当注册成员登录时,他们将被重定向到成员页面。HTML 代码如清单 3-7a 所示。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
      "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
 "sha3849gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
   <script src="verify.js"></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
  padding:20px;">
  <?php include('login-header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
              <?php include('nav.php'); ?>
      </ul>
  </nav>
  <!-- Validate Input -->
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {                               //#1
 require('process-login.php');
} // End of the main Submit conditional.
?>
<div class="col-sm-8">
<h2 class="h2 text-center">Login</h2>
<form action="login.php" method="post" name="loginform" id="loginform">
  <div class="form-group row">
    <label for="email" class="col-sm-4 col-form-label">Email Address:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="email" name="email"
          placeholder="Email" maxlength="30" required
          value="<?php if (isset($_POST['email'])) echo $_POST['email']; ?>" >
    </div>
  </div>
  <div class="form-group row">
    <label for="password" class="col-sm-4 col-form-label">Password:</label>
    <div class="col-sm-8">
<input type="password" class="form-control" id="password" name="password" placeholder="Password" maxlength="40" required
          value=
           "<?php if (isset($_POST['password'])) echo $_POST['password']; ?>">
          <span>Between 8 and 12 characters.</span></p>
    </div>
  </div>
<div class="form-group row">
    <div class="col-sm-12">
        <input id="submit" class="btn btn-primary" type="submit" name="submit"
        value="Login">
    </div>
        </div>
        </form>
</div>
<!-- Right-side Column Content Section -->
<?php
 if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
               style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('footer.php');
 ?>
</footer>
</div>
</body>
</html>

Listing 3-7aCreating the Login Page (login.php)

代码的解释

到目前为止,大部分代码对您来说已经很熟悉了。清单中的注释解释了一些代码。

if ($_SERVER['REQUEST_METHOD'] == 'POST') {                               //#1
 require('process-login.php');
} // End of the main Submit conditional.

一个不同之处是,登录文件会引入流程登录代码来验证用户输入的信息。清单 3-7b 提供了 PHP 登录验证码。

表单是粘性的——也就是说,如果用户犯了一个错误,触发了一个错误消息,PHP 会保留并重新显示用户输入。使用注册页面,用户输入的内容在提交给服务器之前会得到验证。如果有问题,HTML5 会要求用户在提交页面之前纠正它。因此,数据在发送到服务器之前是正确的。如果浏览器和服务器之间的代码被破坏,服务器上的 PHP 代码将返回错误消息。

当有人登录时,只有使用服务器上的 PHP 代码才能验证电子邮件和密码是否正确。PHP 服务器代码必须首先从数据库表中检索电子邮件、密码和其他信息。然后将用户输入的电子邮件和密码与检索到的进行比较。服务器上的 PHP 代码(不是 HTML5 代码)将决定它是否有效。如果无效,代码将向浏览器返回一条错误消息。因为错误消息是由服务器发回的,所以我们也可以同时将用户输入的电子邮件和密码的值返回给表单。这允许更加用户友好的响应,而不会导致任何额外的服务器调用。清单 3-7b 显示了电子邮件和密码验证码。

<?php
// This section processes submissions from the login form
// Check if the form has been submitted:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    //connect to database
try {
    require ('mysqli_connect.php');
    // Validate the email address
// Check for an email address:
               $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
               $errors[] = 'You forgot to enter your email address';
               $errors[] = ' or the e-mail format is incorrect.';
        }
    // Validate the password
            $password =
               filter_var( $_POST['password'], FILTER_SANITIZE_STRING);
        if (empty($password)) {
               $errors[] = 'You forgot to enter your password.';
        }
   if (empty($errors)) { // If everything's OK.                              #1
// Retrieve the user_id, psword, first_name and user_level for that
// email/password combination
 $query =
  "SELECT userid, password, first_name, user_level FROM users WHERE email=?";
      $q = mysqli_stmt_init($dbcon);
      mysqli_stmt_prepare($q, $query);

        // bind $id to SQL Statement
        mysqli_stmt_bind_param($q, "s", $email);
       // execute query
       mysqli_stmt_execute($q);
        $result = mysqli_stmt_get_result($q);
        $row = mysqli_fetch_array($result, MYSQLI_NUM);
        if (mysqli_num_rows($result) == 1) {
        //if one database row (record) matches the input:-
        // Start the session, fetch the record and insert the
        // values in an array
        if (password_verify($password, $row[1])) {                         //#2
               session_start();
               // Ensure that the user level is an integer.
               $_SESSION['user_level'] = (int) $row[3];
               // Use a ternary operation to set the URL                     #3
$url = ($_SESSION['user_level'] === 1) ? 'admin-page.php' :
               'members-page.php';
        header('Location: ' . $url);
        // Make the browser load either the members or the admin page
        } else { // No password match was made.                              #4
$errors[] = 'E-mail/Password entered does not match our records. ';
$errors[] = 'Perhaps you need to register, just click the Register ';
$errors[] = 'button on the header menu';
        }
        } else { // No e-mail match was made.
$errors[] = 'E-mail/Password entered does not match our records. ';
$errors[] = 'Perhaps you need to register, just click the Register ';
$errors[] = 'button on the header menu';
}
}
if (!empty($errors)) {
               $errorstring =
               "Error! <br /> The following error(s) occurred:<br>";
               foreach ($errors as $msg) { // Print each error.
                       $errorstring .= " $msg<br>\n";
               }
               $errorstring .= "Please try again.<br>";
echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
               }// End of if (!empty($errors)) IF.
               mysqli_stmt_free_result($q);
               mysqli_stmt_close($q);
        }
 catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
} // no else to allow user to enter values
?>

Listing 3-7bVerifying the Login (process-login.php)

代码的解释

本节解释代码。

if (empty($errors)) { // If everything's OK                                  #1
   // Retrieve the user_id, psword, first_name and user_level for that
  // email/password combination
  $query =
  "SELECT user_id, password, first_name, user_level FROM users WHERE email=?";
      $q = mysqli_stmt_init($dbcon);
      mysqli_stmt_prepare($q, $query);
      // bind $id to SQL Statement
        mysqli_stmt_bind_param($q, "s", $email);
     // execute query
     mysqli_stmt_execute($q);
     $result = mysqli_stmt_get_result($q);
     $row = mysqli_fetch_array($result, MYSQLI_NUM);
     if (mysqli_num_rows($result) == 1) {

如果没有遇到问题(也就是说,如果用户正确输入了$email$password,则使用电子邮件从数据库中检索记录。假设电子邮件是唯一的。

if (password_verify($password, $row[1])) {                                //#2
       session_start();
      // Ensure that the user level is an integer.
      $_SESSION['user_level'] = $row[3];

必须使用 password_verify 函数来比较密码,因为存储在数据库表中的密码已经过哈希处理。如果来自数据库的密码与来自用户的密码匹配,则启动一个会话。它用于检查登录人员的授权。user_level 存储在一个会话变量(实际上是一个会话数组)中,以便我们在需要验证所需的访问级别时可以访问它。会话将在本说明部分的末尾进行全面解释。

/ Use a ternary operation to set the URL                                    #3
$url = ($_SESSION['user_level'] === 1) ? 'admin-page.php' :
 'members-page.php';
    header('Location: ' . $url);
    // Make the browser load either the members' or the admin page

在这一点上,我们将打破我们为这本书设定的规则:我们正在引入一段聪明的速记代码。这是一种常见的 PHP 设备,用于在两种结果之间进行选择,因此特别有用,也不太难理解。

单词“三元”( trivial )与一个海鸥科海鸟保护区没有任何关系。它是程序员和网站开发者使用的数学术语。它是“一元、二元、三元”系列中的第三个运算符换句话说,三元有三个部分。三元运算符通常被称为三元条件运算符,因为它是设置条件(if-then 表达式)的简洁方式。操作员使用符号?还有:。我们使用的格式将用户重定向到管理页面的 URL 或成员页面的 URL。代码如下:

$url = ($_SESSION['user_level'] === 1) ? 'admin-page.php' :
    'members-page.php';

右边语句的第一部分(括在括号中)查看会话数组中的 user_level,并询问它是否等于 1。三个等号表示“等同于”如果等于 1,问号把后面两项变成条件语句。它说“如果 user_level 等于 1,那么将admin-page.php赋给名为$url 的变量。”冒号相当于else;因此,如果 user_level 不等于 1,则 url 被设置为 members-page.php 的。记住,注册成员的 user_level 是 0。

*因此,变量$url 被设置为一个页面,用户使用熟悉的 header 语句被重定向到该页面。

header('Location: ' . $url);

三进制语句的简写形式如下:

if ($_SESSION['user_level'] === 1) {
header('location: admin-page.php');
}else{
header('location: members-page.php');
}
If the user's login data does not match the data in the database table, messages are stored in the error array as shown below.
       } else { // No password match was made.                              #4
$errors[] = 'E-mail/Password entered does not match our records. ';
$errors[] = 'Perhaps you need to register, just click the Register ';
$errors[] = 'button on the header menu';
       }
       } else { // No e-mail match was made.
$errors[] = 'E-mail/Password entered does not match our records. ';
$errors[] = 'Perhaps you need to register, just click the Register ';
$errors[] = 'button on the header menu';
}

图 3-11 显示了显示的错误信息。

img/314857_2_En_3_Fig11_HTML.jpg

图 3-11

登录不成功时显示的错误消息之一

没有给出两个条目(电子邮件或密码)中哪一个不正确的提示;这是一项安全功能。例如,如果错误消息指出电子邮件地址不正确,但密码可以接受,黑客就可以集中精力尝试可能的电子邮件地址。我们还应该限制无效尝试的次数,以减少黑客猜出正确组合的机会。然而,现在,让我们只验证电子邮件和密码。

新的数据库 logindb 将允许注册成员登录和退出成员页面。因此,未注册用户将被禁止访问该成员的页面。这并不完全安全,因为有人可能会越过该成员的肩膀,在浏览器的地址栏中发现该成员页面的文件名。如前所述,使用该名称,他们可以访问该成员的页面。这个安全漏洞将通过使用会话来解决。

会议

需要保密的页面将使用一个叫做 sessions 的特性;因此,现在将解释会话及其应用。注册会员应该能够登录一次,每次访问该网站。他们不应该为了访问几个私人页面而多次登录。当用户登录时,登录状态应该保持。登录和注销之间的时间称为会话。附加信息(如 user_level)可以存储为会话变量。默认情况下,这些变量实际上存储在一个会话数组中。会话关闭后,这些变量将不再可用。如果存在会话,网页可以使用这些变量为登录用户提供额外的功能。

小费

将会话视为一种安全通行证。当用户成功登录后,会存储该用户的安全通行证(会话和/或会话变量)。每个私页门口都有保安驻守。当用户试图打开一个私人页面时,该页面上的安全警卫检查安全通行证(会话和/或会话变量)。如果通行证确认该人被授权访问该页面,则个人页面被打开。当用户注销时,会话及其数组(包含会话变量)被销毁。会话携带关于用户的一些有用信息,可以在个人页面中访问和使用这些信息。

使用会话的过程如下:

  1. 如果用户被授权登录并成功登录,则会创建一个会话。

  2. 当用户试图访问个人页面时,个人页面会检查该用户的会话是否存在。

  3. 如果会话存在,将打开个人页面。

  4. 如果会话不存在,用户将被定向回登录页面。

  5. 通过使用会话,服务器可以区分不同类型的用户,例如非成员、注册成员和管理员。

  6. 登录的用户可以转到其他个人页面,也可以注销。当他们注销时,会话及其会话阵列将被销毁。

会话按如下方式启动:

// Set the session data:
session_start();

会话按如下方式关闭:

}else{ //cancel the session
        $_SESSION = array[]; // Destroy the variables
        $params = session_get_cookie_params();
        // Destroy the cookie
        Setcookie(session_name(), ", time() – 42000,
                $params["path"], $params["domain"],
                $params["secure"], $params["httponly"]);
        if (session_status() == PHP_SESSION_ACTIVE) { session_destroy(); } // Destroy the session itself
        header("location:index.php");
        }

\(_SESSION 设置为 array()技术上将\)_SESSION 指向一个新的空数组。旧数组(包含会话变量)将不再与当前会话相关联。这将导致系统破坏原始阵列。更简单地说,我们可以说在会话期间创建的任何会话变量现在都被清除了。可选地,会话信息可以存储在 cookie 中(不推荐)。如果选择此选项,session_get_cookie_params 函数将提供对活动会话 cookie 参数的访问。Setcookie 函数使用 time 参数设置一个负的过期时间(一个已经过去的时间)。这将导致 cookie 立即过期。session_destroy 函数销毁会话,这也将删除会话数组。一些使用 PHP 7.1+的开发人员报告说,如果会话已经处于非活动状态,可能会出现警告。if 语句会在尝试销毁会话之前检查会话的状态。

登录操作会创建一个会话,将变量保存在一个名为$_SESSION的数组中。当用户登录并试图打开一个私人页面时,PHP 代码检查是否为该用户建立了一个会话。如果有,个人页面将在用户的浏览器中打开。

在从process-login.php的代码中提取的以下语句中,从数据库中选择数据,然后如果密码正确,则创建一个会话(如果电子邮件不在表中,则不会返回记录,因此也会验证电子邮件)。user_level 保存在服务器上的一个临时会话变量中,以便在私有页面中使用。

$query =
"SELECT userid, password, first_name, user_level FROM users WHERE email=?";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind $id to SQL Statement
mysqli_stmt_bind_param($q, "s", $email);
// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
$row = mysqli_fetch_array($result, MYSQLI_NUM);
if (mysqli_num_rows($result) == 1) {
     //if one database row (record) matches the input:-
     // Start the session, fetch the record and insert the
     // values in an array
     if (password_verify($password, $row[1])) {
              session_start();
              // Ensure that the user level is an integer.
              $_SESSION['user_level'] = (int) $row[3];

小费

session_start()函数和检查会话的语句必须出现在页面顶部 HTML 标记之前。新手经常会被出现在每一个私人页面上的 session_start()函数所迷惑:它为什么不启动一个新的会话来替换现有的会话?答案是,如果一个会话存在,该函数不会启动另一个会话;相反,它检查用户的凭证,如果它们令人满意,它就允许加载个人页面。如果会话不存在,它将启动一个会话;但是,该会话是无效的,条件语句会将用户返回到登录页面。

会话创建唯一的会话 ID (SID)。第一次使用<?php session_start(); ?>语句时,它会建立一个 SID。它在服务器上注册 SID,并保存信息以便在任何个人页面上使用。下面是一个只有用户级别为 1 的管理员才能访问的个人页面开头的代码示例:

<?php
session_start();
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
{ header("Location: login.php");
   exit();
}
?>
<!doctype html>

代码必须在页面的顶部。代码可以翻译如下:“如果会话不存在,或者如果用户级别不等于 1,那么用户将被返回到登录页面。”

我们的下一步是创建一个受登录会话保护的会员专用页面。

会员专用页面

出版社下载members-page.php文件。或者使用清单 3-8 手工编码来创建页面。图 3-12 显示了为本教程创建的成员页面。

img/314857_2_En_3_Fig12_HTML.jpg

图 3-12

会员专用页面

注意图 3-12 中修改后的成员标题。

还要注意清单 3-8 中显示了会话、到登录页面的重定向以及新标题包含的文件。

<?php
session_start();                                                          //#1
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 0))
{ header("Location: login.php");
  exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
        href=
    "https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
     "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
        padding:20px;">
  <?php include('header-members.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8">
<h2 class="text-center">This is the Member's Page</h2>
<p>The members page content. The members page content. The members page content.
<br>The members page content. The members page content. The members page content.
<br>The members page content. The members page content. The members page content.
<br>The members page content. The members page content. The members page content.
</p>
<p class="h3 text-center">Special offers to members only.</p>
    <p class="text-center"><strong>T-Shirts 10.00</strong></p>
<img class="mx-auto d-block" src="img/polo.png" alt="Polo Shirt"> <!--#2-->
<br>
</div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
      <?php include('info-col.php'); ?>
        </aside>
  </div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 3-8Creating the Members Page (members-page.php)

代码的解释

本节解释代码。

<?php
session_start();                                                          //#1
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 0))
{ header("Location: login.php");
  exit();
}
?>

PHP session_start 语句必须立即出现在代码的顶部(就在

所有的私有页面都将以 session_start()函数开始。header redirection 语句(header())确保如果没有启动会话或者 user_level 不为零,用户将使用 header 命令重定向到登录页面。这个命令创建一个 HTTP Get 消息来检索登录页面。exit()函数还通过立即结束页面的执行来增加安全性。这确保了除非用户正确登录,否则不允许对页面的其余部分进行访问。

可选地,我们可以更加个性化,在我们的个人页面上提供特定于用户的问候。

$_SESSION['first_name'] = (int) $row[2];

如果我们将这一行代码添加到登录程序中类似的行之后,我们还可以存储用户的名字。请记住,我们不想简单地存储我们检索到的所有值,因为我们返回了密码,也不想尽可能安全地存储密码。

<?php
echo '<h2>Welcome to the Admin Page ';
if (isset($_SESSION['first_name'])){
echo "{$_SESSION['first_name']}";
}
echo '</h2>';
?>

我们可以修改成员页面中的欢迎代码,如前所示,为成员页面(或任何其他私人页面)提供更个性化的问候。

<p class="h3 text-center">Special offers to members only.</p>
    <p class="text-center"><strong>T-Shirts 10.00</strong></p>
<img class="mx-auto d-block" src="img/polo.png" alt="Polo Shirt"> <!--#2-->

为了给会员提供一些额外的内容,他们有机会购买 polo。h3 标题的显示由 Bootstrap h3 和 text-center 类控制。图像的显示由引导程序 mx-auto 和 d-block 类控制。这些类一起工作,使图像在列中居中。

下一节将展示规划的重要性。这一阶段的草率决定会导致日后的重大问题。

规划管理员的角色

我们现在需要非常仔细地计划,考虑会员秘书可能想对网站和数据库做什么。

让我们假设会员秘书想要做以下事情:

  • 使用原始会员的密码查看会员专用页面

  • 使用管理员的电子邮件和密码查看成员表

  • 偶尔更改管理员的密码以获得额外的安全性

为了实现这些目标,我们需要完成以下任务:

  • 为管理页面创建新标题

  • 创建管理页面

管理员的要求将决定管理页面标题中菜单的内容。

稍后,在第 4 和 5 章中,我们将添加额外的功能,以满足以下要求:

  • 使用管理员密码搜索和编辑成员的详细信息(例如,因结婚而更改姓名)。搜索按钮被添加到标题中;这个按钮将在以后的章节中起作用。

  • 使用管理员密码删除记录(当成员辞职或死亡时需要)。

  • 管理员还需要知道成员的总数——肯定有人会问他们。这将自动显示在成员表下方。

我们现在将创建管理员的标题和私人管理员的页面。

管理页面的新标题

基于前面计划的第一部分,管理页面的标题需要五个按钮:注销、查看成员、搜索、按地址搜索和新密码。图 3-13 显示了嵌入管理员页面的新标题。

img/314857_2_En_3_Fig13_HTML.jpg

图 3-13

新标题显示为嵌入在管理员页面中

标题现在有五个按钮。清单 3-9a 显示了新的按钮。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
 <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group"
        aria-label="Button Group">
  <button type="button" class="btn btn-secondary"
        onclick="location.href = 'logout.php'" >Logout</button>
  <button type="button" class="btn btn-secondary"
        onclick="location.href = 'admin_view_users.php'">View Members
        </button>
  <button type="button" class="btn btn-secondary"
        onclick="location.href = '#'">Search</button>
  <button type="button" class="btn btn-secondary"
        onclick="location.href = '#'">Address</button>
  <button type="button" class="btn btn-secondary"
        onclick="location.href = 'register-password.php'">New Password
        </button>
</div>
    </nav>

Listing 3-9aCreating the Administration Page Header (admin-header.php)

注意

搜索按钮不起作用,因为我们尚未创建搜索页面。我们将在第四章创建搜索页面。

在下一步中,我们将向管理页面添加新的标题和会话详细信息。清单 3-9b 显示了代码。

管理员的页面

清单 3-9b 显示了管理页面的代码。

<?php
session_start();                                                          //#1
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
{ header("Location: login.php");
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Administration Page/title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1,
        shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
        padding:20px;">
  <?php include('header-admin.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8">
<h2 class="text-center">This is the Administration Page</h2>
<h3>You have permission to:</h3>
<p>Edit and Delete a record</p>
<p>Use the View Members button to page through all the members</p>
<p>Use the Search button to locate a particular member</p>
<p>Use the New Password button to change your password.
</p>
</div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
      <?php include('info-col.php'); ?>
        </aside>
  </div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 3-9bCreating an Administration Page (admin-page.php)

代码的解释

本节解释代码。

<?php                                                                                //#1
session_start();
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
{ header("Location: login.php");
   exit();
}
?>

同样,session_start 是一个安全防护,它包含在代码的顶部,只允许那些使用正确的 user_level (1)正确登录的用户访问页面。如果他们没有正确登录或没有 user_level 1 许可,他们将被重定向到登录页面。exit()确保它们不能在页面中继续前进。

注销页面

下一页允许用户注销。当不再需要会话时,代码会将其销毁。

这不是用户可见的页面。它充当登录页面和主页之间的中间页面。清单 3-9c 显示了代码。

<?php
session_start();//access the current session.                                          #1
// if no session variable exists then redirect the user
if (!isset($_SESSION['user_id'])) {                                                  //#2
header("location:index.php");
exit();
//cancel the session and redirect the user:
}else{ //cancel the session                                                          //#3
 $_SESSION = array(); // Destroy the variables
      $params = session_get_cookie_params();
      // Destroy the cookie
      Setcookie(session_name(), ", time() – 42000,
      $params["path"], $params["domain"],
      $params["secure"], $params["httponly"]);
if (session_status() == PHP_SESSION_ACTIVE) {
      session_destroy(); } // Destroy the session itself
      header("location:index.php");
      }

Listing 3-9cCreating the Logout Code (logout.php)

代码的解释

本节解释代码。

session_start();//access the current session.                                          #1

该代码允许登录用户访问在登录页面中启动的会话的内存中的信息。第 1 行将新页面链接到该会话中存储的信息。

//if no session variable then redirect the user
if (!isset($_SESSION['user_id'])) {                                                  //#2
header("location:index.php");
exit();

如果会话变量 user_id 不存在,用户将被重定向到主页。这表明他们从未登录。

}else{ //cancel the session                                                            #3
 $_SESSION = array(); // Destroy the variables
      $params = session_get_cookie_params();
      // Destroy the cookie
      Setcookie(session_name(), ", time() – 42000,
      $params["path"], $params["domain"],
      $params["secure"], $params["httponly"]);
if (session_status() == PHP_SESSION_ACTIVE) {
session_destroy(); } // Destroy the session itself
header("location:index.php");
      }

如果会话存在并且创建了 cookie,则用户通过使 cookie 过期(将时间设置为负值)来注销。会话被销毁,用户被重定向到主页。

测试登录/注销功能

web 设计者或数据库管理员可以使用 phpMyAdmin 访问成员表。通常,管理员/会员秘书会通过网页查看会员列表。管理员/会员秘书应该只能查看、编辑或删除记录。这将在下一节中介绍。

启动 XAMPP 或 easyPHP,并使用浏览器测试登录功能。在浏览器的地址栏中输入以下 URL:

http://localhost/login/login.php

现在,您应该能够为普通成员加载成员页面了。当管理员登录时,您还可以加载管理员页面。唯一不起作用的按钮是搜索按钮。这些将在第四章中讨论。关于管理员页面,表格视图还不是很有用,因为管理员不能删除或修改记录。这将在第四章中通过添加删除和编辑链接来实现。

在 phpMyAdmin 中修改和删除单个记录很容易,但是使用了术语 delete 而不是术语 drop。 Drop 用于删除整个表和数据库。

修改和删除个人记录

网站管理员/开发者可以按如下方式修改和删除记录:

img/314857_2_En_3_Fig14_HTML.jpg

图 3-14

编辑或删除单个记录

  1. 通过在浏览器的地址栏中输入以下内容来访问 phpMyAdmin:

    http://localhost/phpmyadmin/

  2. 访问 logindb 数据库,然后访问 users 表。

  3. 选择“浏览”选项卡,该选项卡显示注册成员的列表。如图 3-14 所示。

找到要修改或删除的成员记录并选中其复选框(如图 3-14 中圆圈所示)。然后点击编辑;您将看到一个屏幕,允许您更改有关用户的信息。您也可以单击删除来删除成员。你会被问到你是不是真心的。然后你可以拒绝或接受。

注意

我们在这一章中增加了安全性。然而,我们将在以后的章节中增加额外的安全性。本章中的代码比第二章中的代码更安全。然而,它还没有为“真实世界”做好准备。

摘要

在这一章中,我们修改了各种标题,给它们更多有意义的菜单按钮,我们也删除了多余的按钮。我们发现了如何登录个人页面以及如何从这些特殊页面注销。我们发现了一种通过使用会话来区分不同类型成员的方法。我们创建了一个会员专用页面,并学习了如何使用会话来保护私人页面。我们还学习了如何使用会话,以便只有管理员可以访问管理员页面。

在每个教程中,都为 PHP 和 SQL 代码提供了解释。我们通过移除普通成员查看成员表的能力来增加一点安全性。我们防止未经授权的人使用浏览器进入私人网页的网址。我们还发现了开发人员如何修改或删除单个记录。

在下一章,我们将学习会员秘书/管理员如何搜索、修改和删除记录。*

四、创建管理界面

在本章中,我们将为具有中等计算机技能的管理员呈现一组用户友好的页面。我们假设管理员不会使用 phpMyAdmin,但是他们知道足够多的知识,能够登录、访问易于使用的显示和使用简单的编辑工具。我们将使成员表具有交互性,以便管理员可以搜索、编辑、删除和重新排序记录。只有管理员才被允许访问这些设施来删除和修改记录。

完成本章后,您将能够创建具有以下功能的网页:

  • 管理数据库

  • 删除和编辑记录

  • 对记录的显示分页

  • 搜索单个记录

警告

当我们讨论本书中的表格时,尽量不要将屏幕上显示的表格与数据库表格混淆。这些类型的表是两个不同的实体。例如,表 4-1 是一个数据库表,对用户不可见。图 4-2 是显示在用户屏幕上的表格。

管理数据库

本章中的教程基于前面章节中使用的模板。我们将通过使用数据库及其文件夹的新名称来保持与之前教程的不同。但是,如果表位于不同的数据库中,则该表可以与早期教程同名。因此,该表将继续被称为用户

让我们从创建数据库和访问它的能力开始。

创建一个新文件夹和一个新数据库,如下所示:

  1. 在 XAMPP htdocs 文件夹或者 easyPHP eds-www 文件夹中,新建一个名为 admintable 的文件夹。

  2. 要么从本书的网页 www.apress.com (推荐)下载 PHP 文件,要么键入本章和上一章所示的代码,并将其放在新的 admintable 文件夹中。

    注意如果您从电子书中复制粘贴代码,某些引用可能无法正确复制。首先将代码复制到一个基本的文本编辑器(如 Microsoft Notepad)中,可以很容易地避免问题。编辑器会自动将任何不正确的报价转换为标准报价。然后保存文件并在 PHP 编辑器中打开它(比如 Notepad++)。

如果你正在创建你自己的文件,从第三章复制所有文件并粘贴到你的新文件夹 admintable 中。在新文件夹中,修改文件 mysqli_connect.php ,如下页所列。将文件admin-header.php重命名为 header-admin。

  1. 现在打开 phpMyAdmin。

  2. 创建一个名为 admintable 的新数据库。

  3. 选中 admintable 旁边的复选框,然后单击 Check Privileges。

  4. 单击添加用户按钮。

  5. 输入新用户和密码,如下所示:

    用户:站长

    密码 : C0ffeep0t

    主机:本地主机

  6. 请务必在笔记本上记录新用户和密码的详细信息。

  7. 向下滚动到全局权限(全部选中/全部取消选中),然后单击全部选中。

  8. 向下滚动到表单的底部,然后单击“添加用户”按钮(或者在某些版本中单击“执行”按钮)。

要连接到管理数据库,可以使用清单 4-1 中所示的代码。

<?php
// Create a connection to the admintable database and set the encoding
Define ('DB_USER', 'webmaster');
Define ('DB_PASSWORD', 'C0ffeep0t');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'admintable');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
mysqli_set_charset($dbcon, 'utf8'); // Set the encoding...

Listing 4-1Creating the Code for Connecting to the admintable Database (mysqli_connect.php)

在尝试运行任何其他程序之前,请确保测试您对数据库的访问权限。一旦您确定您的用户 ID 和密码正确地访问了数据库,那么继续下面几节中显示的过程。要测试连接,请在浏览器中键入http://localhost/admin table/mysqi _ connect . PHP。如果您没有收到任何错误,并且看到一个空白页,则测试成功。

用户表

在 phpMyAdmin 中,确保单击新的数据库 admintable,然后创建这个包含七列的 users 表。将编码设置为 utf8-general-ci。给这些列赋予表 4-1 中所示的标题和属性。

记住,当在 phpMyAdmin 中选择 A_I 复选框时,一个弹出窗口可能会要求您验证 userid 是否是主索引,并将再次请求大小。不要在弹出窗口中放一个尺寸。如果你这样做,你会得到一个错误。

表 4-1

用户表的属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 使用者辩证码 | 中位 | six | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 注册日期 | DATETIME |   | 没有人 |   | -是吗 |   | -是吗 |
| 用户级别 | 蒂尼因特 | one | 没有人 | 无符号的 | -是吗 |   | -是吗 |

密码列的大小将由 PHP 最新的哈希标准决定。请访问 www.php.net 并根据需要调整尺寸。

使用register-page.php文件(包含在从出版社下载的第 4 章的文件中)。com )使用以下数据将管理员注册为普通会员:

  • 名字:詹姆斯

  • 姓氏:史密斯

  • 电子邮件:jsmith@myisp.co.uk

  • 密码:blacks m1

注册会员詹姆斯·史密斯将以笔名杰克·史密斯被任命为会员秘书。我们可以有把握地假设,詹姆斯·史密斯(别名杰克·史密斯)除了查看管理页面之外,偶尔也会查看成员页面。他将使用他的会员电子邮件和密码来查看会员页面,但是他需要不同的电子邮件和密码来访问管理部分。现在用他的假名和新密码第二次注册会员秘书,如下所示:

  • 名字:杰克

  • 姓氏:史密斯

  • 电子邮件:jsmith@outcook.com

  • 密码 : D0gsb0dy

重要的

使用 phpMyAdmin 将杰克·史密斯的用户级别更改为 1。

注意

要更改 user_level,请在 phpMyAdmin 中单击数据库名称(在列列表中),然后单击数据库名称下面列出的 users 表。单击杰克·史密斯左侧的编辑图标。将级别更改为 1,然后单击“开始”(或“保存”)。

现在注册这些成员,这样我们就有东西玩了。使用表 4-2 中的细节。

表 4-2

向成员建议的详细信息

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- |
| 迈克 | 这是什么 | 米克尔@myisp.com | w1 llg @ ?? |
| 橄榄 | 树枝 | obranch@myisp.co.uk | An01yon@ |
| 弗兰克 | 香 | fincense@myisp.net | p @ RFM 3 |
| 泰丽 | Fide | tfide @ my ISP . com | 刀疤@dst1ff |
| 玫瑰 | 矮树丛 | rbush@myisp.co.uk | R@dbl00ms |
| 安妮 | 周年纪念 | aversary@myisp.com | h8 PPy # D8 和 |

您现在总共有八条记录。

注意

现在,我们将会话与成员的 user_level 一起使用,如果不首先以管理员身份登录(user_level = 1),您将无法访问任何管理页面。如果您试图直接访问这些页面,它们会将您重定向到登录页面。

我们将使用第三章的管理员页面(图 4-1 )来访问本章中的新功能。下一节将解释这些变化。

img/314857_2_En_4_Fig1_HTML.jpg

图 4-1

管理页面

我们将使用名为 view_users.php 的修订页面显示一个扩展的数据表。下一节将演示这个概念。

修改“查看用户”页面以包括编辑和删除

显示表需要两个额外的链接列,以便管理员能够编辑和删除记录。在第三章中,我们将名字和姓氏连接在一起,这样它们就出现在一列中。为了能够对记录重新排序,名字和姓氏显示在不同的列中。密码必须始终受到保护,决不能显示在表格中。管理员通常能够重置密码,但不能查看当前密码。修改后的管理员显示将有六列,如表 4-3 所示。请记住,这不是用户的数据库表,而是将在屏幕上显示给管理员的表;这两张桌子完全不同。

表 4-3

仅为管理员显示的新表格的格式

|

编辑

|

删除

|

|

西方人名的第一个字

|

电子邮件

|

注册日期

|
| --- | --- | --- | --- | --- | --- |
| 编辑 | 删除 | 这是什么 | 迈克 | 米克尔@myisp.com | 2018 年 10 月 30 日 |

两个新列现在有链接,使管理员能够与记录进行交互。最重要的是,这个页面只对管理员开放;所以它需要一个安全卫士,换句话说就是一个会话

图 4-2 显示了该交互桌的外观

img/314857_2_En_4_Fig2_HTML.jpg

图 4-2

用于管理员的交互式表格

注意

您可以使用 XAMPP 或 easyPHP 和浏览器来查看该表,但是在我们创建用于删除和编辑记录的页面之前,编辑和删除链接将不起作用。本章稍后将介绍这些页面。

清单 4-2 显示了创建新视图用户页面的修改。

出于安全考虑,管理员页面以会话代码开头。

<?php                                                                                //#1
session_start();
if (!isset($_SESSION['user_level']) || ($_SESSION['user_level'] != 1))
{
 header("Location: login.php");
exit();
}
?>
 <!DOCTYPE html>
<html lang="en">
<head>
 <title>Template for an interactive web page</title>
 <meta charset="utf-8">
 <meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
        href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
        integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px; background: linear-gradient(white, #0073e6); padding:20px;">
               <?php include('header.php'); ?>
</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
 <nav class="col-sm-2">
        <ul class="nav nav-pills flex-column">
<?php include('nav.php'); ?>
        </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8">
<h2 class="text-center">These are the registered users</h2>
<p>
<?php
try {
// This script retrieves all the records from the users table.                  #2
require('mysqli_connect.php'); // Connect to the database.
// Make the query:
// Nothing passed from user safe query
$query = "SELECT last_name, first_name, email, ";
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
$query .=
        " AS regdat, userid FROM users ORDER BY registration_date ASC";
// Prepared statement not needed since hardcoded
$result = mysqli_query ($dbcon, $query); // Run the query.
if ($result) { // If it ran OK, display the records.
// Table header.                                                                #3
echo '<table class="table table-striped">
<tr>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
<th scope="col">Last Name</th>
<th scope="col">First Name</th>
<th scope="col">Email</th>
<th scope="col">Date Registered</th>
</tr>';
// Fetch and print all the records:                                             #4
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
               // Remove special characters that might already be in table to   #5
               // reduce the chance of XSS exploits
               $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
               $last_name = htmlspecialchars($row['last_name'], ENT_QUOTES);
               $first_name = htmlspecialchars($row['first_name'], ENT_QUOTES);
               $email = htmlspecialchars($row['email'], ENT_QUOTES);
               $registration_date =
htmlspecialchars($row['regdat'], ENT_QUOTES);                                 //#6
               echo '<tr>
                       <td><a href="edit_user.php?id=' . $user_id .
'">Edit</a></td>
                       <td><a href="delete_user.php?id=' . $user_id .
                              '">Delete</a></td>’;                            //#7
               echo '<td>' . $last_name . '</td>
                       <td>' . $first_name . '</td>
                       <td>' . $email . '</td>
                       <td>' . $registration_date . '</td>
               </tr>';
        }
        echo '</table>'; // Close the table.                                    #8
        mysqli_free_result ($result); // Free up the resources.
}
else { // If it did not run OK.
// Error message:
echo
'<p class="text-center">The current users could not be retrieved. ';
echo 'We apologize for any inconvenience.</p>';
// Debug message:
// echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
exit;
} // End of if ($result)
mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e) // We finally handle any problems here
{
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
}
catch(Error $e)
{
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
         }
?>
</div>
<!-- Right-side Column Content Section -->
<aside class="col-sm-2">
      <?php include('info-col.php'); ?>
</aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 4-2Amending the Table View to Include Two Links (admin-view-users.php)

代码的解释

本节解释代码。

<?php
session_start();
//                                                                                    #1
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
{
        header("Location: login.php");
        exit();
}
?>

此会话代码保护页面,并将任何未经授权的用户返回到登录页面。除非是管理员(user_level = 1),否则任何人都不能访问该页面。

<?php
try {
// This script retrieves all the records from the users table.                        #2

require('mysqli_connect.php'); // Connect to the database.

// Make the query:
// Nothing passed from user safe query

$query =
"SELECT last_name, first_name, email, DATE_FORMAT(registration_date, '%M %d, %Y')";

$query .=
        " AS regdat, userid FROM users ORDER BY registration_date ASC";

// Prepared statement not needed since hardcoded
$result = mysqli_query ($dbcon, $query); // Run the query.

if ($result) { // If it ran OK, display the records.

前面的代码从 users 表中选择项目,这些项目将填充所显示的表中的列。DATE_FORMAT 函数将以月、日、年的格式格式化从表中检索的 registration_date。AS 语句将允许我们使用 regdat 名称检索这个格式化的日期。如果我们使用 registration_date 名称检索日期,它仍然是表中存在的任何格式。表格记录按注册日期的升序(ASC)显示。要按降序显示记录,请使用 DESC。

// Table header.                                                                       #3
echo '<table class="table table-striped">
<tr>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
<th scope="col">Last Name</th>
<th scope="col">First Name</th>
<th scope="col">Email</th>
<th scope="col">Date Registered</th>
</tr>';

这段代码显示了一个 HTML 表格,在标题行中有六个标题。每一行都有交替的颜色。

// Fetch and print all the records:                                                    #4
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {

代码遍历用户表的数据(包含在\(result 中),直到所有数据都显示出来。这是通过 while()函数实现的。数据被放入一个名为\)row 的关联数组中。关联数组使用键(如 user_id)而不是索引(数字)来确定使用哪一列。例如,在下面的代码中,associate array $row 包含一个键为 userid 的列。这些键是使用数据库表中的列名自动创建的。

       // Remove special characters that might already be in table to                  #5
               // reduce the chance of XSS exploits
               $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
               $last_name = htmlspecialchars($row['last_name'], ENT_QUOTES);
               $first_name = htmlspecialchars($row['first_name'], ENT_QUOTES);
               $email = htmlspecialchars($row['email'], ENT_QUOTES);
               $registration_date =
htmlspecialchars($row['regdat'], ENT_QUOTES);

我们正在尽一切努力在数据库表中插入好的有效数据。那么,我们不能假设我们只是从数据库中提取了好的数据吗?不要。原因有二;第一个是表中的数据可能包含可被执行的有害代码,第二个是我们的程序可能被欺骗,以为数据来自表,而实际上它来自其他地方。所以,我们必须确保我们的代码是不可执行的。最简单的方法是将所有可执行字符(如引号、逗号、括号)转换成转义字符("-->/")。这不会清除数据,但会使数据无法执行。ENT_QUOTES 参数坚持单引号和双引号都要转义。本章和其余章节中的所有代码都将使用这种格式来确保显示的任何数据都是不可执行的。

//                                                                                  #6
               echo '<tr>
<td><a href="edit_user.php?id=' . $user_id . '">Edit</a></td>
<td><a href="delete_user.php?id=' . $user_id . '">Delete</a></td>’;

前面的代码在每行的前两个单元格中创建了一个链接。这些链接连接到页面 edit_user.phpdelete_user.php 。创建的 URL 链接包含一个 id 参数,该参数的值在$user_id 变量中给出(来自表)。当代码遍历 users 表中的数据时,它为每一行存储相应的 user_id。

该行的 user_id 通过 PHP 程序的 URL 地址“传递”给处理删除或编辑的页面。这里有一个例子:

<td><a href="delete_user.php?id=1">Delete</a></td>

ID (user_id)是数据库表中的实际行号。PHP 程序将使用这个数字来决定编辑或删除哪一行。整行代码都是一个链接,单击它将编辑或删除所标识的行。为了清楚起见,图 4-3 显示了交互表和两个链接的片段。

img/314857_2_En_4_Fig3_HTML.jpg

图 4-3

在前两个单元格中显示一条记录和两个链接

//                                                                                  #7
              echo '<td>' . $last_name . '</td>
               <td>' . $first_name . '</td>
               <td>' . $email . '</td>
               <td>' . $registration_date . '</td>
               </tr>';

在前面的代码块中,\(last_name,\)first_name,\(email 和\)registration_date 中包含的值显示在表的相关列中。

echo '</table>'; // Close the table.                                                #8
mysqli_free_result ($result); // Free up the resources.

表的结束标记完成了表的显示,并且释放了内存资源。程序本身会在停止时释放资源,但我们也可以在语句显示时这样做。剩下的代码处理错误消息,并将页脚添加到显示的页面中。在我们创建用于搜索、编辑和删除的页面之前,我们需要停下来做一个有用的转换。

下一节描述一次显示一页表格的方法。

显示记录页面(分页)

由文件 view_users.php 创建的表格显示最多可以显示 30 条记录。更多的记录会导致表格超出大多数监视器的显示大小。这将迫使管理员向下滚动页面以查看更多记录。想象一下,如果数据库包含 1,000 条或更多记录,会发生什么。创建一次在一个屏幕上查看表格数据的能力将提供一个解决方案;然而,即使这样,对于大型数据库来说也是不方便的。在本章的后面,我们将研究一种在大型数据库中选择和查看特定记录的更巧妙的方法;同时,我们将学习如何显示表格,以便管理员可以一次看到一页。这个过程被称为分页

20 到 25 行的页面长度是明智的选择;然而,出于本教程的目的,让我们测试一下这种能力,而不必在数据库中输入大量记录。我们将把每个显示页面的行数设置为 4,因此我们只需要再输入几个成员来进行演示。这将为我们提供四个页面来显示。

表 4-4 建议另外十个成员注册。

表 4-4

注册会员的详细信息

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- |
| Ⅳ. 珀西 | 转向 | pver @ my ISP . com | K @ @ pg0ing |
| 斯坦 | 达尔德族人 | sdad @ my ISP . net | B@tt13fl@g |
| Honora | 骨 | nbone@myisp.com | L1k@t@rr1@r |
| 巴里 | 凯德 | bcade@myisp.co.uk | Bl0ckth@m |
| 迪伊 | 被拒绝 | djected@myisp.ork.uk | Gl00mnd00m# |
| 林恩 | 马 | lseed@myisp.com | Pa1nt@rs01 |
| 巴里 | 音调 | btone@myisp.net | N1c@v01c3 |
| 海伦 | 背部 | hback@myisp.net | Agr1ms0rt1@ |
| 贾斯廷 | 情况 | jcase@myisp.co.uk | 我正在寻找 10u@ |
| 尿壶 | 设置文件属性 | jattrik@myisp.com | 火@rlyman |

数据库现在将有 18 条记录。当我们将它除以 4(每页的行数)时,我们将得到 4 . 5 页。根据这一解释,在清单 4-3a 中,对于 admin_view_users.php ,您将看到每页的行数由变量 pagerows 设置,如下所示:

$pagerows = 4;

通过给变量赋值(比如 9 或 15)来试验这个值,以查看在网页中显示的效果。

图 4-4 显示了每页四条记录的显示以及通过前后移动显示表格内容的链接。该页面还显示成员总数。

img/314857_2_En_4_Fig4_HTML.jpg

图 4-4

显示屏现在每页显示四条记录,并显示会员总数

名为 process _admin _ view _ users . PHP的页面包含分页代码。

在图 4-4 中,显示在工作台下方的链接“上一个”和“下一个”使工作台移动。除了这些链接,我们还可以使用可点击的页码或按钮。然而,如果你不知道每一页上有什么,一个页码是没有多大用处的。浏览页面并显示所有成员的能力对管理员很有用。分页也将使管理员能够打印每一页。这将在第七章中描述。为了显示特定成员的记录,我们将使用本章后面显示的新的搜索菜单按钮。

清单 4-3a 包含了支持分页的代码。在文件 admin_view_users.php 中放置了一个必需的语句,以将 php 代码与其他代码分开。 admin_view_users.php 文件没有列出,因为它与已经显示的其他文件相似。您可以在本章的下载文件中查看它。

<?php
try {
        // This script retrieves all the records from the users table.
        require('mysqli_connect.php'); // Connect to the database.
        //set the number of rows per display page
        $pagerows = 4; //                                                           #1
        // Has the total number of pages already been calculated?
        if ((isset($_GET['p']) && is_numeric($_GET['p']))) {
        //already been calculated
               $pages = htmlspecialchars($_GET['p'], ENT_QUOTES);
               // make sure it is not executable XSS
        }else{//use the next block of code to calculate the number of pages         #2
               //First, check for the total number of records
               $q = "SELECT COUNT(userid) FROM users";
               $result = mysqli_query ($dbcon, $q);
               $row = mysqli_fetch_array ($result, MYSQLI_NUM);
               $records = htmlspecialchars($row[0], ENT_QUOTES);
               // make sure it is not executable XSS
               //Now calculate the number of pages
               if ($records > $pagerows){ //                                        #3
                //if the number of records will fill more than one page
               //Calculate the number of pages and round the result up to the
               //  nearest integer
                       $pages = ceil ($records/$pagerows); //
               }else{
                       $pages = 1;
               }
        }//page check finished
        //Declare which record to start with                                        #4
        if ((isset($_GET['s'])) &&( is_numeric($_GET['s'])))
        {
               $start = htmlspecialchars($_GET['s'], ENT_QUOTES);
               // make sure it is not executable XSS
        }else{
               $start = 0;
        }
        $query = "SELECT last_name, first_name, email, "; //                        #5
        $query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
        $query .=
        " AS regdat, userid FROM users ORDER BY registration_date ASC";
        $query .=" LIMIT ?, ?";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // bind start and pagerows to SQL Statement
        mysqli_stmt_bind_param($q, "ii", $start, $pagerows);
        // execute query
        mysqli_stmt_execute($q);
        $result = mysqli_stmt_get_result($q);
        if ($result) {
        // If it ran OK (records were returned), display the records.
               // Table header.
               echo '<table class="table table-striped">
               <tr>
               <th scope="col">Edit</th>
               <th scope="col">Delete</th>
               <th scope="col">Last Name</th>
               <th scope="col">First Name</th>
               <th scope="col">Email</th>
               <th scope="col">Date Registered</th>
               </tr>';
                       // Fetch and print all the records:
               while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
               // Remove special characters that might already be in table to
                       // reduce the chance of XSS exploits
                       $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
                       $last_name =
                              htmlspecialchars($row['last_name'], ENT_QUOTES);
                       $first_name =
                              htmlspecialchars($row['first_name'], ENT_QUOTES);
                       $email = htmlspecialchars($row['email'], ENT_QUOTES);
                       $registration_date =
                              htmlspecialchars($row['regdat'], ENT_QUOTES);
                       echo '<tr>
                       <td><a href="edit_user.php?id=' . $user_id .
                              '">Edit</a></td>
                       <td><a href="delete_user.php?id=' . $user_id .
                              '">Delete</a></td>
                       <td>' . $last_name . '</td>
                       <td>' . $first_name . '</td>
                       <td>' . $email . '</td>
                       <td>' . $registration_date . '</td>
                       </tr>';
               }
               echo '</table>'; // Close the table.
               mysqli_free_result ($result); // Free up the resources.
        }
        else { // If it did not run OK.
               // Error message:
        echo '<p class="text-center">The current users could not be ';
        echo 'retrieved. We apologize for any inconvenience.</p>';
               // Debug message:
        // echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
               exit;
        } // End of else ($result)
        // Now display the total number of records/members.                             #6
        $q = "SELECT COUNT(userid) FROM users";
        $result = mysqli_query ($dbcon, $q);
        $row = mysqli_fetch_array ($result, MYSQLI_NUM);
        $members = htmlspecialchars($row[0], ENT_QUOTES);
        mysqli_close($dbcon); // Close the database connection.
        $echostring = "<p class='text-center'>Total membership: $members </p>";
        $echostring .= "<p class='text-center'>";
        if ($pages > 1) {   //                                                          #7
               //What number is the current page?
               $current_page = ($start/$pagerows) + 1;
               //If the page is not the first page then create a Previous link
               if ($current_page != 1) {
                       $echostring .=
               '<a href="admin_view_users.php?s=' . ($start - $pagerows) .
                              '&p=' . $pages . '">Previous</a> ';
               }
               //Create a Next link                                                     #8
               if ($current_page != $pages) {
                       $echostring .=
               ' <a href="admin_view_users.php?s=' . ($start + $pagerows) .
                              '&p=' . $pages . '">Next</a> ';
               }
               $echostring .= '</p>';
        echo $echostring;
        }
} //end of try
catch(Exception $e) // We finally handle any problems here
{
        // print "An Exception occurred. Message: " . $e->getMessage();
        print "The system is busy please try later";
}
catch(Error $e)
{
        //print "An Error occurred. Message: " . $e->getMessage();
        print "The system is busy please try again later.";
}
?>

Listing 4-3aCreating a Table Display

That Will Paginate (process_admin_view_users.php)

代码的解释

本节解释代码。

$pagerows = 4;                                                                    // #1

$pagerows 控制每页显示的行数。显示设置为每页四条记录。(在真实的页面中,您可以将这个值设置为 20 左右。)

}else{//use the next block of code to calculate the number of pages                  #2
      //First, check for the total number of records
      $q = "SELECT COUNT(userid) FROM users";
      $result = mysqli_query ($dbcon, $q);
      $row = mysqli_fetch_array ($result, MYSQLI_NUM);
      $records = htmlspecialchars($row[0], ENT_QUOTES);
      // make sure it is not executable XSS
      //Now calculate the number of pages

前面的代码将计算数据库的 users 表中的记录数(COUNT(user_id))。创建一个数值数组(由 MYSQLI_NUM 的用户指示)\(row,并将行数放入其中。这个数组的唯一内容是记录的数量。这个值是从 array 的零位置复制的,经过清理后放入变量\)records 中。

下面的代码检查记录数是否大于每页显示的记录数($pagerows)。

               if ($records > $pagerows){ //                                         #3
                //if the number of records will fill more than one page
//Calculate the number of pages and round the result up to the nearest integer
                       $pages = ceil ($records/$pagerows); //
               }else{
                       $pages = 1;
               }

如果记录数大于每页显示的记录数,PHP 函数 ceil()将向上舍入所需的页数(由记录数除以每页记录数确定)。在教程中,我们有 18 条记录,结果是 4.5 页(18 除以 4)。这是四舍五入到五页。第五页将只显示两条记录。如果记录数不大于每次显示的行数,则只有一页。函数 ceil()表示将上限设置为高于实际计数的整数;在这种情况下,4.5 变成了 5

//Declare which record to start with                                                   #4
if ((isset($_GET['s'])) &&( is_numeric($_GET['s'])))
{
        $start = htmlspecialchars($_GET['s'], ENT_QUOTES);
        // make sure it is not executable XSS
}else{
        $start = 0;
}

当用户单击网页上的上一个或下一个链接时,s 中的值被传递(稍后将解释该代码)。如果用户确实单击了这些链接中的一个,那么它将包含一个要显示的记录的起点(比如记录号 5 ),并且该值将放在\(start 中。如果没有传递任何值(没有设置 s),则假定将显示第一组记录,并将\)start 的值设置为 0。如果 s 中以某种方式存在一个非数值,那么$start 也将被设置为 0。

$query = "SELECT last_name, first_name, email, "; //                                   #5
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
$query .=
" AS regdat, userid FROM users ORDER BY registration_date ASC";
$query .=" LIMIT ?, ?";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind start abd pagerows to SQL Statement
mysqli_stmt_bind_param($q, "ii", $start, $pagerows);
// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);

该查询从用户表中选择要显示的列(姓氏、名字、电子邮件、注册日期)。从表中检索的信息仅限于需要在当前页面上显示的记录。例如,在第一页上,\(start 将被设置为 0,\)pagerows 将被设置为 4。将只检索从 0 到 4 的行。检索到的记录也将按升序排序。尽管我们之前清理了\(start 和\)pagerows,但是为了更加安全,我们将使用一个准备好的语句。

一些程序员可能选择一次从数据库中检索所有记录,并将它们放入一个数组中。这是一种常见的做法。但是,因为我们必须在每次想要显示另一个页面时向服务器发送请求,所以我们可以直接从数据库中提取下一组记录。这对于较小的信息表很有效。

// Now display the total number of records/members.                                    #6
$q = "SELECT COUNT(userid) FROM users";
$result = mysqli_query ($dbcon, $q);
$row = mysqli_fetch_array ($result, MYSQLI_NUM);
$members = htmlspecialchars($row[0], ENT_QUOTES);
mysqli_close($dbcon); // Close the database connection.
$echostring = "<p class='text-center'>Total membership: $members </p>";
$echostring .= "<p class='text-center'>";

如前所述,这段代码计算表中的总行数。返回值将被放入$row[0]中。该值被清理并放入$members 中,它将显示在页面上。

if ($pages > 1) {   //                                                                 #7
       //What number is the current page?
       $current_page = ($start/$pagerows) + 1;
       //If the page is not the first page then create a Previous link
       if ($current_page != 1) {
               $echostring .= '<a href="admin_view_users.php?s=' .
                      ($start - $pagerows) .
                      '&p=' . $pages . '">Previous</a> ';
       }

创建名为 Previous 的链接,该链接调用 admin_view_users.php 程序。如前所述,s(起始位置)将包含要在页面上显示的第一条记录(如记录 0 或记录 4)。这是通过从上一个开始位置(\(start)减去一页中要显示的总行数(\)pagerow)来计算的。例如,如果当前开始位置是 20,我们从这个位置减去 4,新的开始位置(放在 s 中)现在是 16。\(pages 包含之前计算的所需页数。如果\)current_page 为 1,则不会显示上一个链接。

       //Create a Next link                                                           #8
       if ($current_page != $pages) {
               $echostring .=
       ' <a href="admin_view_users.php?s=' . ($start + $pagerows) .
                      '&p=' . $pages . '">Next</a> ';
       }
       $echostring .= '</p>';
echo $echostring;
}

下一个链接的创建方式与上一个链接类似。唯一不同的是计算方式。起始位置(\(start)被添加到要在页面上显示的行数(\)pagerow)中,以确定 s 的值(起始位置)。如果当前起始位置为零,并且页面上显示的行数为 4,则下一个起始位置为 4。单击该链接时,页面将显示记录 4–7。如果当前页面是最后一页($current_page == $pages),则不会显示下一个链接。完成的字符串现在显示在页面上。

启动 XAMPP 或 easyPHP,然后以管理员身份登录查看分页。调整$pagerows 的值,看看它如何改变页面的行为。

虽然这是一个很好的练习,但更常见的是我们只想搜索一条记录,而不是显示所有记录(尤其是当有数百条记录时)。让我们决定如何搜索一个特定的记录,然后显示它。

您可能已经注意到, admin_view_users.php 页面的标题已经更改,因此查看成员按钮是到 admin_view_users.php 页面的链接。此外,一个新的按钮链接到搜索页面【search.php 。清单 4-3b 展示了这些变化。

<div class="col-sm-2">
         <img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
        <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
<nav class="col-sm-2">
<div class="btn-group-vertical btn-group-sm" role="group"
        aria-label="Button Group">
        <button type="button" class="btn btn-secondary"
               onclick="location.href = 'logout.php'" >Logout</button>
        <button type="button" class="btn btn-secondary"
               onclick="location.href = 'admin_view_users.php'">View Members</button>
        <button type="button" class="btn btn-secondary"
               onclick="location.href = 'search.php'">Search</button>
        <button type="button" class="btn btn-secondary"
               onclick="location.href = 'register-password.php'">New Password</button>
</div>
</nav>

Listing 4-3bCreating the Revised Administration Header

(header-admin.php)

规划搜索标准

管理员需要一个搜索工具,不需要滚动浏览大量记录来编辑或删除一个记录。前瞻性规划的重要性怎么强调都不过分;决定如何组织搜索工具需要仔细考虑。

管理员可以编辑和删除记录。那么,什么活动需要他们使用这些设施呢?对于编辑,该事件通常是由成员请求管理员更改电子邮件地址或名称引起的。如果成员辞职或死亡,就会发生删除。

有些名字很常见,例如,我们有五个人叫詹姆斯·史密斯。在我们的例子中,我们需要显示这五个人,然后使用他们的电子邮件地址来区分他们,这样我们就不会删除错误的詹姆斯·史密斯。

注意

通常还会创建一个唯一的键来标识受影响的记录。在我们的例子中,电子邮件可以用来完成这一点。更常见的情况是,创建一个唯一的成员 ID。美国的一些组织使用社会安全号码。然而,这不是一个好的选择,因为这个数字与许多个人财务记录(如退休、医疗和支票账户)相关联。

下一个教程描述了显示搜索结果的页面的创建。为此,您需要注册四个名为詹姆斯·史密斯的成员。表 4-5 提供了一些建议。

表 4-5

一些相同的常用名

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- |
| 詹姆斯 | 锻工 | jimsmith@myisp.org.uk | Ch@vr0n1 |
| 詹姆斯 | 锻工 | James.smith@myisp.com | 包括 1n3d |
| 詹姆斯 | 锻工 | Jimmy.smith@myisp.co.uk | P@d@stal |
| 詹姆斯 | 锻工 | jims@myisp.net | 钨@n |

当你注册后,总共会有五个叫詹姆斯·史密斯的成员,我们数据库中的记录总数将是 22。

用于显示指定成员的临时搜索页面

我们最终将创建一个搜索工具,但首先我们必须创建一个页面,将产生一个名为詹姆斯·史密斯的成员表。搜索詹姆斯·史密斯的目标或最终结果将是一个表格,如图 4-5 所示。

img/314857_2_En_4_Fig5_HTML.jpg

图 4-5

詹姆斯·史密斯的五件作品已经找到,现在正在展出

为了确保我们能够显示如图 4-5 所示的表格,我们需要创建一个名为temp _ view _ found _ record . PHP的页面。因为我们还没有创建搜索页面,所以作为临时的权宜之计,我们将在代码中将姓名詹姆斯史密斯直接输入到 SQL 查询中。PHP 代码在文件process _ temp _ view _ found _ record . PHP中,如清单 4-4 所示。

注意

这些是页面的临时版本。在制作最终版本之前,这是一个有用的阶段。它展示了一个重要的原则,并且是证明将显示一个表的一个有用的练习。本章的可下载文件中包含的临时版本为temp _ view _ found _ record . PHPprocess _ temp _ view _ found _ record . PHP

<?php
try
{

        // This script retrieves records from the users table.                      #1
        require ('mysqli_connect.php'); // Connect to the db.
        echo '<p class="text-center">If no record is shown, ';
        echo 'this is because you had an incorrect ';
        echo ' or missing entry in the search form.';
        echo '<br>Click the back button on the browser and try again</p>';
        $query = "SELECT last_name, first_name, email, ";
        $query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
        $query .=" AS regdat, userid FROM users WHERE ";
        $query .= "last_name='Smith' AND first_name="James" ";
        $query .="ORDER BY registration_date ASC ";
        // Prepared statement not needed because string is hard coded
        $result = mysqli_query ($dbcon, $query); // Run the query.
        if ($result) { // If it ran, display the records.
               // Table header.
               echo '<table class="table table-striped">
               <tr>
               <th scope="col">Edit</th>
               <th scope="col">Delete</th>
               <th scope="col">Last Name</th>
               <th scope="col">First Name</th>
               <th scope="col">Email</th>
               <th scope="col">Date Registered</th>
               </tr>';
               // Fetch and display the records:
               while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
               // Remove special characters that might already be in table to
                       // reduce the chance of XSS exploits
                       $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
                       $last_name =
                              htmlspecialchars($row['last_name'], ENT_QUOTES);
                       $first_name =
                              htmlspecialchars($row['first_name'], ENT_QUOTES);
                       $email = htmlspecialchars($row['email'], ENT_QUOTES);
                       $registration_date =
                              htmlspecialchars($row['regdat'], ENT_QUOTES);
                       echo '<tr>
                              <td><a href="edit_user.php?id=' . $user_id .
                               '">Edit</a></td>
                              <td><a href="delete_user.php?id=' . $user_id .
                               '">Delete</a></td>
                              <td>' . $last_name . '</td>
                              <td>' . $first_name . '</td>
                              <td>' . $email . '</td>
                              <td>' . $registration_date . '</td>
                              </tr>';
               }
               echo '</table>'; // Close the table.
               mysqli_free_result ($result); // Free up the resources.
        } else { // If it did not run OK.
               // Public message:
        echo '<p class="error">The current users could not be retrieved.';
               echo 'We apologize for any inconvenience.</p>';
               // Debugging message:
        //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
               //Show $q is debug mode only
        }
// End of if ($result). Now display the total number of records/members.
        mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e)
{
        print "The system is currently busy. Please try later.";
        //print "An Exception occurred.Message: " . $e->getMessage();
}
catch(Error $e)
{
        print "The system us busy. Please try later.";
        //print "An Error occurred. Message: " . $e->getMessage();
}
?>

Listing 4-4Creating a Temporary Page for Displaying the Results of a Search (process_temp_view_found_record.php)

代码的解释

本节解释代码。

// This script retrieves records from the users table.                                 #1
require ('mysqli_connect.php'); // Connect to the db.
echo '<p class="text-center">If no record is shown, ';
echo 'this is because you had an incorrect ';
echo ' or missing entry in the search form.';
echo '<br>Click the back button on the browser and try again</p>';
$query = "SELECT last_name, first_name, email, ";
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
$query .=" AS regdat, userid FROM users WHERE ";
$query .= "last_name='Smith' AND first_name="James" ";
$query .="ORDER BY registration_date ASC ";

这个 select 语句将从 users 表中提取所有詹姆斯·史密斯记录,并按照注册日期将它们按升序排列。

这个 SQL 查询与process _ admin _ view _ users . PHP中的查询几乎相同,只是取消了对行数的限制,并引入了一个新的关键字 WHERE。取消对行数的限制可以确保无论有多少条詹姆斯·史密斯记录都会显示出来。WHERE 子句确切指定要显示哪些记录。我们输入名称是一种临时的权宜之计,这样我们就可以测试页面并确保它按预期工作。这是一种常见的技术,被称为硬编码。结果表格显示如图 4-5 所示。

请记住,这是搜索页面的临时版本。在常规搜索中,我们不会在 select 语句中编码实际的名字和姓氏。

要查看此表,请启动您的服务器并以管理员身份登录。在浏览器的地址栏中输入http://localhost/admin table/temp _ view _ found _ record . PHP

注意

现在我们正在使用会话,如果不作为管理员登录,您将无法访问任何管理页面。

我们现在有了一个页面,它将显示指定记录的表格。工作正常后,下一步是提供一个搜索表单,以便管理员可以请求一个显示任何指定成员而不仅仅是詹姆斯·史密斯的表。

这个例子很适合测试,但是不太实用。让我们调整我们的逻辑,使它更有用。

搜索表单

当管理员点击标题菜单上的搜索按钮时,将出现search.php页面。该页面有一个包含两个字段的表单,可以在其中输入搜索条件。需要计划来决定搜索标准应该是什么。虽然电子邮件地址将为管理员提供用于查找成员的唯一标识符,但它可能是未知的。成员的注册日期不是查找成员的好选择,因为成员不可避免地会忘记他们注册的确切日期。管理员需要知道一些独特但显而易见的信息来显示成员的记录。显而易见的标准是成员的名和姓;因此,这就是我们将使用的。名字和姓氏可能不是唯一的,但正如我们对詹姆斯·史密斯所做的那样,我们可以显示所有名字和姓氏相同的用户,然后决定哪个是要修改的正确成员信息。

注意:提供多种方式来搜索成员记录是有益的。您可以改进这个示例,允许管理员根据电子邮件地址或者名字和姓氏进行搜索。通过提供这两者,您只允许在已知电子邮件的情况下获取唯一记录。否则,可以在姓氏和名字相同的所有成员中搜索正确的记录。

图 4-6 显示了搜索表单。

img/314857_2_En_4_Fig6_HTML.jpg

图 4-6

搜索表单

搜索表单使管理员能够搜索特定记录。如果搜索成功,表单将调用 view_found_record.php 页面。该页面将显示用户(或多个用户)。如果几个人有相同的名字和姓氏,表格将显示所有人。

暂时不要尝试输入搜索。我们将需要调整我们的视图发现页面,目前总是显示詹姆斯·史密斯的记录。这个临时页面将被更改,以接受清单 4-5a 中描述的搜索表单中的值。

<?php
         session_start();                                                             // #1
         if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
         {
                 header("Location: login.php");
                 exit();
         }
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Search Page</title>
<meta charset="utf-8">
<meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no">
 <!-- Bootstrap CSS File -->
 <link rel="stylesheet"
         href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
         integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
        style=
"margin-bottom:2px; background:linear-gradient(white, #0073e6);padding:20px;">
        <?php include('header-admin.php'); ?>
</header>
<!-- Body Section -->
 <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
<nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('nav.php'); ?>
      </ul>
 </nav>
<div class="col-sm-8">
<h2 class="h2 text-center">Search for a record</h2>
<h6 class="text-center">Both names are required items</h6>
<form action="view_found_record.php" method="post">
<div class="form-group row">                                                    <!-- #2 -->
   <label for="first_name" class="col-sm-4 col-form-label">First Name:</label>
   <div class="col-sm-8">
   <input type="text" class="form-control" id="first_name" name="first_name"
          placeholder="First Name" maxlength="30" required
          value=
                "<?php if (isset($_POST['first_name']))
                echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
  <label for="last_name" class="col-sm-4 col-form-label">Last Name:</label>
  <div class="col-sm-8">
  <input type="text" class="form-control" id="last_name" name="last_name"
          placeholder="Last Name" maxlength="40" required
          value=
                "<?php if (isset($_POST['last_name']))
                echo htmlspecialchars($_POST['last_name'], ENT_QUOTES); ?>">
 </div>
 </div>
<div class="form-group row">
<label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
        <input id="submit" class="btn btn-primary" type="submit"
                name="submit" value="Search">
</div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
<?php
 if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
               style="padding-bottom:1px; padding-top:8px;">';
 }
else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('footer.php');
 ?>
</footer>
</div>
</body>
</html>

Listing 4-5aCreating the Search Form (search.php)

代码的解释

本节解释代码。

<?php
        session_start();                                                               // #1
        if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
        {
               header("Location: login.php");
               exit();
        }
?>

我们所有的管理 HTML 页面都包含这段代码。会话是我们的安全卫士;未经授权的人(任何不是管理员的人)将被重定向到登录页面。

<div class="form-group row">                                                     <!-- #2 -->
   <label for="first_name" class="col-sm-4 col-form-label">First Name:</label>
   <div class="col-sm-8">
   <input type="text" class="form-control" id="first_name" name="first_name"
          placeholder="First Name" maxlength="30" required
          value=
                "<?php if (isset($_POST['first_name']))
                echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >
  </div>
  </div>

代码提供了一个粘性表单。粘性表单是在标记错误时保留用户输入的表单。如果用户每次需要纠正错误(如没有填写某个字段)时都必须填写整个表单,他们会感到很恼火。粘性组件是值后面的 PHP 代码,例如:

  value=
        "<?php if (isset($_POST['first_name']))
        echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >

该表单将使用 POST 提交所有表单数据。我们希望表单数据不会被外力修改。然而,这些数据有可能被修改以攻击我们的系统、数据库或网络。因此,我们使用 htmlspecialchars 函数来整理数据。如上所述,所有可能包含在可执行代码中的符号现在都被转义("--> \ ")。这确保了信息是不可执行的。它不会清除数据,但至少不太可能有害。

我们稍后将添加一些人类检测能力,这将降低程序使用我们的表单的能力。

我们现在将使表单处理程序temp _ view _ found _ record . PHP更加通用。

接收搜索表单输入的最终表单处理程序

下面的代码(process _view _ found _ record . php)展示了 PHP 代码中添加或修改的内容。这些项目使文件能够接收从【search.php】页面发送的名字和姓氏。名和姓被分配给变量,这些变量用于搜索数据库,以便可以对它们进行验证。这些变量随后用于显示记录。

清单 4-5b 显示了消除硬编码并允许搜索任何注册成员记录的代码。

<?php
try
{
    // This script retrieves records from the users table.
    require ('./mysqli_connect.php'); // Connect to the db.
    echo '<p class="text-center">If no record is shown, ';
    echo 'this is because you had an incorrect ';
    echo ' or missing entry in the search form.';
    echo '<br>Click the back button on the browser and try again</p>';
    //                                                                           #1
    $first_name = htmlspecialchars($_POST['first_name'], ENT_QUOTES);
    $last_name = htmlspecialchars($_POST['last_name'], ENT_QUOTES);
    // Since it's a prepared statement below this sanitizing is not needed
    // However, to consistently retrieve than sanitize is a good habit
    //                                                                           #2
    $query = "SELECT last_name, first_name, email, ";
    $query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
    $query .=" AS regdat, userid FROM users WHERE ";
    $query .= "last_name=? AND first_name=? ";
    $query .="ORDER BY registration_date ASC ";
    $q = mysqli_stmt_init($dbcon);
    mysqli_stmt_prepare($q, $query);

    // bind values to SQL Statement
    mysqli_stmt_bind_param($q, 'ss', $last_name, $first_name);

    // execute query
    mysqli_stmt_execute($q);

    $result = mysqli_stmt_get_result($q);

    if ($result) { // If it ran, display the records.
    // Table header.
             echo '<table class="table table-striped">
             <tr>
             <th scope="col">Edit</th>
             <th scope="col">Delete</th>
             <th scope="col">Last Name</th>
             <th scope="col">First Name</th>
             <th scope="col">Email</th>
             <th scope="col">Date Registered</th>
             </tr>';
             // Fetch and display the records:
             while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
             // Remove special characters that might already be in table to
                       // reduce the chance of XSS exploits
                       $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
                       $last_name =
                               htmlspecialchars($row['last_name'], ENT_QUOTES);
                       $first_name =
                               htmlspecialchars($row['first_name'], ENT_QUOTES);
                       $email =
                               htmlspecialchars($row['email'], ENT_QUOTES);
                       $registration_date =
                               htmlspecialchars($row['regdat'], ENT_QUOTES);
                       echo '<tr>
                       <td><a href="edit_user.php?id=' . $user_id .
                       '">Edit</a></td>
                       <td><a href="delete_user.php?id=' . $user_id .
                               '">Delete</a></td>
                       <td>' . $last_name . '</td>
                       <td>' . $first_name . '</td>
                       <td>' . $email . '</td>
                       <td>' . $registration_date . '</td>
                       </tr>';
               }
               echo '</table>'; // Close the table.
               //
               mysqli_free_result ($result); // Free up the resources.
   } else { // If it did not run OK.
               // Public message:
echo '<p class="text-center">The current users could not be retrieved.';
               echo 'We apologize for any inconvenience.</p>';
               // Debugging message:
       //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
               //Show $q is debug mode only
    }
// End of if ($result). Now display the total number of records/members.
          mysqli_close($dbcon); // Close the database connection.
    }
    catch(Exception $e)
    {
        print "The system is currently busy. Please try later.";
        //print "An Exception occurred. Message: " . $e->getMessage();
    }
    catch(Error $e)
    {
        print "The system us busy. Please try later.";
        //print "An Error occurred. Message: " . $e->getMessage();
    }
?>

Listing 4-5bThe Code for Displaying the Search Results (process_view_found_record.php)

代码的解释

本节解释代码。

//                                                                            #1
$first_name = htmlspecialchars($_POST['first_name'], ENT_QUOTES);
$last_name = htmlspecialchars($_POST['last_name'], ENT_QUOTES);
// Since it's a prepared statement below this sanitizing is not needed
// However, to consistently retrieve than sanitize is a good habit

我们应该始终保持对从当前程序之外接收的数据进行净化的流程。可以删除前面的两个语句,但数据仍然不会执行,因为语句已经准备好了。但是如果我们总是记住“输入然后净化”,我们正在创建一个更安全的程序。

//                                                                            #2
$query = "SELECT last_name, first_name, email, ";
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
$query .=" AS regdat, userid FROM users WHERE ";
$query .= "last_name=? AND first_name=? ";
$query .="ORDER BY registration_date ASC ";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);

// bind values to SQL Statement
mysqli_stmt_bind_param($q, 'ss', $last_name, $first_name);

// execute query
mysqli_stmt_execute($q);

$result = mysqli_stmt_get_result($q);
if ($result) { // If it ran, display the records.

该查询已被调整为接受姓氏和名字来搜索匹配记录。由于该信息是从用户处接收的,因此已经创建了一个准备好的语句。使用这种格式,用户输入的任何值都无法执行。用户不能试图使用 SQL 注入访问不应该访问的信息。

SQL 语句包括两个问号。第一个问号绑定到\(last_name 中的值。第二个问号绑定到\)first_name。该语句要求每个都是一个字符串(ss)。然后执行查询,结果放在$result 中。

最后,您可以测试搜索工具。运行 XAMPP 或 easyPHP,并在浏览器的地址栏中键入以下内容:

http://localhost/admin table/log in . PHP

使用管理员的电子邮件地址和密码登录。电子邮件是 jsmith@outlook.com,密码是 d0gsb0dy。然后单击标题菜单上的搜索按钮。在搜索表单中,尝试输入杰克·史密斯来查看显示的单个记录。

结果应该是如图 4-7 所示的记录。

img/314857_2_En_4_Fig7_HTML.jpg

图 4-7

显示使用搜索页面选择的单个记录

尝试输入詹姆斯·史密斯来查看一组具有相同姓氏和名字的记录。

现在我们已经到了可以演示管理员如何删除和编辑记录的阶段。

编辑记录

当管理员单击显示的记录上的编辑链接时,将显示 edit_user.php 页面。该页面显示数据库表中当前保存的字段和数据。如图 4-8 所示。

img/314857_2_En_4_Fig8_HTML.jpg

图 4-8

显示现有数据的编辑用户屏幕

管理员可以修改详细信息,然后单击“编辑”按钮完成编辑过程。

清单 4-6 显示了 process_edit_record.php 的 PHP 代码。 edit_user.php 文件包含与前面所示相同的 HTML 格式。您可以从下载的文件中查看这段代码。

<?php
    try
    {
         // After clicking the Edit link in the found_record.php page. // This code is executed                                                      #1
         // The code looks for a valid user ID, either through GET or POST:
         if ( (isset($_GET['id'])) && (is_numeric($_GET['id'])) ) {
         // From view_users.php
                  $id = htmlspecialchars($_GET['id'], ENT_QUOTES);
         } elseif ( (isset($_POST['id'])) && (is_numeric($_POST['id'])) ) {
         // Form submission.
                 $id = htmlspecialchars($_POST['id'], ENT_QUOTES);
         } else { // No valid ID, kill the script.
      echo '<p class="text-center">This page has been accessed in error.</p>';
                 include ('footer.php');
                 exit();
         }
     require ('./mysqli_connect.php');
     // Has the form been submitted?
     if ($_SERVER['REQUEST_METHOD'] == 'POST') {
           $errors = array();
           // Look for the first name:                                                  #2
           $first_name =
                    filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
           if (empty($first_name)) {
                    $errors[] = 'You forgot to enter your first name.';
           }
           // Look for the last name:
           $last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
           if (empty($last_name)) {
                     $errors[] = 'You forgot to enter your last name.';
           }
           // Look for the email address:
           $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
           if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
                     $errors[] = 'You forgot to enter your email address';
                     $errors[] = ' or the e-mail format is incorrect.';
           }
           if (empty($errors)) { // If everything's OK.                                 #3
                   $q = mysqli_stmt_init($dbcon);
                   $query = 'SELECT userid FROM users WHERE email=? AND userid !=?';
                   mysqli_stmt_prepare($q, $query);
                   // bind $id to SQL Statement
                   mysqli_stmt_bind_param($q, 'si', $email, $id);
                   // execute query
                   mysqli_stmt_execute($q);
                   $result = mysqli_stmt_get_result($q);

                   if (mysqli_num_rows($result) == 0) {
                   // e-mail does not exist in another record                           #4
                   $query = 'UPDATE users SET first_name=?, last_name=?, email=?';
                           $query .= ' WHERE userid=? LIMIT 1';
                           $q = mysqli_stmt_init($dbcon);
                           mysqli_stmt_prepare($q, $query);
                           // bind values to SQL Statement
mysqli_stmt_bind_param($q, 'sssi', $first_name, $last_name, $email, $id);
                           // execute query
                           mysqli_stmt_execute($q);
                           if (mysqli_stmt_affected_rows($q) == 1) { // Update OK
                                     // Echo a message if the edit was satisfactory:
                   echo '<h3 class="text-center">The user has been edited.</h3>';
                           } else { // Echo a message if the query failed.
           echo '<p class="text-center">The user could not be edited due ';
           echo 'to a system error.';
           echo ' We apologize for any inconvenience.</p>'; // Public message.
           //echo '<p>' . mysqli_error($dbcon) . '<br />Query: ' . $q . '</p>';
           // Debugging message.
           // Message above is only for debug and should not display SQL in live //mode
                           }
                  } else { // Already registered.
                  echo '<p class="text-center">The email address has ';
                  echo 'already been registered.</p>';
                  }
           } else { // Display the errors.
           echo '<p class="text-center">The following error(s) occurred:<br />';
                    foreach ($errors as $msg) { // Echo each error.
                             echo " - $msg<br />\n";
                  }
                  echo '</p><p>Please try again.</p>';
           } // End of if (empty($errors))section.
      } // End of the conditionals
      // Select the user's information to display in textboxes:                         #5
      $q = mysqli_stmt_init($dbcon);
      $query = "SELECT first_name, last_name, email FROM users WHERE userid=?";
      mysqli_stmt_prepare($q, $query);
      // bind $id to SQL Statement
      mysqli_stmt_bind_param($q, 'i', $id);
      // execute query
      mysqli_stmt_execute($q);
      $result = mysqli_stmt_get_result($q);
      $row = mysqli_fetch_array($result, MYSQLI_NUM);

      if (mysqli_num_rows($result) == 1) { // Valid user ID, display the form.
           // Get the user's information:
           // Create the form:
?>
<h2 class="h2 text-center">Edit Record</h2>
<form action="edit_record.php" method="post"
        name="editform" id="editform">
<div class="form-group row">
        <label for="first_name" class="col-sm-4 col-form-label">
                 First Name:</label>
<div class="col-sm-8">
     <input type="text" class="form-control" id="first_name" name="first_name"
         placeholder="First Name" maxlength="30" required
         value="<?php echo htmlspecialchars($row[0], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="last_name" class="col-sm-4 col-form-label">
                   Last Name:</label>
<div class="col-sm-8">
     <input type="text" class="form-control" id="last_name" name="last_name"
         placeholder="Last Name" maxlength="40" required
         value="<?php echo htmlspecialchars($row[1], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row">
         <label for="email" class="col-sm-4 col-form-label">E-mail:</label>
<div class="col-sm-8">
    <input type="email" class="form-control" id="email" name="email"
         placeholder="E-mail" maxlength="60" required
         value="<?php echo htmlspecialchars($row[2], ENT_QUOTES); ?>">
</div>
</div>
    <input type="hidden" name="id" value=" <?php echo $id  ?>" />               <!-- #6 -->
<div class="form-group row">
          <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
          <input id="submit" class="btn btn-primary" type="submit" name="submit" value="Register">
</div>
</div>
</form>
<?php
    } else { // The user could not be validated
echo '<p class="text-center">This page has been accessed in error.</p>';
    }
    mysqli_stmt_free_result($q);
    mysqli_close($dbcon);
    }
    catch(Exception $e)
    {
      print "The system is busy. Please try later";
        //print "An Exception occurred. Message: " . $e->getMessage();
    }
    catch(Error $e)
    {
        print "The system is currently buys. Please try later";
        //print "An Error occurred. Message: " . $e->getMessage();
    }
?>

Listing 4-6Creating the Editing Interface

(process_edit_record.php)

代码的解释

大部分代码已经被解释了,但是我们现在将检查一些本章中新的代码。

// After clicking the Edit link in the found_record.php page.
//This code is executed                                                            #1
// The code looks for a valid user ID, either through GET or POST:

if ( (isset($_GET['id'])) && (is_numeric($_GET['id'])) ) {
// From view_users.php

         $id = htmlspecialchars($_GET['id'], ENT_QUOTES);

} elseif ( (isset($_POST['id'])) && (is_numeric($_POST['id'])) ) {
// Form submission.

         $id = htmlspecialchars($_POST['id'], ENT_QUOTES);

} else { // No valid ID, kill the script.

echo '<p class="text-center">This page has been accessed in error.</p>';
         include ('footer.php');
         exit();

}

我们必须确保我们正在编辑正确的记录,这是通过检查所选用户的 ID 来实现的。如果用户的详细信息输入不正确,或者数据库表中不存在该用户,则会显示一条错误消息。请注意 is_numeric()函数,它确保 ID 是一个数字而不是一个字符串。如果 ID 是数字,它将被清理并赋给变量$id,为下一步做好准备。消毒是不需要的,因为我们将使用一个准备好的声明。然而,我们希望保持一致并净化所有输入。

注意:收到的号码可能仍然无效;例如,有人可能试图跳过搜索表单,向程序传递一个数字。但是,一旦搜索完成,如果没有找到该号码,将显示一条错误消息。

// Look for the first name:                                                            #2
    $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
    if (empty($first_name)) {
              $errors[] = 'You forgot to enter your first name.';
    }

表单中的每个条目都经过了清理。如果用户没有为名字输入值,错误消息将存储在错误数组中。这与我们在注册程序中使用的代码相同。

if (empty($errors)) { // If everything's OK.                                            #3
             $q = mysqli_stmt_init($dbcon);
             $query = 'SELECT userid FROM users WHERE email=? AND userid !=?';
             mysqli_stmt_prepare($q, $query);
             // bind $id to SQL Statement
             mysqli_stmt_bind_param($q, 'si', $email, $id);
             // execute query
              mysqli_stmt_execute($q);
             $result = mysqli_stmt_get_result($q);

             if (mysqli_num_rows($result) == 0) {
// e-mail does not exist in another record

如果没有验证错误,请确定新电子邮件是否在数据库中,以及是否与不同的用户 ID 相关。如果它与不同的 ID 无关,我们就可以更新记录。

// e-mail does not exist in another record                                             #4
       $query = 'UPDATE users SET first_name=?, last_name=?, email=?';
                $query .= ' WHERE userid=? LIMIT 1';

                $q = mysqli_stmt_init($dbcon);
                mysqli_stmt_prepare($q, $query);

                // bind values to SQL Statement
mysqli_stmt_bind_param($q, 'sssi', $first_name, $last_name, $email, $id);

                // execute query
                mysqli_stmt_execute($q);

                if (mysqli_stmt_affected_rows($q) == 1) { // Update OK
                // Echo a message if the edit was satisfactory:
                echo '<h3 class="text-center">The user has been edited.</h3>';

如果错误数组不包含错误消息,并且电子邮件与另一条记录没有关联,则查询将运行。使用关键字 UPDATE,该查询更新 users 表中具有正确用户 ID 的记录。将关键字 LIMIT 与 1 一起使用可以确保只更新一条记录。成功完成后,会向用户显示一条消息。

    // Select the user's information to display in textboxes:                          #5
    $q = mysqli_stmt_init($dbcon);

    $query = "SELECT first_name, last_name, email FROM users WHERE userid=?";

    mysqli_stmt_prepare($q, $query);

    // bind $id to SQL Statement
    mysqli_stmt_bind_param($q, 'i', $id);

    // execute query
    mysqli_stmt_execute($q);
    $result = mysqli_stmt_get_result($q);
    $row = mysqli_fetch_array($result, MYSQLI_NUM);

    if (mysqli_num_rows($result) == 1) { // Valid user ID, display the form.
         // Get the user's information:
         // Create the form:
?>
<h2 class="h2 text-center">Edit Record</h2>
<form action="edit_record.php" method="post"
        name="editform" id="editform">
<div class="form-group row">
        <label for="first_name" class="col-sm-4 col-form-label">First Name:</label>
<div class="col-sm-8">
     <input type="text" class="form-control" id="first_name" name="first_name"
         placeholder="First Name" maxlength="30" required
         value="<?php echo htmlspecialchars($row[0], ENT_QUOTES); ?>" >
</div>
</div>

admin_view_users.php 页面传递的用户 ID 用于从 users 表中提取用户信息以显示在表单中。将显示包含所选用户数据的表单。然后,用户可以编辑表单上显示的任何信息,并单击 submit 按钮将更改返回到数据库的表中。

<input type="hidden" name="id" value=" <?php echo $id  ?>" />                <!-- #6 -->

隐藏的输入值(id)确保表单中不显示用户 ID 的字段,除非已经从 admin_view_users.php 页面(通过 GET)或从 edit_user.php (通过 POST)传递了 ID。

删除记录

如果管理员删除了错误的记录,则删除无法撤消;因此,程序将向管理员确认是否应该删除该记录。当管理员在显示的记录上单击删除链接时,会显示 delete_user.php 页面。删除记录屏幕显示管理员要删除的人员的姓名。然后管理员可以点击是按钮或否按钮,如图 4-9 所示。

img/314857_2_En_4_Fig9_HTML.jpg

图 4-9

删除记录的屏幕

该页面显示要删除的成员的名称,并让管理员有机会验证是否删除了正确的记录。在我们描述删除页面的列表之前,表 4-6 中建议了几个名字。注册它们,以便您可以尝试删除它们。

表 4-6

有些名字你可以注册,然后尝试删除

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- |
| 菲利斯 | 齿 | ptine@myisp.co.uk | vug @ R1 @ n |
| 是吗 | 斜面 | dcant@myisp.com | P0lyph0n1c# |
| 账单 | 板 | bboard@myisp.net | -= ytet-伊甸园字幕组=-翻译:粒粒粒尘紫月猫姐 scenery 校对:阿衡时间轴:邦德猪 |
| 伊娃 | 腾讯 | enescent @ myisp.de | Fl@@t1ng |

在下一步中,我们将为管理员创建一个页面,以便可以安全地删除记录。HTML 代码类似于前面的代码。可以从下载的文件中查看。清单 4-7 显示了 PHP 代码。

<?php
try {
// Check for a valid user ID, through GET or POST:                                #1
if ( (isset($_GET['id'])) && (is_numeric($_GET['id'])) ) {
// From view_users.php
     $id = htmlspecialchars($_GET['id'], ENT_QUOTES);
} else
     if ( (isset($_POST['id'])) && (is_numeric($_POST['id'])) ) {
     // Form submission.
             $id = htmlspecialchars($_POST['id'], ENT_QUOTES);
     } else { // No valid ID, kill the script.
              // return to login page
              header("Location: login.php");
              exit();
              }
     require ('./mysqli_connect.php');
     // Check if the form has been submitted:                                           #2
     if ($_SERVER['REQUEST_METHOD'] == 'POST') {
              $sure = htmlspecialchars($_POST['sure'], ENT_QUOTES);
              if ($sure == 'Yes') { // Delete the record.
              // Make the query:
              // Use prepare statement to remove security problems
              $q = mysqli_stmt_init($dbcon);
     mysqli_stmt_prepare($q, 'DELETE FROM users WHERE userid=? LIMIT 1');

              // bind $id to SQL Statement
              mysqli_stmt_bind_param($q, "s", $id);

              // execute query
              mysqli_stmt_execute($q);

              if (mysqli_stmt_affected_rows($q) == 1) { // It ran OK
              // Print a message:
              echo '<h3 class="text-center">The record has been deleted.</h3>';
              } else { // If the query did not run OK display public message

              echo '<p class="text-center">The record could not be deleted.';
       echo '<br>Either it does not exist or due to a system error.</p>';
       // echo '<p>' . mysqli_error($dbcon ) . '<br />Query: ' . $q . '</p>';
       // Debugging message. When live comment out because this displays SQL

              }
       } else { // User did not confirm deletion.
     echo '<h3 class="text-center">The user has NOT been deleted as ';
     echo 'you requested</h3>';
       }
  } else { // Show the form.                                                            #3
       $q = mysqli_stmt_init($dbcon);
       $query = "SELECT CONCAT(first_name, ' ', last_name) FROM ";
       $query .= "users WHERE userid=?";
       mysqli_stmt_prepare($q, $query);

       // bind $id to SQL Statement
       mysqli_stmt_bind_param($q, "s", $id);

       // execute query
       mysqli_stmt_execute($q);

       $result = mysqli_stmt_get_result($q);

       $row = mysqli_fetch_array($result, MYSQLI_NUM); // get user info

       if (mysqli_num_rows($result) == 1) {
       // Valid user ID, display the form.

       // Display the record being deleted:                                             #4
                $user = htmlspecialchars($row[0], ENT_QUOTES);
?>
<h2 class="h2 text-center">
Are you sure you want to permanently delete

<?php echo $user; ?>?</h2>
<form action="delete_user.php" method="post"
        name="deleteform" id="deleteform">
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8" style="padding-left: 70px;">
        <input type="hidden" name="id" value="<?php echo $id; ?>">
        <input id="submit-yes" class="btn btn-primary" type="submit" name="sure" value="Yes"> -
        <input id="submit-no" class="btn btn-primary" type="submit" name="sure" value="No">
</div>
</div>
</form>
<?php
    } else { // Not a valid user ID.
    echo '<p class="text-center">This page has been accessed in error.</p>';
       }
    } // End of the main submission conditional.
         mysqli_stmt_close($q);
         mysqli_close($dbcon );
    }
    catch(Exception $e)
    {
        print "The system is busy. Please try again.";
        //print "An Exception occurred. Message: " . $e->getMessage();
    }
    catch(Error $e)
    {
        print "The system is currently busy. Please try again soon.";
        //print "An Error occurred. Message: " . $e->getMessage();
    }
?>

Listing 4-7Creating a Page

for Deleting Records (process_delete_record.php)

代码的解释

本节解释代码。

// Check for a valid user ID, through GET or POST:                                    #1
if ( (isset($_GET['id'])) && (is_numeric($_GET['id'])) ) {
// From view_users.php

    $id = htmlspecialchars($_GET['id'], ENT_QUOTES);

} else
    if ( (isset($_POST['id'])) && (is_numeric($_POST['id'])) ) {
    // Form submission.

            $id = htmlspecialchars($_POST['id'], ENT_QUOTES);

    } else { // No valid ID, kill the script.
             // return to login page

             header("Location: login.php");
             exit();

             }

如果 ID 没有通过,就不要继续;转到登录页面。

// Check if the form has been submitted:                                              #2
if ($_SERVER['REQUEST_METHOD'] == 'POST') {

         $sure = htmlspecialchars($_POST['sure'], ENT_QUOTES);

         if ($sure == 'Yes') { // Delete the record.
         // Make the query:
         // Use prepare statement to remove security problems

         $q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, 'DELETE FROM users WHERE userid=? LIMIT 1');

// bind $id to SQL Statement
mysqli_stmt_bind_param($q, "s", $id);

// execute query
         mysqli_stmt_execute($q);

         if (mysqli_stmt_affected_rows($q) == 1) { // It ran OK
         // Print a message:

         echo '<h3 class="text-center">The record has been deleted.</h3>';

进行检查以查看是否已经点击了“是”按钮。如果有,则运行删除记录的 SQL 代码;如果成功,将显示一条消息。

} else { // Show the form.                                                             #3
        $q = mysqli_stmt_init($dbcon);
        $query =
"SELECT CONCAT(first_name, ' ', last_name) FROM users WHERE userid=?";

    mysqli_stmt_prepare($q, $query);

    // bind $id to SQL Statement
    mysqli_stmt_bind_param($q, "s", $id);

    // execute query
    mysqli_stmt_execute($q);

    $result = mysqli_stmt_get_result($q);

    $row = mysqli_fetch_array($result, MYSQLI_NUM); // get user info

    if (mysqli_num_rows($result) == 1) {
    // Valid user ID, display the form.

此代码格式化要显示的选定名称:

// Display the record being deleted:                                                   #4
           $user = htmlspecialchars($row[0], ENT_QUOTES);
?>
<h2 class="h2 text-center">
Are you sure you want to permanently delete <?php echo $user; ?>?</h2>

<form action="delete_user.php" method="post"
        name="deleteform" id="deleteform">
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8" style="padding-left: 70px;">

        <input type="hidden" name="id" value="<?php echo $id; ?>">

        <input id="submit-yes" class="btn btn-primary" type="submit" name="sure" value="Yes">

        <input id="submit-no" class="btn btn-primary" type="submit" name="sure" value="No">
</div>
</div>
</form>';

将显示用户名以及“是”和“否”按钮。单击其中一个按钮的结果是将值 Yes 或 No 发送到前面编号为#2 的代码块。

注意

为了节省空间,只描述了需要修改的文件。本章的许多 PHP 文件没有被提及,因为它们与第三章中的文件相同。如果你还没有这样做,请从下载第四章的完整文件。com 网站。使用 XAMPP 或 easyPHP 完成本章的例子,理解显示、编辑和删除成员的逻辑。

摘要

在本章中,您了解了如何创建一种用户友好的方法,允许管理员修改数据库的内容。这包括对整个成员表进行分页的能力。描述了一个搜索表单,该表单可用于选择特定成员,以便修改或删除他们的记录。提供了允许管理员编辑和删除记录的代码。通过净化所有输入(无论来源如何)以确保值不包含可执行代码,安全性得到了提高。我们将在接下来的几章中继续添加更多的安全特性。

在下一章中,我们将探索一个具有一些额外特性的数据库。例如,我们将包括用户的头衔和用户的邮政地址和电话号码。还将引入额外的验证和安全设备。

五、扩展和丰富你的网站

本章中的教程假设网站用户正在申请一个需要会费的组织的会员资格,例如,一个政党或一个保护协会。

这些组织的网站数据库需要其成员的地址,可能还需要他们的电话号码。这些表单还可以使用下拉菜单来允许用户选择成员资格类别。

您可能已经亲身经历过,注册表单包含许多字段;有些是必需的,有些是可选的。本章处理的注册表单比前几章大,有两个字段是可选的。

完成本章后,您将能够

  • 创建一个包含两个表的数据库

  • 理解文档的重要性

  • 创建一个带有下拉菜单的扩展注册表

  • 添加 PayPal 和借记卡/信用卡信息

  • 创建可变定价显示

  • 对记录的显示应用分页

  • 创建编辑记录的代码

第一步是创建一个新的数据库。

创建一个新的数据库、一个有 15 列的表和一个价格表

按照以下步骤创建一个新数据库和两个表:

  1. 打开 XAMPP htdocs 或 easyPHP eds-www 文件夹,新建一个名为邮政的文件夹。

  2. 从本书的网站 www.apress.com 下载第五章的文件,并将文件放在新的邮政文件夹下的 htdocseds-www 中。如果你想手工编码你的文件,从章节 4 中复制文件并粘贴到你的新邮政文件夹中。然后修改代码以匹配本章中列出的修订后的代码。还要将 mysqli_connect.php 中的数据库改为 postaldb。

    注意

    如前几章所述,如果从电子书中复制代码,将代码粘贴到基本编辑器(如记事本)中,以确保所有引用都是标准引用。保存代码,然后在 PHP 编辑器(如 Notepad++)中重新打开代码。

  3. 使用浏览器访问 phpMyAdmin,单击 Databases 选项卡,创建一个名为 postaldb 的新数据库。从编码选项中选择 utf8-通用-ci。

  4. 在下一个屏幕中,创建一个名为 users 的表,输入 15 作为列数,然后单击 Go(在 phpMyAdmin 的某些版本中单击 Submit)。输入表 5-1 所示的信息。创建了 users 表之后,单击左列中数据库名称 postaldb 下面的新列表。这将显示新表格页面。创建另一个名为 prices 的表,输入 10 作为列数,然后单击 Go。输入表 5-2 中显示的信息。同样输入表 5-2 中所示的数值。

  5. 单击检查权限,然后向下滚动并单击添加用户帐户。现在添加一个新的用户和密码,如下所示:

    • 用户名:詹纳

    • 主机:本地主机

    • 密码 : Vacc1nat10n

    向下滚动并单击“全局权限”旁边的“全部选中”。

  6. 滚动到底部,然后单击开始。

创建用于连接到数据库的文件

将以下文件命名为 mysqli_connect.php (或从 www.apress.com 下载),并添加到 htdocs 文件夹中的邮政文件夹中:

<?php
// Create a connection to the postaldb database
// Set the encoding to utf-8
// Set the database access details as constants
Define ('DB_USER', 'jenner');
Define ('DB_PASSWORD', 'Vacc1nat10n');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'postaldb');
// Make the connection
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
mysqli_set_charset($dbcon, 'utf8'); // Set the encoding...

测试您的连接(http://localhost/postal/mysqli _ connect . PHP)。请记住,如果您在屏幕上看不到任何内容,则您的连接是成功的。

接下来,如果您还没有这样做,请按照下一节中的描述创建包含列和属性的表。当您成功输入表 5-1 中的信息后,向下滚动到 phpMyAdmin 页面的底部,然后单击 Go(或 Submit)。

创建表

我们现在将创建一个适用于需要地址和电话号码的会员数据库的表。数据库表还将包括额外的项目,以方便管理员。这些是会员的类别和一个指示会员是否已支付订阅费的字段。该表将包含标题和属性如表 5-1 所示的列。

表 5-1

用户表的标题和属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

阿奇

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 使用者辩证码 | 中位 | six | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 注册日期 | DATETIME |   | 没有人 |   | -是吗 |   | -是吗 |
| 用户级别 | 蒂尼因特 | one | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 班级 | 茶 | Twenty | 没有人 |   | -是吗 |   | -是吗 |
| 地址 1 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 地址 2 | 可变长字符串 | Fifty | 没有人 |   | ·······················。 |   | -是吗 |
| 城市 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 州 _ 国家 | 茶 | Twenty-five | 没有人 |   | -是吗 |   | -是吗 |
| S7-1200 可编程控制器 | 茶 | Ten | 没有人 |   | -是吗 |   | -是吗 |
| 电话 | 茶 | Fifteen | 没有人 |   | ·······················。 |   | -是吗 |
| 有报酬的 | ENUM | “是”,“不是” | 没有人 |   | -是吗 |   | -是吗 |

请注意,address2(第二个地址)和 phone 列选择了它们的空索引框。它们是可选字段。NULL 仅仅意味着用户可以将一个字段留空。许多用户可能没有额外的地址(例如大城市的一个区或一个公寓号码)。一些用户不想公开他们的电话号码,以减少陌生来电者带来的麻烦。

虽然邮政编码与该人居住的城市直接相关,但最好包括城市和州或国家信息,以验证是否输入了正确的代码。

想注册成为会员的人需要付费。然而,费用会随着时间而变化。为了避免每次有价格调整时都要求开发人员进行更改,我们将把价格存储在一个表中,并在需要时检索它们。这将允许网站管理员在数据库中进行价格更改,价格将立即出现在页面上。我们也可以为管理员创建一个价格变化页面,但我们会留给你去开发。

表 5-2

价格表的标题和属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

阿奇

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 一年 b | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 一岁 | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 五年 b | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 五年 | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 军队 | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 美国军方 | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| u21gb | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| u21us | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 最低价格 b | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| minpriceus | 小数 | six | 没有人 | 无符号的 | -是吗 |   | -是吗 |

表 5-2 显示了价格表的详细信息

创建价格表后,单击 phpMyAdmin 中的 Insert 选项卡,并在每个单元格的值文本框中输入数字,如下所示。然后单击 Go 按钮将值保存到表中。

|

one year GB30:00

|   |

one year40:00

|   |

fife year GB125.00

|   |
| --- | --- | --- | --- | --- | --- |
| fife year GB140.00 | 军用 gb 5.00 |   | 军事人员上午 8 点 |   |   |
| u21gb 2.00 |   | u21us 3.00 |   |   | 最低价格 gb 15.00 |
|   |   |   | 矿山精度 20 点 |   |   |

使用枚举

ENUM(枚举)提供了列出要输入到数据库中的有效选项的能力。它确保只有您指定的值才能存储在表中的该单元格中。在我们的教程中,只能输入否或是。选项必须用逗号分隔,并且每个选项必须用单引号或双引号括起来。您可以在框中键入值,或者单击框下方的链接。如果您单击编辑枚举/设置值链接,您可以键入一个值列表,系统将为您添加引号和逗号。如果为“已付”列选择 NOT NULL,则第一个值(否)将是默认值。如果为枚举列选择空框,则空字段将是有效值。

文件的重要性

因为数据库变得越来越复杂,所以您必须继续记录您所做的一切。如果你不这样做,你会花很多不愉快的时间去想你做了什么,为什么某些改变不起作用。对于许多页面来说尤其如此,就像在当前项目中一样。记住,数据库管理员负责许多数据库。数据库或表的调整可能需要几个月或几年的时间。即使站长创建了原始数据库,也不太可能记得原始设计。通过创建设计文档,它将帮助网站管理员快速记住或理解逻辑,并做出任何必要的更改。

我们建议在笔记本(物理或电子)中仔细记录。创建显示数据库和程序之间逻辑关系的流程图;事实上,没有流程图是很难工作的。图表通常会延伸几页。您可能想要打印图表并将它们粘贴在一起。当团队重新设计应用及其数据库的逻辑时,在会议室的墙上放置图表并不罕见。许多人还把图表放在一个共享系统中,比如微软的 SharePoint。通过使用这种类型的系统,设计团队可以很容易地跟踪不同的版本。

下面的示例图表显示了所有页面、页面标题、页眉以及页面之间的链接。随着工作的进展,通过纠正错误、添加页面和添加客户要求的任何新功能来修改流程图。在开发数据库驱动的网站时,修改图表是至关重要的。作为一个练习,您可以修改这个例子来包含价格表。

图 5-1 显示了示例流程图的一小部分。

img/314857_2_En_5_Fig1_HTML.jpg

图 5-1

流程图的一小部分

商业程序可用于创建流程图,但您可以使用 Microsoft Word 中的绘图工具栏和文本框。菱形文本框表示决策结构(开关)。图 5-1 所示流程图中的结构决定了成员的 user_level,并将代码逻辑切换到成员或管理员页面。互联网上有许多免费教程和视频可以演示如何创建流程图。Visio 是具有许多附加功能的付费替代产品;要获得优秀的免费程序,请尝试使用以下网站的图表设计器:

meesoft。逻辑学家。dk/

更多免费流程图程序可在 www.download.com 找到。

现在让我们根据前面教程中使用的代码创建一个注册页面。我们将调整代码,在新表中包含额外的字段。代码还将提供一个下拉选择菜单。注册页面将显示 12 个字段;其他字段将对用户隐藏。

扩展注册表单并添加下拉菜单

图 5-2 显示了扩展注册页面。

img/314857_2_En_5_Fig2_HTML.jpg

图 5-2

扩展注册页面

必填字段标有星号。当正确填写字段并单击注册按钮时,用户将被带到一个包含 PayPal 支付按钮和徽标的“谢谢”页面。然后,用户将通过 PayPal 或借记卡/信用卡支付适当的注册费。

总是提前宣布价格和费用支付

始终预先声明全部费用和可用的付款方式。这现在是英国的一项法律要求。在世界上的其他地方,这可能不是法律要求,但你应该总是提前申报所有费用。如果在交易结束时突然宣布额外费用,用户会变得不耐烦。他们通常会不付钱就关闭你的网站。除了说明费用,我们还会添加一张 PayPal 提供的图片,显示各种可接受的信用卡。但是,用户只有在提交注册表格并出现“谢谢”页面后才能付款。

注意

如前所述,网站必须符合所在国的规定。这可能需要不同国家的不同网站。

注册页面现在包含 12 个可见字段和隐藏字段。隐藏字段是注册日期和已支付。隐藏字段在用户屏幕上显示的页面上不可见,因为它们只与管理员相关。清单 5-1a 显示了新字段。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Register Page</title>
  <meta charset="utf-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
   <script src="verify.js"></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
       padding:20px;">
       <?php include('register-header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
               <?php include('nav.php'); ?>
      </ul>
  </nav>
<!-- Validate Input -->
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
 require('process-register-page.php');
} // End of the main Submit conditional.
?>
<div class="col-sm-8">
<h2 class="h2 text-center">Register</h2>
<h3 class="text-center">Items marked with an asterisk * are required</h3>
<?php
try {
        require_once ("mysqli_connect.php");$query = "SELECT * FROM prices";          //#1
        $result = mysqli_query ($dbcon, $query); // Run the query.
        if ($result) { // If it ran OK, display the records.
               $row = mysqli_fetch_array($result, MYSQLI_NUM);
               $yearsarray = array(
               "Standard one year:", "Standard five year:",
               "Military one year:", "Under 21 one year:",
               "Other - Give what you can. Maybe:" );
echo '<h6 class="text-center text-danger">Membership classes:</h6>' ;
echo '<h6 class="text-center text-danger small"> ';
for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {
        echo $yearsarray[$j] . " &pound; " .
               htmlspecialchars($row[$i], ENT_QUOTES)  .
               " GB, &dollar; " .
               htmlspecialchars($row[$i + 1], ENT_QUOTES) .
               " US";
        if ($j != 4) {
        if ($j % 2 == 0) {
               echo "</h6><h6 class='text-center text-danger small'>"; }
        else {
               echo " , "; }
        }
}
echo "</h6>";
}
?>
<form action="register-page.php" method="post" onsubmit="return checked();"
        name="regform" id="regform">
<div class="form-group row">
    <label for="first_name" class="col-sm-4 col-form-label">
        *First Name:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="first_name"
        name="first_name"
        placeholder="First Name" maxlength="30" required
        value=
               "<?php if (isset($_POST['first_name']))
               echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >
</div>
  </div>
  <div class="form-group row">
  <label for="last_name" class="col-sm-4 col-form-label">*Last Name:</label>
  <div class="col-sm-8">
      <input type="text" class="form-control" id="last_name" name="last_name"
               placeholder="Last Name" maxlength="40" required
               value=
                              "<?php if (isset($_POST['last_name']))
               echo htmlspecialchars($_POST['last_name'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
       <label for="email" class="col-sm-4 col-form-label">*E-mail:</label>
  <div class="col-sm-8">
       <input type="email" class="form-control" id="email" name="email"
               placeholder="E-mail" maxlength="60" required
               value=
                       "<?php if (isset($_POST['email']))
               echo htmlspecialchars($_POST['email'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
       <label for="password1"
                class="col-sm-4 col-form-label">*Password:</label>
  <div class="col-sm-8">
      <input type="password" class="form-control" id="password1"
        name="password1"
        placeholder="Password" minlength="8" maxlength="12" required
               value=
                       "<?php if (isset($_POST['password1']))
               echo htmlspecialchars($_POST['password1'], ENT_QUOTES); ?>" >
        <span id="message">Between 8 and 12 characters.</span>
  </div>
  </div>
  <div class="form-group row">
        <label for="password2" class="col-sm-4 col-form-label">
               *Confirm Password:</label>
  <div class="col-sm-8">
      <input type="password" class="form-control" id="password2"
        name="password2"
        placeholder="Confirm Password" minlength="8" maxlength="12" required
               value=
                       "<?php if (isset($_POST['password2']))
               echo htmlspecialchars($_POST['password2'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
        <label for="level" class="col-sm-4 col-form-label">
               *Membership Class</label>
  <div class="col-sm-8">                                                       <!--#2-->
               <select id="level" name="level" class="form-control" required>
               <option value="0" >-Select-</option>
<?php
        for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {
               echo '<option value="' .
               htmlspecialchars($row[$i], ENT_QUOTES) . '" ';
        if ((isset($_POST['level'])) && ( $_POST['level'] == $row[$i]))
               {
        ?>
               selected
        <?php }
        echo ">" . $yearsarray[$j] . " " .
            htmlspecialchars($row[$i], ENT_QUOTES) .
               " &pound; GB, " .
                htmlspecialchars($row[$i + 1], ENT_QUOTES) .
                "&dollar; US</option>";
}
?>
</select>
</div>
</div>
<div class="form-group row">                                                   <!--#3-->
    <label for="address1" class="col-sm-4 col-form-label">*Address:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="address1" name="address1"
          placeholder="Address" maxlength="30" required
               value=
                       "<?php if (isset($_POST['address1']))
               echo htmlspecialchars($_POST['address1'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="address2" class="col-sm-4 col-form-label">Address:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="address2" name="address2"
          placeholder="Address" maxlength="30"
               value=
                       "<?php if (isset($_POST['address2']))
               echo htmlspecialchars($_POST['address2'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="city" class="col-sm-4 col-form-label">*City:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="city" name="city"
          placeholder="City" maxlength="30" required
               value=
                       "<?php if (isset($_POST['city']))
               echo htmlspecialchars($_POST['city'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
    <label for="state_country"
        class="col-sm-4 col-form-label">*State/Country:</label>
  <div class="col-sm-8">
      <input type="text" class="form-control"
               id="state_country" name="state_country"
               placeholder="State or Country" maxlength="30" required
               value=
                       "<?php if (isset($_POST['state_country']))
               echo htmlspecialchars($_POST['state_country'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
    <label for="zcode_pcode" class="col-sm-4 col-form-label">
               *Zip Code/Post Code:</label>
    <div class="col-sm-8">
               <input type="text" class="form-control" id="zcode_pcode"
                       name="zcode_pcode"
               placeholder="Zip Code or Postal Code" maxlength="15" required
               value=
                       "<?php if (isset($_POST['zcode_pcode']))
               echo htmlspecialchars($_POST['zcode_pcode'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
        <label for="phone" class="col-sm-4 col-form-label">
               Phone Number:</label>
  <div class="col-sm-8">
      <input type="tel" class="form-control" id="phone" name="phone"
               placeholder="Phone Number" maxlength="30"
               value=
                       "<?php if (isset($_POST['phone']))
               echo htmlspecialchars($_POST['phone'], ENT_QUOTES); ?>" >
  </div>
  </div>
  <div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
  <div class="col-sm-8">
        <input id="submit" class="btn btn-primary" type="submit"
               name="submit" value="Register">
  </div>
  </div>
</form>
</div>
<!-- Right-side Column Content Section -->
<?php

 if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('info-col-cards.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
                style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
        include('footer.php');
        echo "</footer>";
        echo "</div>";
  }
catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
 ?>
</body>
</html>

Listing 5-1aCreating the New

Registration Page with 12 Visible Fields (register-page.php)

代码的解释

本节解释代码。

$query = "SELECT * FROM prices";                                              //#1
$result = mysqli_query ($dbcon, $query); // Run the query.
if ($result) { // If it ran OK, display the records.
        $row = mysqli_fetch_array($result, MYSQLI_NUM);

所有的价格都从 prices 表中提取出来,创建一个名为 $row 的数字索引数组。

$yearsarray = array(
"Standard one year:", "Standard five year:",
"Military one year:", "Under 21 one year:",
"Other - Give what you can. Maybe:" );

创建一个数组$yearsarray 来保存不同类的文本。在大多数编程语言中,不能更改用这种格式创建的数组的内容。然而,在 PHP 中你可以。

echo '<h6 class="text-center text-danger">Membership classes:</h6>' ;
echo '<h6 class="text-center text-danger small"> ';
for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {

在 PHP 中,一个 for 循环可以有多个索引,每个索引可以单独递增或递减。在这个例子中,\(j 每次增加 1,而\)i 每次增加 2。每个单一描述(标准一年)有两个值(GB 和 US)。因此,当描述一次改变一个时,价格跳升两个位置。

       echo $yearsarray[$j] . " &pound; " .
               htmlspecialchars($row[$i], ENT_QUOTES)  .
               " GB, &dollar; " .
               htmlspecialchars($row[$i + 1], ENT_QUOTES) .
               " US";
$j is controlling the description. $i is controlling the two prices to be displayed.
        if ($j != 4) {
        if ($j % 2 == 0) {
               echo "</h6><h6 class='text-center text-danger small'>"; }
        else {
               echo " , "; }
        }

对于\(J 的每两次跳转(这样我们可以在一行上有两个描述),我们关闭行()并为下一个描述打开一个新行,除非\)j 是 4。这将表明没有另一个描述,所以我们不需要开始一个新的行。

}
echo "</h6>";
}
At the end we close the line of the last description.
<div class="col-sm-8">                                                          <!--#2-->
               <select id="level" name="level" class="form-control" required>
               <option value="0" >-Select-</option>
<?php
        for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {
               echo '<option value="' .
               htmlspecialchars($row[$i], ENT_QUOTES) . '" ';
        if ((isset($_POST['level'])) && ( $_POST['level'] == $row[$i]))
               {
        ?>
               selected
        <?php }
        echo ">" . $yearsarray[$j] . " " .
           htmlspecialchars($row[$i], ENT_QUOTES) .
               " &pound; GB, " .
                htmlspecialchars($row[$i + 1], ENT_QUOTES) .
                "&dollar; US</option>";
}
?>
</select>

本例的信息循环和控制与上例相似。但是,我们正在为表单创建一个 select 语句。请注意,代码会检查用户之前是否选择了一个级别。如果是,它会将“selected”放在为该级别创建的选项语句中(如标准的一年)。注意,要使 selected 在表单上正确工作,它不能在 PHP 代码的 echo 语句中。

<div class="form-group row">                                                  <!--#3-->
    <label for="address1" class="col-sm-4 col-form-label">*Address:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="address1" name="address1"
         placeholder="Address" maxlength="30" required
               value=
                       "<?php if (isset($_POST['address1']))
               echo htmlspecialchars($_POST['address1'], ENT_QUOTES); ?>" >
</div>
</div>

表单中附加字段的 HTML 代码与我们之前看到的格式相同。在使用 PHP 对输入标签进行消毒之后,输入标签会将用户输入的任何内容粘回到表单中。

<?php
// This script is a query that INSERTs a record in the users table.
// Check that form has been submitted:
try {
        $errors = array(); // Initialize an error array.
        // Check for a first name:
        $first_name =
        filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
        if (empty($first_name)) {
                $errors[] = 'You forgot to enter your first name.';
        }
        // Check for a last name:
           $last_name =
        filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
        if (empty($last_name)) {
                $errors[] = 'You forgot to enter your last name.';
        }
        // Check for an email address:
           $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
                $errors[] = 'You forgot to enter your email address';
                $errors[] = ' or the e-mail format is incorrect.';
        }
        // Check for a password and match against the confirmed password:
                       $password1 =
        filter_var( $_POST['password1'], FILTER_SANITIZE_STRING);
                       $password2 = f
        ilter_var( $_POST['password2'], FILTER_SANITIZE_STRING);
        if (!empty($password1)) {
               if ($password1 !== $password2) {
                       $errors[] = 'Your two password did not match.';
               }
        } else {
               $errors[] = 'You forgot to enter your password(s).';
        }
        // Check for an membership class
        if(isset($_POST['level'])) {
               $class =
               filter_var( $_POST['level'], FILTER_SANITIZE_STRING); }
        if (empty($class)) {
               $errors[] = 'Please choose your membership class.';
        }
        // Check for address:
               $address1 =
                       filter_var( $_POST['address1'], FILTER_SANITIZE_STRING);
        if (empty($address1)) {
               $errors[] = 'You forgot to enter your address.';
        }
        // Check for address2:                                                        #1
               $address2 =
               filter_var( $_POST['address2'], FILTER_SANITIZE_STRING);
        if (empty($address2)) {
               $address2 = NULL;
        }
        // Check for city:
               $city =
                       filter_var( $_POST['city'], FILTER_SANITIZE_STRING);
        if (empty($city)) {
               $errors[] = 'You forgot to enter your City.';
        }
// Check for the county:
               $state_country =
               filter_var( $_POST['state_country'], FILTER_SANITIZE_STRING);
        if (empty($state_country)) {
               $errors[] = 'You forgot to enter your country.';
        }
        // Check for the post code:
               $zcode_pcode =
               filter_var( $_POST['zcode_pcode'], FILTER_SANITIZE_STRING);
        if (empty($zcode_pcode)) {
               $errors[] = 'You forgot to enter your post code.';
        }
        // Check for the phone number:
               $phone =
               filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
               if (empty($phone)) {
                       $phone = NULL;
        }
        if (empty($errors)) { // If everything's OK.
        //Determine whether the email address has already been registered             #2
               require ('mysqli_connect.php'); // Connect to the db.
               $query = "SELECT userid FROM users WHERE email = ? ";
               $q = mysqli_stmt_init($dbcon);
               mysqli_stmt_prepare($q, $query);
               mysqli_stmt_bind_param($q, 's', $email);
               mysqli_stmt_execute($q);
               $result = mysqli_stmt_get_result($q);

               if (mysqli_num_rows($result) == 0){
               //The email address has not been registered
        // Register the user in the database...
        // Hash password current 60 characters but can increase
            $hashed_passcode = password_hash($password1, PASSWORD_DEFAULT);
               require ('mysqli_connect.php'); // Connect to the db.
               // Make the query:
               $query =
"INSERT INTO users (userid, first_name, last_name, email, password, ";
               $query .=
"class, address1, address2, city, state_country, zcode_pcode, phone, ";
               $query .= "registration_date ) ";
               $query .=
        "VALUES(' ', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW() )";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // use prepared statement to ensure that only text is inserted
        // bind fields to SQL Statement
        mysqli_stmt_bind_param($q, 'sssssssssss',
                       $first_name, $last_name, $email, $hashed_passcode,
                       $class, $address1, $address2, $city, $state_country,
                       $zcode_pcode, $phone);
     // execute query
        mysqli_stmt_execute($q);
        if (mysqli_stmt_affected_rows($q) == 1) { // One record inserted
               header ("location: register-thanks.php?class=" . $class);
               exit();
               } else { // If it did not run OK.
               // Public message:
                  $errorstring =
               "<p class='text-center col-sm-8' style='color:red'>";
               $errorstring .=
                       "System Error<br />You could not be registered due ";
               $errorstring .=
               "to a system error. We apologize for any inconvenience.</p>";
               echo "<p class=' text-center col-sm-2'
                        style='color:red'>$errorstring</p>";
               // Debugging message below do not use in production
               //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $query . '</p>';
               mysqli_close($dbcon); // Close the database connection.
               // include footer then close program to stop execution
               echo '<footer class="jumbotron text-center col-sm-12"
                       style="padding-bottom:1px; padding-top:8px;">
            include("footer.php");
            </footer>';
        exit();
               }else{//The email address is already registered                     #3
               $errorstring = 'The email address is already registered.';
                       echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
        }
        } else { // Report the errors.
               $errorstring =
               "Error! <br /> The following error(s) occurred:<br>";
               foreach ($errors as $msg) { // Print each error.
                       $errorstring .= " - $msg<br>\n";
               }
               $errorstring .= "Please try again.<br>";
               echo "<p class=' text-center col-sm-2'
                       style='color:red'>$errorstring</p>";
               }// End of if (empty($errors)) IF.
        }
   catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
   }
   catch(Error $e)
   {
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
   }
?>

Listing 5-2bCode for the New Registration Page with 12 Visible Fields (process-register-page.php)

代码的解释

本节解释代码。

// Check for address2:                                                                #1
               $address2 =
               filter_var( $_POST['address2'], FILTER_SANITIZE_STRING);
        if (empty($address2)) {
               $address2 = NULL;
        }

第二个地址字段(地址 2)不是必填字段。该字段没有必需的属性,因为用户可以故意将其留空。如果该字段为空,将执行 else 语句。没有错误消息。相反,关键字 NULL 被赋给变量$adress2。NULL 表示没有值。如果 address2 的注册表单字段为空,则数据库表中的 address2 单元格也将被设置为 NULL。检查代码的其余部分,看看是否可以找到用于另一个可选字段的相同模式。

//Determine whether the email address has already been registered                     #2
       require ('mysqli_connect.php'); // Connect to the db.
       $query = "SELECT userid FROM users WHERE email = ? ";
       $q = mysqli_stmt_init($dbcon);
       mysqli_stmt_prepare($q, $query);
       mysqli_stmt_bind_param($q, 's', $email);
       mysqli_stmt_execute($q);
       $result = mysqli_stmt_get_result($q);

       if (mysqli_num_rows($result) == 0){

现在进行检查以确定电子邮件是否已经在数据库中。如果不是,则插入信息。

}else{//The email address is already registered                                       #3
               $errorstring = 'The email address is already registered.';
                       echo "<p class=' text-center col-sm-2'
                       style='color:red'>$errorstring</p>";
       }
If the email already exists, display an error message.

扩展注册页面通知用户可以使用 PayPal 或借记卡/信用卡支付会员费。这是通过使用右侧信息栏中的 PayPal 图片实现的,如下一节所示。

添加 PayPal 借记卡/信用卡图片

这张图片是从 PayPal 网站下载的,并被添加到新版本的信息栏中。结果如图 5-3 所示。

img/314857_2_En_5_Fig3_HTML.jpg

图 5-3

修订信息栏

新的info-col-cards.php文件仅包含在注册页面和“感谢”页面中。所有其他页面都不需要这些信息;因此,他们将包括原来的 info-col.php

*## 包括 PayPal 的“谢谢”页面

“谢谢”页面现在将添加一种支付方式。如图 5-4 所示。

img/314857_2_En_5_Fig4_HTML.jpg

图 5-4

“谢谢”页面上添加了一个 PayPal 链接

前一章中的“谢谢”页构成了新页面的基础。用户的成员资格申请将被记录在数据库中,但是付费单元将包含默认值 No。这是因为付费项目是对用户隐藏的 ENUM。管理员是唯一被允许在该字段中输入数据的人。当管理员收到 PayPal 确认付款的电子邮件时(或当信用卡付款到达时),管理员会将“已付款”单元格更改为“是”。PayPal 电子邮件还提供了新成员的地址和电子邮件。如果管理员对详细信息与用户在数据库中输入的信息相匹配感到满意,则数据库中成员的已付字段将被设置为是。管理员需要比较用户最初选择的会员类别和用户支付的类别,以确保它们匹配。如果有差异,管理员还需要调整数据库中的付费类。

我们在“谢谢”页面中使用 PayPal,因为它是一种流行的支付系统。网站所有者可以在 paypal 注册。com 页面(或 paypal.co.uk 页面)。他们将获得一个商家 ID,并选择生成和复制代码片段,以嵌入任何页面中的支付链接。如果所有者已经有一个 PayPal 帐户,他们可以登录并进入我的个人资料和我的商业信息,在那里他们可以找到商家帐户 ID。该 ID 将使他们能够访问适当的生成代码以嵌入到页面中。

注:register-thanks.php的 PayPal 代码仅用于演示。提供的代码将不起作用,因为您必须有一个商家帐户 ID。PayPal 提供测试支付交易的能力,而无需处理 PayPal 或借记卡/信用卡账户。一旦您有了帐户,PayPal 将提供代码,您可以复制并粘贴这些代码来替换本演示中的示例代码。您可以在此找到有关设置测试环境的更多信息:

https://developer.paypal.com/docs/classic/paypal-payments-standard/ht_test-pps-buttons/

清单 5-4 展示了“谢谢”页面的代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Register Thanks</title>
  <meta charset="utf-8">
  <meta name="viewport"
       content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
style="margin-bottom:2px;
background:linear-gradient(white, #0073e6); padding:20px;">
  <?php include('thanks-header.php'); ?>
</header>
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
               <?php include('nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8">
<h3 class="h2 text-center" >Thank you for registering</h2>
<h6 class="text-center">
To confirm your registration please verify membership class and pay the membership fee now.</h6>
<h6 class="text-center">
You can use PayPal or a credit/debit card.</h6>
<p class="text-center" >
When you have completed your registration you will be able to login
to the member's only pages.</p>
<?php
try {
        require ("mysqli_connect.php");
        $query = "SELECT * FROM prices";
        $result = mysqli_query ($dbcon, $query); // Run the query.
        if ($result) { // If it ran OK, display the records.
        $row = mysqli_fetch_array($result, MYSQLI_NUM);
        $yearsarray = array(
"Standard one year:", "Standard five year:", "Military one year:",
"Under 21 one year:", "Other - Give what you can. Maybe:" );
echo '<h6 class="text-center text-danger">Membership classes:</h6>' ;
echo '<h6 class="text-center text-danger small"> ';
for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {

        echo $yearsarray[$j] . " &pound; " .
               htmlspecialchars($row[$i], ENT_QUOTES)  .
               " GB, &dollar; " .
               htmlspecialchars($row[$i + 1], ENT_QUOTES) .
               " US";

        if ($j != 4) {
        if ($j % 2 == 0) { echo "</h6><h6 class='text-center text-danger small'>"; }
        else { echo " , "; }
        }
}
echo "</h6>";
}
?>
<p></p>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="XXXXXXXXXXXXX">
<div class="form-group row">
    <label for="level" class="col-sm-4 col-form-label">
        *Membership Class</label>
<div class="col-sm-8">
        <select id="level" name="level" class="form-control" required>
        <option value="0" >-Select-</option>
<?php                                                                             //#1
$class = htmlspecialchars($_GET['class'], ENT_QUOTES);
for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {

        echo '<option value="' .
                htmlspecialchars($row[$i], ENT_QUOTES) . '" ';
        if ((isset($class)) && ( $class == $row[$i]))
               {
                       echo ' selected ';
         }
        echo ">" . $yearsarray[$j] . " " .
            htmlspecialchars($row[$i], ENT_QUOTES) .
               " &pound; GB, " .
               htmlspecialchars($row[$i + 1], ENT_QUOTES) .
               "&dollar; US</option>";
}
?>
</select>
</div>
</div>
<div class="form-group row">
    <label for="" class="col-sm-4 col-form-label"></label>
    <div class="col-sm-8">
<!--                                                                              #2-->
<!-- Replace the code below with code provided by PayPal once you obtain a Merchant ID -->
<input type="hidden" name="currency_code" value="GBP">
<input style="margin:10px 0 0 40px" type="image" src="https://www.paypalobjects.com/en_US/GB/i/btn/btn_buynowCC_LG.gif" name="submit" alt="PayPal  The safer, easier way to pay online.">
<img alt="" src="https://www.paypalobjects.com/en_GB/i/scr/pixel.gif" width="1" height="1">
<!-- Replace code above with PayPal provided code -->
</div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
               <?php include('info-col-cards.php'); ?>
        </aside>
  </div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
        <?php include('footer.php'); ?>
</footer>
<?php
} // end try
catch(Exception $e) // We finally handle any problems here
 {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
 }
catch(Error $e)
{
     //print "An Error occurred. Message: " . $e->getMessage();
     print "The system is busy please try again later.";
}
?>
</body>
</html>

Listing 5-4Creating a Combined “Thank You” and PayPal Payment Page (register-thanks.php)

代码的解释

本节描述代码。

<?php                                                                                //#1
$class = htmlspecialchars($_GET['class'], ENT_QUOTES);
for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {

        echo '<option value="' .
               htmlspecialchars($row[$i], ENT_QUOTES) . '" ';
        if ((isset($class)) && ( $class == $row[$i]))
               {
                       echo ' selected ';
         }
        echo ">" . $yearsarray[$j] . " " .
            htmlspecialchars($row[$i], ENT_QUOTES) .
               " &pound; GB, " .
               htmlspecialchars($row[$i + 1], ENT_QUOTES) .
               "&dollar; US</option>";
}

为类创建下拉列表的代码类似于 register_page.php 文件中的代码。但是,决定将哪个类设置为选中状态是由$class 中的值决定的,该值是从 register 页面传递到页面中的。

<!--                                                                                 #2-->
<!-- Replace the code below with code provided by PayPal once you obtain a Merchant ID -->
<input type="hidden" name="currency_code" value="GBP">
<input style="margin:10px 0 0 40px" type="image" src="https://www.paypalobjects.com/en_US/GB/i/btn/btn_buynowCC_LG.gif" name="submit" alt="PayPal  The safer, easier way to pay online.">
<img alt="" src="https://www.paypalobjects.com/en_GB/i/scr/pixel.gif" width="1" height="1">
<!-- Replace code above with PayPal provided code -->

这是 PayPal 生成的一小部分代码。为了安全起见,我们在本教程中为 PayPal ID 使用了一个虚拟值。PayPal 提供 13 个字符的 ID 代码。标题现在没有菜单按钮;在收到注册人的付款之前,注册人只能访问主页。主页可以从主菜单访问。

在我们继续之前,你需要注册一些额外的成员。

注册一些成员

小费

输入数据可能很繁琐。为了减少本教程的繁琐,每个条目的地址和电话号码可以是相同的。接下来建议一组合适的数据。

实验时,每个记录都使用以下数据。这是可以接受的,因为没有使用地址和电话号码进行搜索。

  • 会员类别:对于每个条目的会员类别,选择任何下拉选项。

  • 地址 1 : 2 街。

  • 地址 2 :村庄(为了演示数据库中 NULL 设置的效果,将其中一些字段留空,并用村庄填充一些字段)。

  • 城市:汤斯维尔。

  • 国家或州:英国(如果是美国的一个州,则为 CA)。

  • 邮政编码 : EX7 9PP(或美国邮政编码 33040)。

  • 电话 : 01234 777 888(或美国电话号码,3055551111。这个字段是可选的,你应该在一些记录中省略电话。

注册表 5-3 中列出的成员。

表 5-3

一些成员姓名

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- |
| 詹姆斯 | 锻工 | 斯科史密斯@myisp.com | Bl3cksmith |
| 插口 | 锻工 | jsmith@outcook.com | 那就好 |
| 迈克 | 这是什么 | 米克尔@myisp.com | 威尔盖特 3 公司 |
| 橄榄 | 树枝 | obranch@myisp.co.uk | S7-1200 可编程控制器 |
| 弗兰克 | 香 | fincense@myisp.net | P3 RFM 3s |
| 安妮 | 周年纪念 | aversary@myisp.com | 页:1 |
| 泰丽 | Fide | tfide @ my ISP . com | S7-1200 可编程控制器 |
| 玫瑰 | 矮树丛 | rbush@myisp.co.uk | R3db100ms |
| 安妮 | 苔藓 | amositty@myisp.org.uk | S7-1200 可编程控制器 |
| 皮尔斯!皮尔斯 | 转向 | pver @ my ISP . com | K33pg01ng |
| 戴劳 | 豆儿 | ddoo@myisp.co.uk | 星期六 f1ed |
| 斯坦 | 达尔德族人 | sdad @ my ISP . net | b@ttl3fl@g |
| Honora | 骨 | nbone@myisp.com | L1k3t3rr13r |
| 巴里 | 凯德 | bcade@myisp.co.uk | Bl0ckth3m |

如果您以后需要更多的数据来处理,表 5-4 中给出了一些建议。

表 5-4

注册的一些附加建议

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- |
| 迪伊 | 被拒绝 | djected@myisp.org.uk | 总帐 00mnd00m |
| 林恩 | 种子 | lseed@myisp.com | 第一季第 3 集 |
| 巴里 | 音调 | btone@myisp.net | 尼奇 3v01c3 |
| 海伦 | 背部 | hback@myisp.net | Agr1ms0rt1e |
| 帕特里克(男子名) | 欧哈拉 | pohara@myisp.org.uk | shame0ck |
| 黎明 | 合唱 | dchorus@myisp.com | Tr1ll1ng(消歧义) |
| 康妮 | 冷杉 | cfirst@myisp.com | P1n3tr33s |
| 伊娃 | 内桑特 | enessant@outlook.com | 反 13nt |
| -艾尔 | 壁画 | sfeirco @ my ISP . net | Fr3sha1rf00d |

重要的

杰克·史密斯将再次担任管理人。进入 phpMyAdmin,将杰克·史密斯的 user_level 从 0 改为 1。另外,一定要使用 phpMyAdmin 将管理员的 paid 列更改为 Yes。将一些其他成员的付费列更改为是。

要将 payed 字段更改为 Yes,在输入所有成员后,打开 phpMyAdmin,在左侧面板中单击 postaldb 旁边的+号,然后单击带下划线的单词 users 。用户的记录将以水平格式显示。要更改成员的已付字段,请单击每个成员记录左侧的编辑项目。然后,记录将以更方便的垂直格式显示。向下滚动到付费枚举单选按钮,然后单击是按钮。然后单击开始。

注意

记住,如果你不能登录一个会员的账户,很可能是因为他的付费栏没有设置为是。这是应该的。

对登录页面的一个小修改

登录页面(包含在下载的文件中)与前一章中的页面几乎相同,除了我们必须防止申请人在管理员收到会员费之前访问会员专用页面。以下代码片段显示了对查询的修改:

// Retrieve the user_id, psword, first_name and user_level for that
// email/password combination
 $query = "SELECT user_id, psword, fname, user_level FROM users ";
 $query .= "WHERE paid="Yes" AND email=?";

错误消息也修改如下:

echo '<p class="error">our E-mail/Password does not match our records.';
echo ' Perhaps your fee has not yet been processed from PayPal or the credit card.';
echo '<br>Perhaps you need to register, just click the Register' ;
echo 'button on the header menu</p>';

我们现在必须改变管理员的页面以匹配额外的列。例如,会员秘书(管理员)想要更新哪些会员已经支付了费用或请求了其他更改。例如,伊娃·内桑特可能会搬家或改名。在第六章中,我们还会加入包含一个头衔的能力(先生小姐夫人,Mx。博士)发给每个成员。在第七章中,我们将减轻管理员的工作量,允许成员在自己的记录中更新这些字段。

我们现在将对管理页面的标题进行更改,以包含一个新按钮。

修改管理员的标题

为了避免一英里宽的表格显示,大量的列将被分成两个单独的显示:一个显示成员的详细信息,另一个显示成员的地址和电话号码。需要一个新的搜索工具,以便成员的地址可以显示在表中。这将通过标题菜单上标记为地址的新按钮启动。

我们已经修改了标题以包含 Addresses 按钮,如图 5-5 所示。因为标题是启动板,我们将从列出新标题开始。

img/314857_2_En_5_Fig5_HTML.jpg

图 5-5

包含“新地址”按钮的新标题

新地址按钮如清单 5-5 所示。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
        <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
</div>
        <nav class="col-sm-2">
        <div class="btn-group-vertical btn-group-sm" role="group"
               aria-label="Button Group">
               <button type="button" class="btn btn-secondary"
                       onclick="location.href = 'logout.php'" >
                       Logout</button>
               <button type="button" class="btn btn-secondary"
                       onclick="location.href = 'admin_view_users.php'">
                       View Members</button>
               <button type="button" class="btn btn-secondary"
                       onclick="location.href = 'search.php'">
                       Search</button>
               <button type="button" class="btn btn-secondary"
                       onclick="location.href = '#'">
                       Addresses</button>
               <button type="button" class="btn btn-secondary"
                       onclick="location.href = 'register-password.php'">
                       New Password</button>
        </div>
</nav>

Listing 5-5Installing a New Addresses Button in the Header (header-admin.php)

注意

链接搜索地址将不会工作,直到我们产生了 search_address.php 页面。我们将在下一章做这件事。

admin_view_users.php 文件显示的表格需要两个额外的列,称为付费

向 admin_view_users 表添加 Class 和 Paid

从管理员的角度来看,需要修改 admin_view_users 表以显示一些新列。分页后的表格需要显示如图 5-6 所示的列。

img/314857_2_En_5_Fig6_HTML.jpg

图 5-6

新表格显示。请注意新的 Class 和 Paid 列

为了节省您输入大量注册的繁琐任务,每个表再次被限制为四个记录。在实际情况下,每页显示大约 20 到 25 行。图 5-6 显示了带有两个额外列的表格显示。

清单 5-6 显示了对代码的修改。请注意,右侧的 info 列已被删除,以便为表格提供更多空间。在下一节中,我们将展示实现这一点的代码更改。

<?php
try {
        // This script retrieves all the records from the users table.
        require('mysqli_connect.php'); // Connect to the database.
        //set the number of rows per display page
        $pagerows = 4;
        // Has the total number of pages already been calculated?
        if ((isset($_GET['p']) && is_numeric($_GET['p']))) {
               //already been calculated
               $pages = htmlspecialchars($_GET['p'], ENT_QUOTES);
               // make sure it is not executable XSS
        }else{//use the next block of code to calculate the number of pages
               //First, check for the total number of records
               $query = "SELECT COUNT(userid) FROM users";
               $result = mysqli_query ($dbcon, $query);
               $row = mysqli_fetch_array ($result, MYSQLI_NUM);
               $records = htmlspecialchars($row[0], ENT_QUOTES);
               // make sure it is not executable XSS
               //Now calculate the number of pages
               if ($records > $pagerows){
                       //if the number of records will fill more than one page
                       //Calculate the number of pages and round the result up
                       // to the nearest integer
                       $pages = ceil ($records/$pagerows);
               }else{
                       $pages = 1;
               }
        }//page check finished
        //Declare which record to start with
        if ((isset($_GET['s'])) &&( is_numeric($_GET['s'])))
        {
               $start = htmlspecialchars($_GET['s'], ENT_QUOTES);
               // make sure it is not executable XSS
        }else{
               $start = 0;
        }
               $query = "SELECT last_name, first_name, email, ";                       //#1
               $query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
               $query .=" AS regdat, class, paid, userid FROM users ORDER BY ";
               $query .="registration_date DESC";
               $query .=" LIMIT ?, ?";

               $q = mysqli_stmt_init($dbcon);
               mysqli_stmt_prepare($q, $query);

               // bind $id to SQL Statement
               mysqli_stmt_bind_param($q, "ii", $start, $pagerows);

               // execute query
               mysqli_stmt_execute($q);
               $result = mysqli_stmt_get_result($q);

               if ($result) { //                                                       #2
               // If it ran OK (records were returned), display the records.
               // Table header
               echo '<table class="table table-striped">
               <tr>
               <th scope="col">Edit</th>
               <th scope="col">Delete</th>
               <th scope="col">Last Name</th>
               <th scope="col">First Name</th>
               <th scope="col">Email</th>
               <th scope="col">Date Registered</th>
               <th scope="col">Class</th>
               <th scope="col">Paid</th>
               </tr>';
               // Fetch and print all the records:
               while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
        // Remove special characters that might already be in table to
               // reduce the chance of XSS exploits                                   #3
               $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
               $last_name = htmlspecialchars($row['last_name'], ENT_QUOTES);
               $first_name = htmlspecialchars($row['first_name'], ENT_QUOTES);
               $email = htmlspecialchars($row['email'], ENT_QUOTES);
               $registration_date =
               htmlspecialchars($row['regdat'], ENT_QUOTES);
               $class = htmlspecialchars($row['class'], ENT_QUOTES);
               $paid = htmlspecialchars($row['paid'], ENT_QUOTES);
               echo '<tr>
               <td><a href="edit_user.php?id=' . $user_id . '">Edit</a></td>
               <td><a href="delete_user.php?id=' . $user_id . '">Delete</a></td>
               <td>' . $last_name . '</td>
               <td>' . $first_name . '</td>
               <td>' . $email . '</td>
               <td>' . $registration_date . '</td>
               <td>' . $class . '</td>
               <td>' . $paid . '</td>
               </tr>';
        }
               echo '</table>'; // Close the table.
               mysqli_free_result ($result); // Free up the resources.
}
else { // If it did not run OK.
// Error message:
        echo '<p class="text-center">The current users could not be ';
        echo 'retrieved We apologize for any inconvenience.</p>';
// Debug message:
// echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
// exit;
} // End of else ($result)
// Now display the total number of records/members.
$q = "SELECT COUNT(userid) FROM users";
$result = mysqli_query ($dbcon, $q);
$row = mysqli_fetch_array ($result, MYSQLI_NUM);
$members = htmlspecialchars($row[0], ENT_QUOTES);
mysqli_close($dbcon); // Close the database connection.
$echostring = "<p class='text-center'>Total membership: $members </p>";
$echostring .= "<p class='text-center'>";
if ($pages > 1) {//
        //What number is the current page?
        $current_page = ($start/$pagerows) + 1;
        //If the page is not the first page then create a Previous link
        if ($current_page != 1) {
        $echostring .= '<a href="admin_view_users.php?s=' .
               ($start - $pagerows) . '&p=' . $pages . '">
               Previous</a> ';
}
//Create a Next link
if ($current_page != $pages) {
        $echostring .= ' <a href="admin_view_users.php?s=' .
        ($start + $pagerows) . '&p=' . $pages . '">Next</a> ';
}
$echostring .= '</p>';
echo $echostring;
}
} //end of try
catch(Exception $e) // We finally handle any problems here
{
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
}
catch(Error $e)
{
      //print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try again later.";
}
?>

Listing 5-6Code for Creating the Paginated Table

to Display Two Extra Columns (process_admin_view_users.php)

代码的解释

本节解释代码。

$query = "SELECT last_name, first_name, email, ";                                    //#1
$query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
$query .=" AS regdat, class, paid, userid FROM users ORDER BY ";
$query .="registration_date DESC";
$query .=" LIMIT ?, ?";

$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);

// bind $id to SQL Statement
mysqli_stmt_bind_param($q, "ii", $start, $pagerows);

// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);

if ($result) {

名为 classpayed的两列被添加到选择查询中。这些行再次按注册日期排序,但现在它们将按降序排列(DESC),因为我们假想的管理员更喜欢在表的顶部查看最新的注册。

if ($result) { //                                                                    #2
// If it ran OK (records were returned), display the records.
// Table header
echo '<table class="table table-striped">
<tr>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
<th scope="col">Last Name</th>
<th scope="col">First Name</th>
<th scope="col">Email</th>
<th scope="col">Date Registered</th>
<th scope="col">Class</th>
<th scope="col">Paid</th>
</tr>';

如果没有遇到问题,则显示包括额外两项的表标题。

// reduce the chance of XSS exploits                                                      #3
               $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
               $last_name = htmlspecialchars($row['last_name'], ENT_QUOTES);
               $first_name = htmlspecialchars($row['first_name'], ENT_QUOTES);
               $email = htmlspecialchars($row['email'], ENT_QUOTES);
               $registration_date =
                       htmlspecialchars($row['regdat'], ENT_QUOTES);
               $class = htmlspecialchars($row['class'], ENT_QUOTES);
               $paid = htmlspecialchars($row['paid'], ENT_QUOTES);
               echo '<tr>
               <td><a href="edit_user.php?id=' . $user_id . '">Edit</a></td>
               <td><a href="delete_user.php?id=' . $user_id . '">Delete</a></td>
               <td>' . $last_name . '</td>
               <td>' . $first_name . '</td>
               <td>' . $email . '</td>
               <td>' . $registration_date . '</td>
               <td>' . $class . '</td>
               <td>' . $paid . '</td>
               </tr>';
        }
               echo '</table>'; // Close the table.

我们清理了两个新列,并将结果放入表的最后两列。

现在让我们更新搜索和编辑记录的能力。

搜索和编辑记录

搜索表单及其代码与第四章中的相同;因此,这里不再赘述。但是,当使用标题菜单上的 Search 按钮显示一条记录时,我们需要将 Class 和 Paid 列添加到屏幕上显示的表中。

图 5-7 显示了带有两个新列的搜索结果。

img/314857_2_En_5_Fig7_HTML.jpg

图 5-7

搜索结果现在显示一条记录,其中有两个额外的列,Class 和 Paid

为了给长的电子邮件地址、姓氏和两个新列腾出空间,我们删除了显示中的信息列。这里显示了 view_found_record.php 文件最后几行的部分清单:

<div class="col-sm-10">
  <h2 class="text-center">These are users found</h2>
<p>
<?php
    require ("process_view_found_record.php");
?>
</div>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
    style="padding-bottom:1px; padding-top:8px;">
  <?php include('footer.php'); ?>
</footer>
</div>
</body>
</html>

显示表格的引导列数从 8 增加到 10 (col-sm-10)。信息栏代码已被删除。

清单 5-7 展示了为表添加两个额外列的 PHP 代码的变化。

<?php
try
{
       // This script retrieves records from the users table.
       require ('./mysqli_connect.php'); // Connect to the db.
       echo '<p class="text-center">If no record is shown, ';
       echo 'this is because you had an incorrect ';
       echo ' or missing entry in the search form.';
       echo '<br>Click the back button on the browser and try again</p>';
       $first_name = htmlspecialchars($_POST['first_name'], ENT_QUOTES);
       $last_name = htmlspecialchars($_POST['last_name'], ENT_QUOTES);
       // Since it's a prepared statement below this sanitizing is not needed
       // However, to consistently retrieve than sanitize is a good habit

       $query = "SELECT last_name, first_name, email, ";
       $query .= "DATE_FORMAT(registration_date, '%M %d, %Y')";
       $query .=" AS regdat, class, paid, userid FROM users WHERE ";
       $query .= "last_name=? AND first_name=? ";
       $query .="ORDER BY registration_date ASC ";

       $q = mysqli_stmt_init($dbcon);
       mysqli_stmt_prepare($q, $query);

       // bind values to SQL Statement
       mysqli_stmt_bind_param($q, 'ss', $last_name, $first_name);

       // execute query
       mysqli_stmt_execute($q);

       $result = mysqli_stmt_get_result($q);

       if ($result) { // If it ran, display the records.
       // Table header.
       echo '<table class="table table-striped">
       <tr>
       <th scope="col">Edit</th>
       <th scope="col">Delete</th>
       <th scope="col">Last Name</th>
       <th scope="col">First Name</th>
       <th scope="col">Email</th>
       <th scope="col">Date Registered</th>
       <th scope="col">Class</th>
       <th scope="col">Paid</th>
       </tr>';
       // Fetch and display the records:
       while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
       // Remove special characters that might already be in table to
       // reduce the chance of XSS exploits
               $user_id = htmlspecialchars($row['userid'], ENT_QUOTES);
               $last_name = htmlspecialchars($row['last_name'], ENT_QUOTES);
               $first_name = htmlspecialchars($row['first_name'], ENT_QUOTES);
               $email = htmlspecialchars($row['email'], ENT_QUOTES);
               $registration_date =
                       htmlspecialchars($row['regdat'], ENT_QUOTES);
               $class = htmlspecialchars($row['class'], ENT_QUOTES);
               $paid = htmlspecialchars($row['paid'], ENT_QUOTES);
               echo '<tr>
               <td><a href="edit_user.php?id=' . $user_id . '">Edit</a></td>
               <td><a href="delete_user.php?id=' . $user_id . '">Delete</a></td>
               <td>' . $last_name . '</td>
               <td>' . $first_name . '</td>
               <td>' . $email . '</td>
               <td>' . $registration_date . '</td>
               <td>' . $class . '</td>
               <td>' . $paid . '</td>
               </tr>';
       }
       echo '</table>'; // Close the table.
       //
       mysqli_free_result ($result); // Free up the resources.
} else { // If it did not run OK.
       // Public message:
       echo '<p class="center-text">The current users could not ';
       echo 'be retrieved.';
       echo 'We apologize for any inconvenience.</p>';
       // Debugging message:
       //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
       //Show $q is debug mode only
} // End of if ($result). Now display the total number of records/members.
mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e)
{
       print "The system is currently busy. Please try later.";
       //print "An Exception occurred.Message: " . $e->getMessage();
}catch(Error $e)
{
       print "The system us busy. Please try later.";
       //print "An Error occurred. Message: " . $e->getMessage();
}
?>
</html>

Listing 5-7Creating Two Extra Columns

in the Table Display (process_view_found_record.php)

代码的解释

对代码的修改与我们之前讨论过的清单中的相同,所以我们在这里不再解释。

修改表单以编辑记录

用于编辑记录的表单现在将包含两个额外的字段,Membership Class 和 Paid。这使会员秘书能够查看会员选择的会员类别。在处理会员的 PayPal 付款时,他们也可以在“已付款”字段中输入“是”。这些字段如图 5-8 所示。

img/314857_2_En_5_Fig8_HTML.jpg

图 5-8

编辑记录的屏幕

所有五个字段都可以编辑。单击 Edit 按钮时,将显示修改后的数据,同时显示一条消息,说明记录已成功编辑。编辑地址或电话号码将在第六章中介绍。清单 5-8 显示了编辑记录的代码。

<?php
try
{
// After clicking the Edit link in the found_record.php page. This code is executed
// The code looks for a valid user ID, either through GET or POST:
if ( (isset($_GET['id'])) && (is_numeric($_GET['id'])) ) {
// From view_users.php
    $id = htmlspecialchars($_GET['id'], ENT_QUOTES);
} elseif ( (isset($_POST['id'])) && (is_numeric($_POST['id'])) ) {
// Form submission.
       $id = htmlspecialchars($_POST['id'], ENT_QUOTES);
} else { // No valid ID, kill the script.
       echo '<p class="text-center">
               This page has been accessed in error.</p>';
       include ('footer.php');
       exit();
}

require ('./mysqli_connect.php');
// Has the form been submitted?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
       $errors = array();
       // Look for the first name:
       $first_name =
               filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
       if (empty($first_name)) {
               $errors[] = 'You forgot to enter your first name.';
       }
       // Look for the last name:
       $last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
       if (empty($last_name)) {
               $errors[] = 'You forgot to enter your last name.';
       }
       // Look for the email address:
       $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
       if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
               $errors[] = 'You forgot to enter your email address';
               $errors[] = ' or the e-mail format is incorrect.';
       }
       // Look for the class:                                                           #1
       $class = filter_var( $_POST['class'], FILTER_SANITIZE_NUMBER_INT);
       if (empty($class)) {
               $errors[] = 'You forgot to the class or it is not numeric.';
       }
       // Look for the Paid Status:                                                     #2
       $paid = filter_var( $_POST['paid'], FILTER_SANITIZE_STRING);
       if (empty($paid)) {
               $errors[] = 'You forgot to enter the paid status.';
       }
       if (!(($paid == "No") || ($paid == "Yes"))) {
               $errors[] = "Paid must be No or Yes.";
       }
       if (empty($errors)) { // If everything's OK.
               $q = mysqli_stmt_init($dbcon);
               $query = 'SELECT userid FROM users WHERE email=? AND userid !=?';
               mysqli_stmt_prepare($q, $query);

               // bind $id to SQL Statement
               mysqli_stmt_bind_param($q, 'si', $email, $id);

       // execute query
       mysqli_stmt_execute($q);
               $result = mysqli_stmt_get_result($q);

               if (mysqli_num_rows($result) == 0) {
               // e-mail does not exist in another record
               $query = 'UPDATE users SET first_name=?, last_name=?, email=?,';
               $query .= ' class=?, paid=?';
               $query .= ' WHERE userid=? LIMIT 1';
               $q = mysqli_stmt_init($dbcon);
               mysqli_stmt_prepare($q, $query);

               // bind values to SQL Statement
mysqli_stmt_bind_param($q, 'sssssi', $first_name, $last_name, $email, $class, $paid, $id);
       // execute query
       mysqli_stmt_execute($q);

               if (mysqli_stmt_affected_rows($q) == 1) { // Update OK
               // Echo a message if the edit was satisfactory:
                       echo '<h3 class="text-center">
                              The user has been edited.</h3>';
               } else { // Echo a message if the query failed.
                       echo '<p class="text-center">
                       The user could not be edited due to a system error.';
                       echo ' We apologize for any inconvenience.</p>';
                       // Public message.
       //echo '<p>' . mysqli_error($dbcon) . '<br />Query: ' . $q . '</p>';
       // Debugging message.
       // Message above is only for debug and should not in live mode
               }
       } else { // Already registered.
               echo '<p class="text-center">
                       The email address has already been registered.</p>';
               }
       } else { // Display the errors.
               echo '<p class="text-center">
                       The following error(s) occurred:<br />';
               foreach ($errors as $msg) { // Echo each error.
                       echo " - $msg<br />\n";
               }
               echo '</p><p>Please try again.</p>';
 } // End of if (empty($errors))section.
} // End of the conditionals
// Select the user's information to display in textboxes:

       $q = mysqli_stmt_init($dbcon);
       $query =
"SELECT first_name, last_name, email, class, paid FROM users WHERE userid=?";
       mysqli_stmt_prepare($q, $query);
       // bind $id to SQL Statement
       mysqli_stmt_bind_param($q, 'i', $id);
       // execute query
       mysqli_stmt_execute($q);
       $result = mysqli_stmt_get_result($q);
       $row = mysqli_fetch_array($result, MYSQLI_NUM);
       if (mysqli_num_rows($result) == 1) {
       // Valid user ID, display the form.
       // Get the user's information:
       // Create the form:
?>
<h2 class="h2 text-center">Edit a Record</h2>
<form action="edit_user.php" method="post"
       name="editform" id="editform">
<div class="form-group row">
       <label for="first_name" class="col-sm-4 col-form-label">
              First Name:</label>
<div class="col-sm-8">
       <input type="text" class="form-control" id="first_name"
       name="first_name" placeholder="First Name" maxlength="30" required
       value="<?php echo htmlspecialchars($row[0], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
       <label for="last_name" class="col-sm-4 col-form-label">
              Last Name:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="last_name"
       name="last_name" placeholder="Last Name" maxlength="40" required
       value="<?php echo htmlspecialchars($row[1], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row">
        <label for="email" class="col-sm-4 col-form-label">
        E-mail:</label>
<div class="col-sm-8">
      <input type="email" class="form-control" id="email"
        name="email" placeholder="E-mail" maxlength="60" required
        value="<?php echo htmlspecialchars($row[2], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row">
    <label for="class" class="col-sm-4 col-form-label">
        Membership Class:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="class"
        name="class" placeholder="Membership Class" maxlength="60" required
        value="<?php echo htmlspecialchars($row[3], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row">
        <label for="paid" class="col-sm-4 col-form-label">Paid:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="paid"
        name="paid" placeholder="Paid" maxlength="60" required
        value="<?php echo htmlspecialchars($row[4], ENT_QUOTES); ?>">
</div>
</div>
<input type="hidden" name="id" value="<?php echo $id ?>" />
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
        <input id="submit" class="btn btn-primary" type="submit"
        name="submit" value="Register">
</div>
</div>
</form>
<?php
} else { // The user could not be validated
        echo '<p class="text-center">
               This page has been accessed in error.</p>';
}
mysqli_stmt_free_result($q);
mysqli_close($dbcon);
}
catch(Exception $e)
{
        print "The system is busy. Please try later";
        //print "An Exception occurred.Message: " . $e->getMessage();
}catch(Error $e)
{
        print "The system is currently busy. Please try again later";
        //print "An Error occurred. Message: " . $e->getMessage();
}
?>
// Look for the class:                                                                    #1
        $class = filter_var( $_POST['class'], FILTER_SANITIZE_NUMBER_INT);
        if (empty($class)) {
               $errors[] = 'You forgot to the class or it is not numeric.';
        }
The integer parameter of the filter_var method is used to make sure the class is numeric.

        // Look for the Paid Status:                                                                                   #2
        $paid = filter_var( $_POST['paid'], FILTER_SANITIZE_STRING);
        if (empty($paid)) {
               $errors[] = 'You forgot to enter the paid status.';
        }
        if (!(($paid == "No") || ($paid == "Yes"))){
               $errors[] = "Paid must be No or Yes.";
        }

Listing 5-8The Code for Creating an Editing Screen

to Include the Two Additional Fields (process_edit_record.php)

一个额外的检查是用来确保支付是否或是。其他值会导致错误,因为我们将数据库中的列设置为只接受这两个值。

剩下的代码前面都已经看到解释过了;因此,无需进一步解释。在插入新内容的地方,清单中的注释给出了完整的解释。

注意删除记录的屏幕与第四章相同,为节省空间,此处不再重复。然而,文件 delete_record.php 包含在章节 5 的下载中。

我们需要限制向管理员显示的信息量。要显示成员的全部详细信息,需要一个非常宽且混乱的表。因此,成员的详细信息将被分割并使用两个单独的页面显示。通过search-address.php页面,管理员可以独立于其他成员信息查看地址。新地址按钮被添加到菜单中以访问此页面。然而,我们将在下一章为这个页面创建代码。

注意

第章第五章下载的文件包含了这些演示所需的所有文件,包括第章第四章未更改的文件。

摘要

在这一章中,我们创建了一个数据库,它包含了比前面章节更多的字段。为了安全起见,我们在所有管理页面中添加了会话。我们在管理员的页面标题中添加了一个新的地址按钮。我们学习了如何在页面上添加 PayPal 标志和按钮。我们讨论了如何获得必要的代码链接到贝宝系统接受付款。成员的表格显示演示了分页。修改了编辑记录的表单,以包含额外的数据。创建了第二个表来保存会员资格的当前价格信息。此信息显示在课程定价和下拉列表中,供用户选择课程。

在下一章中,您将了解管理员如何查看和编辑地址和电话号码。此外,在下一章,我们将添加数据库的收尾工作。通过将包含的文件放在包含文件夹中来整理归档系统。为了增加安全性,将引入额外的用户输入过滤器。标题列将被添加到表格显示中,用于编辑和删除记录,标题列也将被添加到表格显示中,用于编辑地址和电话号码。*

六、添加收尾工作:安全性和验证

在前一章中,我们创建了一个有用的数据库和几个交互式网站页面的开端。但是,管理员无法编辑所有成员信息。在本章中,我们将提供编辑地址和电话号码信息的能力。这将通过使用地址菜单按钮来实现。此外,我们将向数据库添加一个额外的标题字段。该字段也可通过地址菜单按钮进行编辑。PHP 页面的文件夹和文件系统将被整理以减少混乱。更多信息将放在文件夹中,以便于查找和检索。以前的交互式页面只有最少的用户输入验证和清理。本章将介绍一个更安全的系统。

完成本章后,您将能够

  • 从现有数据库及其关联表生成 SQL 脚本

  • 将一个 SQL 脚本导入 phpMyAdmin,以生成一个新的数据库和相关的表

  • 向现有数据库表中添加附加字段

  • 创建一个有组织的文件夹结构来存储相关的文件和网页

  • 为访问和存储数据库信息提供更高的安全性

  • 在用户输入进入数据库之前对其进行验证和清理

  • 为标题、地址和电话号码提供额外的搜索功能

  • 提供查看和编辑职务、地址和电话号码的附加功能

创建数据库

首先,我们将创建一个新版本的 postaldb 数据库,并将其重命名为 finalpost

  1. 打开 XAMPP htdocs 或 easyPHP eds-www 文件夹,新建一个名为 finalpost 的文件夹。

  2. 从本书的网站 www.apress.com 下载第六章的文件,并将文件放在名为 finalpost 的新文件夹中。

  3. 然后使用浏览器访问 phpMyAdmin,点击 Database 选项卡,创建一个名为 finalpost 的新数据库。

  4. 向下滚动并找到新创建的数据库。然后选中旁边的复选框。

  5. 单击检查权限,单击添加用户,然后添加新用户和密码,如下所示:

    • 用户名:白菜

    • 密码 : in4aPin4aL

    • 主机:本地主机

    向下滚动;在全局权限旁边,单击全部选中。

  6. 接受资源限制的默认值。

  7. 滚动到底部,然后单击“Go”保存用户的详细信息。

创建用于连接到数据库的文件

创建一个名为 mysqli_connect.php 的新连接文件。(如果您不需要从头开始练习创建该文件,它已经包含在第六章的下载文件中。)

<?php
// Create a connection to the postaldb database
// Set the database access details as constants and set the encoding to utf-8

Define ('DB_USER', 'cabbage');
Define ('DB_PASSWORD', 'in4aPin4aL');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'finalpost');
// Make the connection
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
mysqli_set_charset($dbcon, 'utf8'); // Set the encoding...
Test your connection in a browser (http://localhost/finalpost/mysqli_connect.php). Remember, if you do not see anything on the screen, your connection was successful.

Listing 6-1The Database Connection File (mysqli_connect.php)

小费

当设计一个在现实世界中使用的密码时,你应该使它尽可能的复杂。确保它不以任何方式与数据库名称相关。复杂的密码可以通过助记符很容易地记住。然后可以把助记符写下来,锁起来。本教程中为密码(in4aPin4aL)选择的助记符是一句古老的英国谚语,“一便士一英镑。”这句格言的意思之一是,“如果我们开始这个,我们必须确保我们完成它;没有回头路。”在密码本身中,大写的 P 代表便士,大写的 L 是一英镑(100 便士)的古老符号。

通过导入 SQL 转储文件创建用户表

我们现在需要创建一个名为 users 的 17 列表。

我们可以听到你在呻吟,“哦,不!不是另一张桌子!”不要担心,我们将向您展示使用 phpMyAdmin 中的导入/导出工具的快捷方式。

已经为您创建了生成的代码,并将其导出到名为 users.sql 的文件中。这个文件实际上包含了创建用户和价格表的代码。你会在你安装在文件夹 finalpost 的下载文件中找到这个。

然而,您也可以从第五章的当前 postaldb 数据库中创建 users.sql 文件。如果你想从一个不受你在第 5 章中所做的任何事情影响的数据库和表格开始,你可以忽略下面的说明并跳到导入说明。

要创建要导入的文件,请按照下列步骤操作:

  1. 打开 phpMyAdmin。从屏幕左侧的列表中选择 postaldb 数据库。

  2. 然后选择页面顶部的导出选项卡。为模板创建一个名称,例如 users ,并将其输入到新模板文本框中。

  3. 单击文本框右侧的“创建”按钮。新模板名 users 现在应该出现在右边的 existing templates 下拉框中。保持选择快速导出方法。还要确保 SQL 显示在格式下拉框列表中。

  4. 如果一切正常,单击 Go 按钮。系统将下载一个包含 SQL 代码的文件,该文件不仅会为您创建表格,还会将您现有的数据从章节 5 数据库复制到章节 6 的新数据库中。

  5. 在 Notepad++或类似的编辑器中打开下载的文件,逐行显示代码。检查代码,看看能否确定哪些行创建了表,哪些行向表中添加了数据。

  6. 从文件菜单中选择另存为,将文件另存为 users.sql.

要从第六章的教材文件中导入 users.sql 文件,或者导入您通过导出第五章的表格而创建的文件,请完成以下说明:

img/314857_2_En_6_Fig1_HTML.jpg

图 6-1

phpMyAdmin 中的导入屏幕

  1. 在 phpMyAdmin 的主页中,单击数据库 finalpost。

  2. 单击导入选项卡。

  3. 点击导入界面上的浏览按钮,如图 6-1 所示。

浏览按钮允许您导航到文件 users.sql 。打开 users.sql 文件。

  1. 然后单击 Go,文件将被导入。

  2. 现在在 phpMyAdmin 中打开 users 表(单击 browse 选项卡),您将能够查看该表及其数据。

或者,在选择数据库后,可以使用 Operations 选项卡将表从一个数据库复制到另一个数据库。表 6-1 显示了导入用户表的结构。

表 6-1

第六章导入用户表的标题和属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 使用者辩证码 | 中位 | six | 没有人 | 无符号的 | □ | 主要的 | ·······················。 |
| 标题 | 可变长字符串 | Ten | 没有人 |   | ·······················。 |   | □ |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | □ |   | □ |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | □ |   | □ |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | □ |   | □ |
| 密码 | 茶 | Sixty | 没有人 |   | □ |   | □ |
| 注册日期 | DATETIME |   | 没有人 |   | □ |   | □ |
| 用户级别 | 蒂尼因特 | one | 没有人 | 无符号的 | □ |   | □ |
| 班级 | 茶 | Twenty | 没有人 |   | □ |   | □ |
| 地址 1 | 可变长字符串 | Fifty | 没有人 |   | □ |   | □ |
| 城市 | 可变长字符串 | Fifty | 没有人 |   | □ |   | □ |
| 州 _ 国家 | 茶 | Twenty-five | 没有人 |   | □ |   | □ |
| zcode _ pcode 函数 | 茶 | Ten | 没有人 |   | □ |   | □ |
| 电话 | 茶 | Fifteen | 没有人 |   | ·······················。 |   | □ |
| 秘密 | 可变长字符串 | Thirty | 没有人 |   | □ |   | □ |
| 有报酬的 | ENUM | “是”,“不是” | 没有人 |   | □ |   | □ |

如果您从教科书文件中导入了 users.sql 文件,该结构还将包括一个标题和一个秘密字段。这些字段在第五章的数据库中不存在。我们将很快解释如果您从上一章导入了自己的数据,如何添加这些列。另外,请注意,标题、第二个地址和电话号码列的空框已被选中。这是因为它们是用户可以忽略的可选字段。许多用户可能没有额外的地址,例如公寓号码。有些用户不在电话簿上,不想公开他们的电话号码。

手动注册一些成员

如果没有从“用户”表中导入现有数据(用户),则需要手动输入。要注册会员,运行 XAMPP 或 EasyPHP 并使用浏览器输入以下 URL:

http://localhost/final post/safer-register-page . PHP

文件safer-register-page.php是第五章中register-page.php文件的更安全版本。该文件包含在第六章的可下载文件中。本章稍后将介绍更安全文件的增强功能。

如第五章所述,詹姆斯·史密斯首先注册为会员,然后以别名杰克·史密斯注册为会员秘书(管理员)。提醒一下,他们的详细情况如下:

  • 名字:詹姆斯

  • 姓氏:史密斯

  • 电子邮件:jsmith@myisp.co.uk

  • 密码:Bl @ cks m1

  • 名字:杰克

  • 姓氏:史密斯

  • 电子邮件:jsmith@outcook.com

  • 密码 : D@gsb0dy

将杰克·史密斯的用户级别设置为 1。通过使用 phpMyAdmin,两个 Smiths 都应该将他们的 paid 列设置为 Yes。您可以选择为两个用户添加标题和机密答案。

小费

当添加新成员时,使用 phpMyAdmin 将 Yes 放入成员的 Paid 列。为此,单击左侧面板中 finalpost 旁边的框将其展开,然后单击 users 项。你会看到注册会员。在用户记录中,单击编辑。在下一个屏幕中,向下滚动并单击右下角的 Yes 单选按钮。然后单击开始。

除非会员秘书(管理员)将会员的付费栏标记为是,否则会员无法登录。

为了便于您访问会员数据,可下载会员的详细信息如表 6-2 所示。这与第五章中的数据相同。数据已经从导入的 users.sql 文件放入表中。本章假设数据如下所示。如果您导入了自己的数据,您可能希望进行一些手动调整来反映这些信息。您不需要再次手动输入信息。

表 6-2

可下载注册会员的详细信息

|

标题

|

西方人名的第一个字

|

|

电子邮件

|

密码

|
| --- | --- | --- | --- | --- |
| 先生 | 迈克 | 这是什么 | 米克尔@myisp.com | willg @ ?? |
| 女士 | 橄榄 | 树枝 | obranch@myisp.co.uk | S7-1200 可编程控制器 |
| 先生 | 弗兰克 | 香 | fincense@myisp.net | p @ RFM 3s |
| 女士 | 安妮 | 周年纪念 | aversary@myisp.com | 页:1 |
| 先生 | 泰丽 | Fide | tfide @ my ISP . com | S7-1200 可编程控制器 |
| 夫人 | 玫瑰 | 矮树丛 | rbush@myisp.co.uk | R@db100ms |
| 夫人 | 安妮 | 苔藓 | amositty@myisp.org.uk | S7-1200 可编程控制器 |
| 先生 | 皮尔斯!皮尔斯 | 转向 | pver @ my ISP . com | K33pg0!尼日利亚 |
| 先生 | 戴劳 | 豆儿 | ddoo@myisp.co.uk | Sat!sf1ed |
| 先生 | 斯坦 | 达尔德族人 | sdad @ my ISP . net | b@TTl3fl@g |
| 夫人 | Honora | 骨 | nbone@myisp.com | L1k@t@rr13r |
| 先生 | 巴里 | 凯德 | bcade@myisp.co.uk | Bl0ckth@m |

小费

请记住,如前一章所述,使用手动方法输入数据可能会很繁琐。为了减少繁琐,您可以为不同的用户输入一些相同的地址、电话号码和机密答案。接下来建议一组合适的数据。

在第五章中,我们在每个记录中输入了以下信息,以节省时间和精力。以下是一些建议的默认条目:

| 地址 1 | 2 街道 | | 地址 2 | 村庄(这是可选的,在注册一些成员的地址时应该省略) | | 城市 | 汤斯维尔 | | 州或国家 | 英国(或美国) | | 邮政编码 | EX7 9PP(或在美国,33040) | | 电话 | 01234 777 888(或者在美国,999 888 4444;这是可选的,在注册一些成员的电话号码时应该省略) |

记住,在 phpMyAdmin 中,将几个成员的 paid 列更改为 Yes。只有付费栏设置为“是”的会员才能登录系统。

向用户表中添加标题列

如果您从第五章导入了自己的数据,则表格不包括标题和秘密栏。如果您确实使用了章节 6 教材文件中的 users.sql 文件,那么这些列已经创建好了,您可以跳过这一节。

让我们添加一个标题列,允许成员可选地提供一个首选的标题(如先生、夫人、女士、Mx。,或博士)。

  1. 在 phpMyAdmin 中,在屏幕左侧的列表中选择 finalpost 数据库。

  2. 然后选择数据库中的 users 表。

  3. 选择页面顶部的“结构”选项卡。现在将出现图 6-2 所示的屏幕。

    img/314857_2_En_6_Fig2_HTML.jpg

    图 6-2

    phpMyAdmin 中的结构屏幕

  4. 找到当前列列表下方的添加列信息。

  5. 更改设置以指示将在 user_id 列后添加 1 列,然后单击 Go 按钮。

  6. 为新标题列输入以下设置。

    |

    列名

    |

    类型

    |

    长度/值

    |

    默认

    |

    属性

    |

    |

    索引

    |

    A_I

    |
    | --- | --- | --- | --- | --- | --- | --- | --- |
    | 标题 | 可变长字符串 | Ten | 没有人 |   | ·······················。 |   | □ |

    请注意,新列将允许空值。这将使标题的输入成为可选的。

  7. 输入新设置后,单击保存按钮。新列现在将出现在 userid 和 first_name 列之间的表结构中。

  8. 现在单击 browse 选项卡查看表中的当前数据。通过单击数据左侧的编辑链接,随机选择一些用户,输入标题,然后保存更改。当显示成员的搜索结果时,您需要在表中显示一些标题。

  9. 重复这些步骤,使用以下设置添加机密列。

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 秘密 | 可变长字符串 | Thirty | 没有人 |   | □ |   | □ |

通过导入 SQL 转储文件创建价格表

如果您从章节 6 教材文件中导入了 users.sql 文件,它已经导入了价格表,所以您可以跳过这一节。我们现在需要重复导入过程,将第五章中的价格表复制到 finalpost 数据库中。

  1. 在 phpMyAdmin 的主页中,单击数据库 finalpost。

  2. 单击导入选项卡。

  3. 点击导入屏幕上的浏览按钮(参见图 6-1 )。浏览按钮允许您导航到文件 prices.sql

  4. 打开 prices.sql 文件,然后点击 Go。文件将被导入。

或者,在选择数据库后,可以使用 Operations 选项卡将表从一个数据库复制到另一个数据库。如果您现在在 phpMyAdmin 中打开 prices 表(单击 browse 选项卡),您将能够查看该表及其数据。

表 6-3 显示了价格表的结构。

表 6-3

价格表的标题和属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

阿奇

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 一年 b | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| 一岁 | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| 五年 b | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| 五年 | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| 军队 | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| 美国军方 | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| u21gb | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| u21us | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| 最低价格 b | 小数 | six | 没有人 | 无符号的 | □ |   | □ |
| minpriceus | 小数 | six | 没有人 | 无符号的 | □ |   | □ |

该表现在还应该包括在第五章中输入的数据。您也可以从章节 5 数据库中导出和导入您自己的数据。

在我们继续之前,让我们转移一下话题,做些家务。如果我们不提供更好的组织,PHP 和 HTML 文件数量的不断增加会成为一个问题。

整理文件夹和文件系统

随着每一章增加了更多的功能,邮政文件夹中的文件数量也增加了。这种混乱会让人迷惑。为了整理新的 finalpost 文件夹,我们将把所有包含的文件放在它们自己的 include 文件夹中。这已经在本章的可下载文件中完成了。注意,使用 include 语句插入 PHP 程序的所有文件现在都在新的 includes 文件夹中。在主文件中,所有 PHP 包含文件现在都有前缀 includes/,如下面的代码片段所示:

<?php
        include("includes/header-admin.php");
        include("includes/nav.php");
        include("includes/info-col.php");
?>

我们现在将为数据提供一些额外的验证和清理。安全性是开发过程中最重要的方面,也可能是最乏味的。用户的不当输入可能会将错误或损坏的数据插入到数据库表中。更严重的是,它会导致私人信息的泄露,或者让邪恶的人破坏桌子。它还可以让人们控制服务器和服务器上的任何数据。不可接受的输入或控制可能是犯罪分子所为,也可能只是用户的错误造成的。因此,必须过滤所有用户输入,以确保它适合您的数据库。

安全等级

数据库可能是不安全的或部分安全的;它永远不可能百分百安全。但是,必须对该语句进行限定,因为除了数据库的内置安全性之外,风险还取决于其他几个因素。邮政地址、电子邮件地址、信用卡信息和用户偏好都可以从不安全的数据库中获取,并以高价出售。

小型公司、俱乐部或社团的数据库仍然必须提供一层安全性。管理员有法律责任尽可能保证数据的安全。即使黑客没有攻击数据库,用户也可能无意中输入无效信息。请记住,在将数据输入数据库之前,过滤掉任何潜在的问题要容易得多。一旦数据库中存在坏数据,就很难删除它。

增加一层安全性

数据库必须具有唯一的名称(最长 16 个字符)、密码(尽可能复杂)、主机名(如 localhost)、指定用户和一组权限。

允许多个用户,并且应该限制他们的权限。为了最大程度的安全,必须限制数据库的用户 id 数量。应该只向用户提供完成其工作(任务)所需的权限。最近的新闻报道中有许多这样的例子:员工被授予了过多的权限,从而将用户的个人信息暴露给了犯罪分子或其他网站。大多数用户不应该拥有诸如 GRANT、SHUTDOWN 或 DROP 等权限。

我们在前面章节中演示的 PHP 代码在将密码存储在服务器上之前对其进行哈希处理。此外,网站管理员应该选择一个难以推断的密码。永远不要使用可以在字典中找到的单词或者是网站管理员名字的变位词。你可以用一个流行的说法,比如“又下雨了,鸭子的好天气”,然后创建一个类似 RaGw4dux 的密码。

总是假设用户输入的所有内容对数据库都是危险的。此外,数据应尽可能准确。人的年龄不是数据库的好条目。人的年龄随着时间而变化。数据很快就会失效。一个更好的条目是这个人的出生日期。这一点不会改变。这个人的年龄可以从他的出生日期计算出来,而且总是准确的。

此外,格式不正确的电子邮件地址对用户或数据库所有者都没有用。应该提供代码以确保电子邮件的格式正确。有些系统甚至会对输入的电子邮件进行验证,以确保不仅格式正确,而且是一封真实的电子邮件。

我们在示例代码中使用准备好的 SQL 语句。这确保了不会有用户数据被执行并对数据库造成损害。但是,我们目前不会从用户条目中删除任何无效或不必要的字符。我们希望尽可能避免无效或损坏的数据进入数据库。

我们应该始终清理任何输入字符串,以消除可执行代码进入数据库的可能性。预处理语句也会阻止数据的执行。

验证电子邮件地址、电话号码、所有数字项以及任何其他具有标准格式的项(如邮政编码)。任何过于笼统而无法验证的东西至少应该被清理掉。这两种技术验证净化将在本章稍后解释。

在将所有用户输入显示到页面或插入数据库之前,对其进行清理。脚本可以结合使用 HTML 标签(如

在实时网站中,尽可能将访问数据库的文件存储在比根文件夹高一级的文件夹中。(根文件夹是 htdocs 或者 eds- www 。)您可能需要联系您的远程主机提供商,看看这是否可行。将该文件移到 htdocseds-www 之外的文件夹会使其更难访问。将此文件留在根文件夹中会允许个人或程序访问数据库。我们一直使用的连接代码如下:

require ('mysqli_connect.php');

当使用比根级别高一级的文件夹中的文件时,连接代码如下:

require ('../mysqli_connect.php');

注意

本教程的可下载 PHP 文件将保留所需的代码(' mysqli _ connect . PHP ');因为数据库将位于您的计算机上,而不是远程主机上。将文件上载到生产服务器时,您应该将该文件的位置(并更改所有类似的语句)移动到更安全的文件夹中。如果您不保护此文件,将会留下很大的安全隐患。

尽可能防止浏览器显示泄露数据库结构的错误信息。在示例代码中,代码的 catch 块捕获并显示错误信息。在现实世界中,catch 块将错误消息存储在用户无法访问的错误日志中。还可以向网站管理员发送电子邮件,通知他们发生了错误。然后可以查看日志来确定问题。将向用户提供一条通用消息,要求他们稍后返回该站点。第七章将提供一个记录错误信息的例子。

正如我们在前面的章节中所展示的,总是使用会话来限制对易受攻击页面的访问,例如管理页面和成员的特殊页面。您还可以考虑提供不同的访问级别,比如用户级别、成员主席级别和网站管理员级别。这可以通过将 user_level 设置为不同的值(如 1、2、3)来实现,然后在每个安全页面顶部的会话代码中检查这些值中的一个(或多个)。

您可以使用 PayPal、Stripe 或类似的安全支付系统通过网站安全地接受付款。但是从不存储会员的银行/借记卡/信用卡详细信息。此外,不要存储政府识别号码(如社会安全号码)。请记住,访问这种类型的信息会为黑客提供破坏个人信用、窃取退税和窃取退休支票的能力。如果您需要一个惟一的标识符,使用 autonum 生成一个用户 ID,就像我们前面展示的那样。

增加了一层保护

如果您的客户希望使用内部支付系统,而不是使用 PayPal 或类似的第三方公司,他们将需要更昂贵的安全预算。对于安全性很重要或存储用户财务信息的网站,网站需要专业人员或第三方来监督和持续监控网站。这可能既费钱又费时。

每当安全信息必须从客户端传输到服务器时,安全套接字层(SSL)是必需的。SSL 将为信息流动提供加密通道。包含 SSL 的站点使用 HTTPS 协议,而不是 HTTP 协议。许多浏览器还提供一个锁图标,表示通信信道是由 SSL 加密的。一些主机,如脸书,现在要求所有托管的应用都使用 SSL。客户可能需要支付更高的年费来使用安全套接字层。

验证和消毒

让我们演示一下验证和清理之间的区别。

  • 验证:验证器检查用户输入的格式。如果格式不正确,就不会提交表单,并且会出现一条错误消息,要求用户更正输入。例如,验证确保电子邮件地址的格式正确(即用户名后跟@符号,后跟 ISP 地址)。这可以防止不正确的数据被输入到数据库表中。

  • Sanitizing :这可以通过两种方法实现:使用标准的 PHP 清理器或者使用带有 SANITIZE 属性的 filter_var()函数。在数据发送到数据库表或显示在页面上之前,清理会自动删除不需要的字符。使用 SANITIZE 属性时,不会显示任何错误消息。用户不会被提醒他们犯了错误或者他们的输入包含危险的字符或脚本。这可能不是所希望的,因为它没有给用户提供纠正他们已经输入的内容的机会。清理将删除或停用可能损害数据库的 HTML 标记和 JavaScript 命令。

可以通过 filter_var()函数来应用验证和净化。

filter_var()函数

函数 filter_var()包含在 PHP 5.2 版和更高版本中。其目的是简化用户输入的验证和净化。该函数与 FILTER_VALIDATE 或 FILTER_SANITIZE 属性结合使用。有关此功能的更多详细信息,请参见 http://php.net/manual/en/function.filter-var.php

确认

在第五章中,使用 HTML 5 在注册页面中提供了一些验证。但是,用户仍然可以输入无效的信息。此外,即使信息已经在客户机上使用 HTML 5 或 JavaScript 进行了验证,它在从客户机传输到服务器的过程中仍然可能被破坏。服务器端接收的数据可能会损坏网页或服务器,或者干扰数据库。用户也可能输入了格式错误的项目。作为错误格式的一个例子,用户可能在输入电子邮件地址时出错。错误的地址可能包含空格或不可接受的字符,如下所示:

rbush 'myisp.com

这个错误的电子邮件地址不包含@符号,它有两个空格和一个撇号。这些是不可接受的电子邮件字符。验证过程将识别问题,并提供一条错误消息,要求用户更正电子邮件地址。

在第五章中,在 register-page.php 的文件中,电子邮件的代码如下:

// Check for an email address:
            $email = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if  ((empty($email)) || (!filter_var($email, FILTER_VALIDATE_EMAIL))) {
                $errors[] = 'You forgot to enter your email address';
                $errors[] = ' or the e-mail format is incorrect.';
        }

这段代码清理输入,检查字段是否包含一些字符,并验证电子邮件的格式。这避免了电子邮件格式完全没有意义;这些废话会被输入到数据库表中。一些程序员使用 regex ( regex 是“正则表达式”的缩写)来验证电子邮件地址的格式,但是随着 PHP 5.2 和更高版本的出现,filter_var()函数变得可用并且更容易使用。通过正则表达式进行验证是相当复杂的,并且容易出现输入错误,正如您将从下面的例子中看到的:

$email = "name@usersisp.com";

if (preg_match('/^[⁰-9][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[@][a-zA-Z0-9_]+([.]

[a-zA-Z0-9_]+)*[.][a-zA-Z]{2,4}$/',$email)) {
echo "Your email is in the acceptable format";
} else {
echo "Your email address does not have an acceptable format";
}

通过 filter_var()函数进行验证是一个很大的改进。此代码用于更安全的注册码( process_register_page.php)。

一些文本输入没有遵循标准格式。让我们看看名字。在第五章中,我们使用以下代码检查了名字的内容:

// Check for a first name:
$first_name = filter_var( $_POST['first_name'],
         FILTER_SANITIZE_STRING);
if (empty($first_name)) {
         $errors[] = 'You forgot to enter your first name.';
}

在本例中,字符串 first_name 被清除;但是,在将数据输入数据库之前,没有提供任何验证来尝试收集良好的数据。

在这一章中,我们提供了一些额外的验证。

// Trim the first name
   $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
if ((!empty($first_name)) && (preg_match('/[a-z\s]/i',$first_name)) &&
                   (strlen($first_name) <= 30)) {
           //Sanitize the trimmed first name
    $first_nametrim = $first_name;
           }else{
           $errors[] =
           'First name missing or not alphabetic and space characters. Max 30';
           }

除了检查$first_name 是否为空之外,我们现在还检查字符串中有限的一组字符,以及该字符串是否小于或等于 30 个字符。字符长度匹配数据库表中列的长度和 HTML 文件中设置的长度(safer-register-page.php)。

因为不存在用于验证名字的 filter_var 函数参数,所以我们必须开发自己的参数。

preg_match('/[a-z\s]/i',$first_name)

preg_match 函数将字符串(first _ name)与正则表达式('/[a-z\s]/i ')进行比较,如果字符串符合要求,则返回 True,否则返回 False。

在本例中,a-z 允许所有字母字符。\s 还表示允许使用空格。[]符号用于包含要比较的值。I 字符(以及包含其他字符的//)表示我们忽略了大小写。以下示例在不使用 I 的情况下提供了相同的功能:

preg_match('[a-zA-z\s]', $first_name)

我们很快会看到一些额外的正则表达式。然而,正如您所见,创建表达式时很容易出错。当现有功能满足您的需求时,使用它们会更安全。

小费

某些文本输入可能没有有效的格式,因为它不符合设定的模式,例如,用于注释的文本区域或某人的头衔。在英国,一些头衔是中世纪封建制度的遗留物,如男爵夫人、勋爵、夫人或先生等古怪的称呼。一些退役军人使用他们的武装部队头衔,如少校或上校。大学教师可能会使用 Professor 或 Prof。不符合设定模式的文本可以使用带有 regex 函数的正则表达式进行验证。

卫生处理

在我们更安全的注册页面中,我们将使用带有净化参数的 filter_var 函数来删除任何有害字符。

//Is the last name present? If it is, sanitize it
$last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
if ((!empty($last_name)) && (preg_match('/[a-z\-\s\']/i',$last_name)) &&
                (strlen($last_name) <= 40)) {
        //Sanitize the trimmed last name
        $last_nametrim = $last_name;
        }else{
        $errors[] =
'Last name missing or not alphabetic, dash, quote or space. Max 30.';
        }

正如我们在第五章中看到的,FILTER_SANITIZE_STRING 参数用于净化任何没有通用格式的字符串。有应该用于特定格式的净化过滤器(例如 FILTER_SANTITIZE_EMAIL)。电子邮件清理过滤器比尝试为所有电子邮件格式创建正则表达式容易得多。

验证电话号码

电话号码不符合通用格式。用户将输入数字、空格、连字符和括号的任意组合。还有国际差异。让事情变得复杂的是,一些用户会将数字分组如下:

0111 222 333 或(0111) 222 333 或 0111-222-333

一些程序员会尝试使用几个 regex 语句来验证多种格式的电话号码。这本书提供了一种简化的方法。最简洁的解决方案是去掉所有不是数字的字符。我们将使用 filter_var()函数,如下所示:

//Is the phone number present? If it is, sanitize it
$phone = filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
if ((!empty($phone)) && (strlen($phone) <= 30)) {
        //Sanitize the trimmed phone number
        $phonetrim = (filter_var($phone, FILTER_SANITIZE_NUMBER_INT));
    $phonetrim = preg_replace('/[⁰-9]/', ", $phonetrim);
        }else{
        $phonetrim = NULL;
        }

如果用户输入(01234) 777 888,那么在杀毒之后,它将作为 01234777888 输入数据库。preg_replace 函数将用第二个参数中的字符替换正则表达式中的任何字符。在本例中,^符号表示 NOT(就像!PHP 中的符号)。因此,这个表达式告诉我们用“(nothing)”替换任何不是数字的字符。这将清除所有非数字字符。在向用户显示电话号码时,我们可以使用页面所在国家的格式,将字符放回电话号码中。

小费

有关可用的验证和消毒类型的列表,请参见附录 B。

更安全的注册页面

为了使注册页面更加安全,采用了几种技术。修改后的注册页面外观与第五章中的注册页面相似(增加了标题条目、安全问题和验证码),如图 6-3 所示。

img/314857_2_En_6_Fig3_HTML.jpg

图 6-3

注册页面

已经包括了额外的安全和清洁,这将在清单 6-3a 和清单 6-3b 的末尾进行解释。

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        require("cap.php"); // recaptcha check
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Register Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS File -->
<link rel="stylesheet"
        href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
        integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
<script src="verify.js"></script>
<script src='https://www.google.com/recaptcha/api.js'></script>
// Required for Captcha Verification
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
        style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
                padding:20px;">
        <?php include('includes/register-header.php'); ?>
</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
<nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                     <?php include('includes/nav.php'); ?>
      </ul>
</nav>
<!-- Validate Input -->
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        require('process-register-page.php');
} // End of the main Submit conditional.
?>
<div class="col-sm-8">
         <h2 class="h2 text-center">Register</h2>
         <h3 class="text-center">
                  Items marked with an asterisk * are required</h3>
         <?php
         try {
         require_once("mysqli_connect.php");
         $query = "SELECT * FROM prices";
         $result = mysqli_query ($dbcon, $query); // Run the query.
         if ($result) { // If it ran OK, display the records.
                    $row = mysqli_fetch_array($result, MYSQLI_NUM);
                    $yearsarray = array(
                             "Standard one year:", "Standard five year:",
                             "Military one year:", "Under 21 one year:",
                             "Other - Give what you can. Maybe:" );
                    echo '<h6 class="text-center text-danger">
                            Membership classes:</h6>' ;
                    echo '<h6 class="text-center text-danger small"> ';
                    for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {

                               echo $yearsarray[$j] . " &pound; " .
                                       htmlspecialchars($row[$i], ENT_QUOTES)  .
                                       " GB, &dollar; " .
                                       htmlspecialchars($row[$i + 1], ENT_QUOTES) .
                                       " US";

                               if ($j != 4) {
                               if ($j % 2 == 0) {
                                          echo "</h6><h6 class=
                                                      'text-center text-danger small'>"; }
                               else { echo " , "; }
                               }
                    }
                    echo "</h6>";
         }
?>
<form action="safer-register-page1.php" method="post"
        onsubmit="return checked();" name="regform" id="regform">
<div class="form-group row">
         <label for="title" class="col-sm-4 col-form-label
                  text-right">Title:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="title" name="title"
                  placeholder="Title" maxlength="12"
                  pattern='[a-zA-Z][a-zA-Z\s\.]*'
                  title="Alphabetic, period and space max 12 characters"
                  value=
                          "<?php if (isset($_POST['title']))
                          echo htmlspecialchars($_POST['title'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">                                                      <!--#1-->
         <label for="first_name" class="col-sm-4 col-form-label
                  text-right">First Name*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="first_name"
                  name="first_name" pattern="[a-zA-Z][a-zA-Z\s]*"
                  title="Alphabetic and space only max of 30 characters"
                  placeholder="First Name" maxlength="30" required
                  value=
                          "<?php if (isset($_POST['first_name']))
                  echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="last_name" class="col-sm-4 col-form-label text-right">
                  Last Name*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="last_name" name="last_name"
                 pattern="[a-zA-Z][a-zA-Z\s\-\']*"
                 title="Alphabetic, dash, quote and space only max of 40 characters"
                 placeholder="Last Name" maxlength="40" required
                 value=
                         "<?php if (isset($_POST['last_name']))
                 echo htmlspecialchars($_POST['last_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="email" class="col-sm-4 col-form-label text-right">
                  E-mail*:</label>
<div class="col-sm-8">
         <input type="email" class="form-control" id="email" name="email"
                 placeholder="E-mail" maxlength="60" required
                 value=
                         "<?php if (isset($_POST['email']))
                 echo htmlspecialchars($_POST['email'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">                                                      <!--#2-->
         <label for="password1" class="col-sm-4 col-form-label
                  text-right">Password*:</label>
<div class="col-sm-8">
         <input type="password" class="form-control" id="password1"
                 name="password1"
                 pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,12}"
                 title="One number, one upper, one lower, one special, with 8 to 12 characters"
                 placeholder="Password" minlength="8" maxlength="12" required
                 value=
                         "<?php if (isset($_POST['password1']))
                 echo htmlspecialchars($_POST['password1'], ENT_QUOTES); ?>" >
<span id="message">Between 8 and 12 characters.</span>
</div>
</div>
<div class="form-group row">
        <label for="password2" class="col-sm-4 col-form-label
                 text-right">Confirm Password*:</label>
<div class="col-sm-8">
         <input type="password" class="form-control" id="password2"
                  name="password2"
                  pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,12}"
                  title="One number, one uppercase, one lowercase letter, with 8 to 12 characters"
                  placeholder="Confirm Password" minlength="8"
                  maxlength="12" required
                  value=
                          "<?php if (isset($_POST['password2']))
                  echo htmlspecialchars($_POST['password2'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="level" class="col-sm-4 col-form-label
                 text-right">Membership Class*</label>
<div class="col-sm-8">
                <select id="level" name="level" class="form-control" required>
                <option value="0" >-Select-</option>
<?php
for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {

        echo '<option value="' .
                htmlspecialchars($row[$i], ENT_QUOTES) . '" ';
        if ((isset($_POST['level'])) && ( $_POST['level'] == $row[$i]))
                {
        ?>
                        selected
        <?php }
        echo ">" . $yearsarray[$j] . " " .
        htmlspecialchars($row[$i], ENT_QUOTES) .
              " &pound; GB, " .
              htmlspecialchars($row[$i + 1], ENT_QUOTES) .
              "&dollar; US</option>";
}
echo "here";
?>
</select>
</div>
</div>
<div class="form-group row">
         <label for="address1" class="col-sm-4 col-form-label
                  text-right">Address*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="address1" name="address1"
                 pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
                 title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
                 placeholder="Address" maxlength="30" required
                 value=
                         "<?php if (isset($_POST['adress1']))
                 echo htmlspecialchars($_POST['address1'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="address2" class="col-sm-4 col-form-label
                  text-right">Address:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="address2" name="address2"
                 pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
                 title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
                 placeholder="Address" maxlength="30"
                 value=
                         "<?php if (isset($_POST['address2']))
                 echo htmlspecialchars($_POST['address2'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="city" class="col-sm-4 col-form-label
                  text-right">City*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="city" name="city"
                 pattern="[a-zA-Z][a-zA-Z\s\.]*"
                 title="Alphabetic, period and space only max of 30 characters"
                 placeholder="City" maxlength="30" required
                 value=
                         "<?php if (isset($_POST['city']))
                 echo htmlspecialchars($_POST['city'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="state_country" class="col-sm-4 col-form-label text-right">
                 Country/state*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="state_country"
                 name="state_country"
                 pattern="[a-zA-Z][a-zA-Z\s\.]*"
                 title="Alphabetic, period and space only max of 30 characters"
                 placeholder="State or Country" maxlength="30" required
                 value=
                         "<?php if (isset($_POST['state_country']))
                 echo htmlspecialchars($_POST['state_country'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="zcode_pcode" class="col-sm-4 col-form-label text-right">
                  Zip/Postal Code*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="zcode_pcode"
                 name="zcode_pcode"
                 pattern="[a-zA-Z0-9][a-zA-Z0-9\s]*"
                 title="Alphabetic, period and space only max of 30 characters"
                 placeholder="Zip or Postal Code" minlength="5" maxlength="30"
                 required
                 value=
                         "<?php if (isset($_POST['zcode_pcode']))
                 echo htmlspecialchars($_POST['zcode_pcode'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="phone" class="col-sm-4 col-form-label
                  text-right">Telephone:</label>
<div class="col-sm-8">
         <input type="tel" class="form-control" id="phone" name="phone"
                 placeholder="Phone Number" maxlength="30"
         value=
                         "<?php if (isset($_POST['phone']))
         echo htmlspecialchars($_POST['phone'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="question" class="col-sm-4 col-form-label text-right">
                  Secret Question*:</label>
<div class="col-sm-8">
         <select id="question" class="form-control">
                  <option selected value="">- Select -</option>
                  <option value="Maiden">Mother's Maiden Name</option>
                  <option value="Pet">Pet's Name</option>
                  <option value="School">High School</option>
                  <option value="Vacation">Favorite Vacation Spot</option>
         </select>
</div>
</div>
<div class="form-group row">                                                      <!--#3-->
         <label for="secret" class="col-sm-4 col-form-label
                  text-right">Answer*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="secret" name="secret"
                  pattern="[a-zA-Z][a-zA-Z\s\.\,\-]*"
                  title="Alphabetic, period, comma, dash and space only max of 30 characters"
                  placeholder="Secret Answer" maxlength="30" required
                  value=
                          "<?php if (isset($_POST['secret']))
                  echo htmlspecialchars($_POST['secret'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">                                                      <!--#4-->
         <label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<div class="float-left g-recaptcha"
         data-sitekey="yourdatasitekeyhere"></div>
</div>
</div>
<div class="form-group row">
         <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8 text-center">
         <input id="submit" class="btn btn-primary" type="submit"
                 name="submit" value="Register">
</div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
<?php

if(!isset($errorstring)) {
         echo '<aside class="col-sm-2">';
         include('includes/info-col-cards.php');
         echo '</aside>';
         echo '</div>';
         echo '<footer class="jumbotron text-center row col-sm-14"
                  style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
         echo '<footer class="jumbotron text-center col-sm-12"
         style="padding-bottom:1px; padding-top:8px;">';
 }
  include('includes/footer.php');
  echo "</footer>";
  echo "</div>";
  }
catch(Exception $e) // We finally handle any problems here
   {
         // print "An Exception occurred. Message: " . $e->getMessage();
         print "The system is busy please try later";
   }
catch(Error $e)
   {
         //print "An Error occurred. Message: " . $e->getMessage();
         print "The system is busy please try again later.";
   }
?>
</body>
</html>

Listing 6-3aCreating a More Secure Registration Page
(safer-register-page.php)

代码的解释

本节解释代码。

<div class="form-group row">                                                     <!--#1-->
         <label for="first_name" class="col-sm-4 col-form-label
                  text-right">First Name*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="first_name"
                  name="first_name" pattern="[a-zA-Z][a-zA-Z\s]*"
                  title="Alphabetic and space only max of 30 characters"
                  placeholder="First Name" maxlength="30" required
                  value=
                           "<?php if (isset($_POST['first_name']))
                  echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >
</div>
</div>

输入 HTML 标记的 pattern 属性允许我们使用正则表达式来验证与我们在 PHP 代码中提供的验证相关的信息。本例中显示的模式允许大写和小写字母字符和空格。title 属性是模式不匹配时显示的消息。此信息是必需的,最大长度为 30 个字符。用前面显示的 PHP 代码在服务器端也实现了同样的验证。

<div class="form-group row">                                                     <!--#2-->
         <label for="password1" class="col-sm-4 col-form-label
                  text-right">Password*:</label>
<div class="col-sm-8">
         <input type="password" class="form-control" id="password1"
                  name="password1"
                  pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,12}"
                  title="One number, one upper, one lower, one special, with 8 to 12 characters"
                  placeholder="Password" minlength="8" maxlength="12" required
                  value=
                          "<?php if (isset($_POST['password1']))
                  echo htmlspecialchars($_POST['password1'], ENT_QUOTES); ?>" >
<span id="message">Between 8 and 12 characters.</span>
</div>
</div>

密码限制增加了。前面的模式将输入限制为需要一个大写字母、一个小写字母、一个数字和一个特殊字符。这个特殊字符实际上并没有在这里得到验证,而是在 PHP 代码中得到验证。我们把这个挑战留给你去发现特殊字符的模式。input 语句的模式和属性都要求用户输入 8 到 12 个字符。在“真实世界”中,我们会增加这些数字以提供更高的安全性。从用户处接受的其他值也以类似的方式进行验证。

<div class="form-group row">                                                       <!--#3-->
         <label for="secret" class="col-sm-4 col-form-label
                  text-right">Answer*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="secret" name="secret"
                  pattern="[a-zA-Z][a-zA-Z\s\.\,\-]*"
                  title="Alphabetic, period, comma, dash and space only max of 30 characters"
                  placeholder="Secret Answer" maxlength="30" required
                  value=
                           "<?php if (isset($_POST['secret']))
                  echo htmlspecialchars($_POST['secret'], ENT_QUOTES); ?>" >
</div>
</div>

用户现在需要一个新的机密问题。当用户忘记密码时,该信息将用于允许用户重置他们的密码。在第十一章中,我们将为用户创建一个请求新密码的表单。

<div class="form-group row">                                                       <!--#4-->
         <label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<div class="float-left g-recaptcha"
         data-sitekey="yourdatasitekeyhere"></div>
</div>
</div>

目前,我们仍然有一个安全弱点,我们可以很容易地修复。我们没有能力检测一个人是否真的在使用我们的程序。这可能是一个主要问题,因为程序可以用虚假注册淹没我们的网站,直到数据库和服务器崩溃。此外,正如我们提到的,他们可以尝试插入可执行代码,这将对网站、数据库和服务器造成损害。

银行和其他高安全性组织将使用多种方法来验证人工输入,包括验证所使用的 IP 地址。我们将采用一种常见的方法,使用 Google 的 Recaptcha 系统来请求用户验证他们是人类。使用 Recaptcha 只需要导入所需的 JavaScript 代码并显示 Recaptcha 应用。显示应用的代码如前面代码中的#4 所示。导入所需 JavaScript 代码的代码如下:

<script src='https://www.google.com/recaptcha/api.js'></script>
// Required for Captcha Verification
We will also need to verify that a human did actually check the box on the server side.
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
         require("cap.php"); // recaptcha check
}
?>

前面的代码位于 safer-register 页面的顶部,用于插入复选框已被选中的验证。

<?php
        if(isset($_POST['g-recaptcha-response'])){
                        $captcha=$_POST['g-recaptcha-response'];
                        $secretKey = "Put your secret key here";
                        $ip_address = $_SERVER['REMOTE_ADDR'];
                        $response=
file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".
$secretKey."&response=".$captcha."&remoteip=".$ip_address);
                $keys = json_decode($response,true);
                if(intval($keys["success"]) !== 1) {
        echo "<h4 class='text-center'>Are you human? Click recaptcha</h4>";
                        header( "refresh:1;" );
                }
        }
        else {
echo "<h4 class='text-center'>Are you human? Click recaptcha!</h4>";
                        header( "refresh:1;" );
        }
?>

如果尝试被验证为人类,Recaptcha 将创建 g-recaptcha-response POST 值。如果存在的话,密钥和当前的 IP 地址被放在变量中,然后通过函数 file_get_contents 使用这些变量来检索响应的内容。响应采用 JSON 数据结构的格式。json_decode 函数将该结构转换成一个 PHP 数组($keys)。intval 函数用于判断数组成功列的内容是否为数字 1(相当于检查是否为真)。否则,将显示一条消息,并重置页面(一秒钟后)。如果响应不存在,将显示类似的消息,并重置页面。如果人类确实选中了这个框,那么一切都很好,程序继续运行。将文件与此代码(cap.php)链接的 require 语句在示例文件中被注释掉,因为它需要一个密钥才能成功工作。

要注册站点和密钥并阅读更多信息,请访问此站点:

https://www.google.com/recaptcha

现在让我们看一些 PHP 代码。

<?php
// Has the form been submitted?
try {
        require ('mysqli_connect.php'); // Connect to the database
        $errors = array(); // Initialize an error array.
        // --------------------check the entries-------------
        //Is the title present? If it is, sanitize it                               #1
        $title = filter_var( $_POST['title'], FILTER_SANITIZE_STRING);
        if ((!empty($title)) && (preg_match('/[a-z\.\s]/i',$title)) &&
                (strlen($title) <= 12)) {
                //Sanitize the trimmed title
                $titletrim = $title;
        }else{
                $titletrim = NULL; // Title is optional
        }
        // Trim the first name
        $first_name =
                filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
                if ((!empty($first_name)) && (preg_match('/[a-z\s]/i',$first_name)) &&
                (strlen($first_name) <= 30)) {
                //Sanitize the trimmed first name
                $first_nametrim = $first_name;
        }else{
                $errors[] =
        'First name missing or not alphabetic and space characters. Max 30';
        }
        //Is the last name present? If it is, sanitize it
        $last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
        if ((!empty($last_name)) &&
                (preg_match('/[a-z\-\s\']/i',$last_name)) &&
                        (strlen($last_name) <= 40)) {
                //Sanitize the trimmed last name
                $last_nametrim = $last_name;
        }else{
                $errors[] =
        'Last name missing or not alphabetic, dash, quote or space. Max 30.';
        }
        // Check that an email address has been entered
        $emailtrim = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if ((empty($emailtrim)) ||
                (!filter_var($emailtrim, FILTER_VALIDATE_EMAIL))
                        || (strlen($emailtrim > 60))) {
                $errors[] = 'You forgot to enter your email address';
                $errors[] = ' or the e-mail format is incorrect.';
        }
        // Check for a password and match against the confirmed password:           #2
        $password1trim =
                filter_var( $_POST['password1'], FILTER_SANITIZE_STRING);
                $string_length = strlen($password1trim);
        if (empty($password1trim)){
                $errors[] ='Please enter a valid password';
        }
        else {
        if(!preg_match(
'/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$@!%&*?])[A-Za-z\d#$@!%&*?]{8,12}$/',
$password1trim)) {
        $errors[] =
'Invalid password, 8 to 12 chars, 1 upper, 1 lower, 1 number, 1 special.';
        } else
        {
                $password2trim =
                filter_var( $_POST['password2'], FILTER_SANITIZE_STRING);
        if($password1trim === $password2trim) {
                $password = $password1trim;
        }else{
                $errors[] = 'Your two passwords do not match.';
                $errors[] = 'Please try again';
                }
        }
        }
        //Is the 1st address present? If it is, sanitize it
        $address1 = filter_var( $_POST['address1'], FILTER_SANITIZE_STRING);
        if ((!empty($address1)) &&
                (preg_match('/[a-z0-9\.\s\,\-]/i', $address1)) &&
                        (strlen($address1) <= 30)) {
                //Sanitize the trimmed 1st address
                $address1trim = $address1;
        }else{
        $errors[] =
'Missing address. Numeric, alphabetic, period, comma, dash and space.Max 30.';
        }
        //If the 2nd address is present? If it is, sanitize it
        $address2 = filter_var( $_POST['address2'], FILTER_SANITIZE_STRING);
        if ((!empty($address2)) &&
                (preg_match('/[a-z0-9\.\s\,\-]/i', $address2)) &&
                        (strlen($address2) <= 30)) {
                //Sanitize the trimmed 2nd address
                $address2trim = $address2;
        }else{
                $address2trim = NULL;
        }
        //Is the city present? If it is, sanitize it
        $city = filter_var( $_POST['city'], FILTER_SANITIZE_STRING);
        if ((!empty($city)) && (preg_match('/[a-z\.\s]/i', $city)) &&
                (strlen($city) <= 30)) {
                //Sanitize the trimmed city
                $citytrim = $city;
        }else{
                $errors[] =
                'Missing city. Only alphabetic, period and space. Max 30.';
        }
        //Is the state or country present? If it is, sanitize it
        $state_country =
                filter_var( $_POST['state_country'], FILTER_SANITIZE_STRING);
        if ((!empty($state_country)) &&
                (preg_match('/[a-z\.\s]/i', $state_country)) &&
                        (strlen($state_country) <= 30)) {
                //Sanitize the trimmed state or country
                $state_countrytrim = $state_country;
        }else{
                $errors[] =
        'Missing state/country. Only alphabetic, period and space. Max 30.';}
        //Is the zip code or post code present? If it is, sanitize it
        $zcode_pcode =
                filter_var( $_POST['zcode_pcode'], FILTER_SANITIZE_STRING);
        $string_length = strlen($zcode_pcode);
        if ((!empty($zcode_pcode)) &&
                (preg_match('/[a-z0-9\s]/i', $zcode_pcode))  &&
                        ($string_length <= 30) && ($string_length >= 5)) {
        //Sanitize the trimmed zcode_pcode
                $zcode_pcodetrim = $zcode_pcode;
        }else{
                $errors[] =
'Missing zip code or post code. Alpha, numeric, space only max 30 characters';
        }
        //Is the secret present? If it is, sanitize it
        $secret = filter_var( $_POST['secret'], FILTER_SANITIZE_STRING);
        if ((!empty($secret)) && (preg_match('/[a-z\.\s\,\-]/i', $secret)) &&
                (strlen($secret) <= 30)) {
                //Sanitize the trimmed city
                $secrettrim = $secret;
        }else{
                $errors[] =
'Missing city. Only alphabetic, period, comma, dash and space. Max 30.';
        }
        //Is the phone number present? If it is, sanitize it
        $phone = filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
        if ((!empty($phone)) && (strlen($phone) <= 30)) {
                //Sanitize the trimmed phone number
                $phonetrim = (filter_var($phone, FILTER_SANITIZE_NUMBER_INT));
                $phonetrim = preg_replace('/[⁰-9]/', ", $phonetrim);
        }else{
                $phonetrim = NULL;
        }
        //Is the class present? If it is, sanitize it
        $class = filter_var( $_POST['level'], FILTER_SANITIZE_STRING);
        if ((!empty($class)) && (strlen($class) <= 3)) {
                //Sanitize the trimmed phone number
                $classtrim = (filter_var($class, FILTER_SANITIZE_NUMBER_INT));
        }else{
                $errors[] = 'Missing Level Selection.';
        }
        if (empty($errors)) { // If everything's OK.
                // If no problems encountered, register user in the database
                //Determine whether the email address has already been registered
                $query = "SELECT userid FROM users WHERE email = ? ";
                $q = mysqli_stmt_init($dbcon);
                mysqli_stmt_prepare($q, $query);
                mysqli_stmt_bind_param($q,'s', $emailtrim);
                mysqli_stmt_execute($q);
                $result = mysqli_stmt_get_result($q);

        if (mysqli_num_rows($result) == 0){
        //The email address has not been registered
        //already therefore register the user in the users table
        //-------------Valid Entries - Save to database -----
        //Start of the SUCCESSFUL SECTION.
        // i.e., all the required fields were filled out
                $hashed_password = password_hash($password, PASSWORD_DEFAULT);
                // Register the user in the database...
                $query = "INSERT INTO users (userid, title, first_name, ";
                $query .= "last_name, email, password, class, ";
                $query .= "address1, address2, city, state_country, ";
                $query .= "zcode_pcode, phone, secret, registration_date) ";
                $query .= "VALUES ";
                $query .= "(' ',?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())";
                $q = mysqli_stmt_init($dbcon);
                mysqli_stmt_prepare($q, $query);
// use prepared statement to ensure that only text is inserted
// bind fields to SQL Statement
                mysqli_stmt_bind_param($q, 'sssssssssssss',
                        $titletrim, $first_nametrim, $last_nametrim, $emailtrim,
                        $hashed_password, $classtrim, $address1trim,
                        $address2trim, $citytrim, $state_countrytrim,
                        $zcode_pcodetrim, $phonetrim, $secrettrim);
                // execute query
                mysqli_stmt_execute($q);
                if (mysqli_stmt_affected_rows($q) == 1) {
                header ("location: register-thanks.php?class=" . $classtrim);
        } else {
        // echo 'Invalid query:' . $dbcon->error;
                $errorstring = "System is busy, please try later";
                echo "<p class=' text-center col-sm-2'
                        style='color:red'>$errorstring</p>";
        }
        }else{//The email address is already registered
                $errorstring = 'The email address is already registered.';
                echo "<p class=' text-center col-sm-2'
                        style='color:red'>$errorstring</p>";
        }
} else {//End of SUCCESSFUL SECTION
        // ---------------Process User Errors---------------
        // Display the users entry errors
        $errorstring = 'Error! The following error(s) occurred: ';
        foreach ($errors as $msg) { // Print each error.
                $errorstring .= " - $msg<br>\n";
        }
        $errorstring .= 'Please try again.';
        echo "<p class=' text-center col-sm-2' style=
                'color:red'>$errorstring</p>";
}// End of if (empty($errors)) IF.
}
catch(Exception $e)
{
        print "The system is busy, please try later";
        //print "An Exception occurred. Message: " . $e->getMessage();
}
catch(Error $e)
{
        print "The system is busy, please come back later";
        //print "An Error occurred. Message: " . $e->getMessage();
}
?>

Listing 6-3bCode for a More Secure Registration Page (process-register-page.php)

代码的解释

本节解释代码。

//Is the title present? If it is, sanitize it                                           #1
        $title = filter_var( $_POST['title'], FILTER_SANITIZE_STRING);
        if ((!empty($title)) && (preg_match('/[a-z\.\s]/i',$title)) &&
                (strlen($title) <= 12)) {
                //Sanitize the trimmed title
                $titletrim = $title;
        }else{
                $titletrim = NULL; // Title is optional
        }

标题被删除了。如果净化的输入不为空,则将其与所需的格式进行比较,该格式允许字母字符、空格和句点。如果验证检查失败,那么$title 被设置为 NULL。由于 title 是可选的,因此如果 title 缺失或无效,不会产生错误消息。对于不可选的变量,错误消息放在错误数组中。

// Check for a password and match against the confirmed password:                       #2
$password1trim =
        filter_var( $_POST['password1'], FILTER_SANITIZE_STRING);
        $string_length = strlen($password1trim);
if (empty($password1trim)){
        $errors[] ='Please enter a valid password';
}
else {
if(!preg_match(
'/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$@!%&*?])[A-Za-z\d#$@!%&*?]{8,12}$/',
$password1trim)) {
$errors[] = 'Invalid password, 8 to 12 chars, 1 upper, 1 lower, 1 number, 1 special.';

注意 preg_match 前面的感叹号。这意味着“如果密码不匹配,将一条错误消息放入\(errors 数组中。”regex 函数 preg_match()检查第一个密码的字符组合是否正确。必须有一个大写字母、一个小写字母、一个数字和一个特殊字符(#\)@!%&*?).另外,至少要有八个字符,不超过十二个。由于第二个密码必须与第一个密码匹配,因此第二个密码不需要进行此项检查。有关 preg_match 的更多信息,请参见本页:

http://php.net/manual/en/function.preg-match.php

如果两个密码不匹配或格式不正确,则会在$errors 数组中放置一条错误消息。完成这两种检查是为了一次向用户提供所有的错误反馈。例如,如果格式不正确并且密码不匹配,用户将会看到两个错误都显示出来。

注意

本章可从 www.apress.com 下载的文件现在在标题的注册菜单按钮中包含了更安全的注册页面链接。第五章中的所有其他文件也已经使用本章中显示的清理和验证技术进行了更新。本文件中使用的验证技术用于演示。

在现实世界中,您可能会在许多用户输入中允许额外的字符,并要求更长的密码。总是考虑用户和他们需要的需求。这是安全性和用户便利性之间的平衡。

运行 XAMPP 或 easyPHP,在浏览器的地址栏输入http://localhost/final post/safer-register-page . PHP。然后注册一些会员,但是故意出错看结果。

在前一章中,我们创建并列出了一个搜索地址的屏幕。现在,我们将利用该屏幕来搜索和编辑标题、地址和电话号码。

搜索标题、地址或电话号码

在文件header-admin.php中,Addresses 菜单按钮中的死链接已经被替换为一个到页面 search_addresses.php 的链接。图 6-4 显示了搜索标题、地址和电话号码的屏幕。

img/314857_2_En_6_Fig4_HTML.jpg

图 6-4

搜索标题、地址或电话号码

代码以一个会话开始,以确保只有管理员可以查看和修改成员的记录。在这种情况下,我们已经假设管理员的会话是足够的保护。只有管理员可以搜索用户记录。HTML 代码如清单 6-4 所示。

<?php
session_start();
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1)) //#1
{ header("Location: login.php");
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Search Address Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
        href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
        integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
<script src="verify.js"></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
        padding:20px;">
        <?php include('includes/login-header.php'); ?>
</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
<nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('includes/nav.php'); ?>
      </ul>
</nav>
<!-- Validate Input -->
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        require('process-view_found_addresses.php');
} // End of the main Submit conditional.
?>
<div class="col-sm-8">
<div class="h2 text-center">
<h5>Search for an Address or Phone Number</h5>
<h5 style="color: red;">Both Names are required items</h5>
</div>
<form action="view_found_address.php" method="post" name="searchform"
        id="searchform">
<div class="form-group row">
        <label for="first_name" class=
                "col-sm-4 col-form-label text-right">First Name:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="first_name"
                name="first_name"
                placeholder="First Name" maxlength="30" required
                value=
                "<?php if (isset($_POST['first_name']))
                echo htmlspecialchars($_POST['first_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="last_name" class="col-sm-4 col-form-label text-right">
                Last Name:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="last_name"
                name="last_name"
                placeholder="Last Name" maxlength="40" required
                value=
                "<?php if (isset($_POST['last_name']))
                echo htmlspecialchars($_POST['last_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="l" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
        <input id="submit" class="btn btn-primary" type="submit" name="submit"
                value="Search">
</div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
<?php
 if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('includes/info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
                style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('includes/footer.php');
 ?>
</footer>
</div>
</body>
</html>

Listing 6-4Creating

the Search Address Page (search_address.php)

代码的解释

本节解释代码。

<?php
session_start();
if (!isset($_SESSION['user_level']) or ($_SESSION['user_level'] != 1))
{
header("Location: login.php");
exit();
}
?>

如前所示,只有管理员可以访问该页面。

<form action="view_found_address.php" method="post" name="searchform"
        id="searchform">

在可下载文件中,文件的

部分已被修改,因此它连接到文件 *view_found_address.php* ,如下所示。

我们现在将检查检索到的标题、地址和电话号码的屏幕。

请注意,图 6-5 中显示的表格中没有删除列,因为删除是最终行为;必须使用 delete_record.php 页面来完成,在这里管理员有第二次机会来决定是否删除记录。

查看检索到的标题、地址和电话号码

图 6-5 显示了头衔、地址和电话号码。

img/314857_2_En_6_Fig5_HTML.jpg

图 6-5

新表格显示所选成员的头衔、地址和电话号码

清单 6-5 显示了显示两个新列的代码。请注意,因为没有空间来包含电子邮件列,所以可以在 admin_view_users.php 页面中编辑电子邮件地址。

<?php
try
{
        // This script retrieves records from the users table.
        require ('./mysqli_connect.php'); // Connect to the db.
        echo '<p class="text-center">If no record is shown, ';
        echo 'this is because you had an incorrect ';
        ' or missing entry in the search form.';
        echo '<br>Click the back button on the browser and try again</p>';
        $first_name = htmlspecialchars($_POST['first_name'], ENT_QUOTES);
        last_name = htmlspecialchars($_POST['last_name'], ENT_QUOTES);
// Since it's a prepared statement below this sanitizing is not needed
        // However, to consistently retrieve than sanitize is a good habit

        $query = "SELECT userid, title, last_name, first_name, ";
$query .= "address1, address2, city, state_country, zcode_pcode, phone ";
        $query .= "FROM users WHERE ";
        $query .= "last_name=? AND first_name=?";

        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);

        // bind values to SQL Statement
        mysqli_stmt_bind_param($q, 'ss', $last_name, $first_name);

        // execute query
        mysqli_stmt_execute($q);

        $result = mysqli_stmt_get_result($q);

        if ($result) { // If it ran, display the records.
                // Table header.                                                        #1
                echo '<table class="table table-striped table-sm">
                <tr>
                <th scope="col">Edit</th>
                <th scope="col">Title</th>
                <th scope="col">Last Name</th>
                <th scope="col">First Name</th>
                <th scope="col">Address1</th>
                <th scope="col">Address2</th>
                <th scope="col">City</th>
                <th scope="col">State or Country</th>
                <th scope="col">Zip or Postal Code</th>
                <th scope="col">Phone</th>
                </tr>';

// Fetch and display the records:
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
        // Remove special characters that might already be in table to
        // reduce the chance of XSS exploits
        $user_id = htmlspecialchars($row['title'], ENT_QUOTES);
        $title = htmlspecialchars($row['state_country'], ENT_QUOTES);
        $last_name = htmlspecialchars($row['last_name'], ENT_QUOTES);
        $first_name = htmlspecialchars($row['first_name'], ENT_QUOTES);
        $address1 = htmlspecialchars($row['address1'], ENT_QUOTES);
        $address2 = htmlspecialchars($row['address2'], ENT_QUOTES);
        $city = htmlspecialchars($row['city'], ENT_QUOTES);
        $state_country = htmlspecialchars($row['state_country'], ENT_QUOTES);
        $zcode_pcode = htmlspecialchars($row['zcode_pcode'], ENT_QUOTES);
        $phone = htmlspecialchars($row['phone'], ENT_QUOTES);
        //                                                                              #2
        echo '<tr>
        <td scope="row"><a href="edit_address.php?id=' . $user_id .
        '">Edit</a></td>
        <td scope="row">' . $title . '</td>
        <td scope="row">' . $first_name . '</td>
        <td scope="row">' . $last_name . '</td>
        <td scope="row">' . $address1 . '</td>
        <td scope="row">' . $address2 . '</td>
        <td scope="row">' . $city . '</td>
        <td scope="row">' . $state_country . '</td>
        <td scope="row">' . $zcode_pcode . '</td>
        <td scope="row">' . $phone . '</td>
        </tr>';
}
echo '</table>'; // Close the table.
//
mysqli_free_result ($result); // Free up the resources.
} else { // If it did not run OK.
        // Public message:
        echo
        '<p class="center-text">The current users could not be retrieved.';
        echo 'We apologize for any inconvenience.</p>';
        // Debugging message:
        //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
        //Show $q is debug mode only
} // End of if ($result). Now display the total number of records/members.
mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e)
{
        print "The system is currently busy. Please try later.";
        //print "An Exception occurred.Message: " . $e->getMessage();
}
catch(Error $e)
{
        print "The system us busy. Please try later.";
        //print "An Error occurred. Message: " . $e->getMessage();
}
?>

Listing 6-5Code for Displaying a Table with Title, Address, and Phone Number (process_view_found_address.php

)

代码的解释

本节解释代码。

// Table header.                                                                        #1
echo '<table class="table table-striped table-sm">
<tr>
<th scope="col">Edit</th>
<th scope="col">Title</th>
<th scope="col">Last Name</th>
<th scope="col">First Name</th>
<th scope="col">Address1</th>
<th scope="col">Address2</th>
<th scope="col">City</th>
<th scope="col">State or Country</th>
<th scope="col">Zip or Postal Code</th>
<th scope="col">Phone</th>
</tr>';

现在,列标题包括成员的头衔、地址和电话号码。

//                                                                                      #2
        echo '<tr>
        <td scope="row"><a href="edit_address.php?id=' . $user_id .
        '">Edit</a></td>
        <td scope="row">' . $title . '</td>
        <td scope="row">' . $first_name . '</td>
        <td scope="row">' . $last_name . '</td>
        <td scope="row">' . $address1 . '</td>
        <td scope="row">' . $address2 . '</td>
        <td scope="row">' . $city . '</td>
        <td scope="row">' . $state_country . '</td>
        <td scope="row">' . $zcode_pcode . '</td>
        <td scope="row">' . $phone . '</td>
        </tr>';

该表显示并填充了成员的职务、地址和电话号码。注意,编辑标题/地址/电话表的链接现在是 edit_address.php 。接下来我们将创建这个文件。

编辑标题、地址和电话号码

当找到记录并点击编辑链接时,出现如图 6-6 所示的屏幕。这允许管理员编辑显示中的任何字段。请注意,管理员不能从标题/地址/电话搜索中删除记录,因为这必须使用 delete.php 页面的显示的记录来完成。从 delete.php页面的中删除记录会自动删除标题、地址和电话细节,因为它们是记录的一部分。

img/314857_2_En_6_Fig6_HTML.jpg

图 6-6

用于编辑标题/地址/电话显示字段的屏幕

在该屏幕中,可以编辑所有字段。标题、第二个地址和电话字段是可选的,也就是说,如果需要,它们可以保留为空。清单 6-6 显示了屏幕的代码。

注意

清单 6-6 和文件 edit_record.php 中已经使用了验证和清理。这里没有列出文件 edit_record.php ,但是您可以通过在您的文本编辑器中查看下载的文件来检查它。

<?php
try
{
// After clicking the Edit link in the found_record.php page. This code is executed
// The code looks for a valid user ID, either through GET or POST:
if ( (isset($_GET['id'])) && (is_numeric($_GET['id'])) ) {
    $id = htmlspecialchars($_GET['id'], ENT_QUOTES);
} elseif ( (isset($_POST['id'])) && (is_numeric($_POST['id'])) ) {
// Form submission.
        $id = htmlspecialchars($_POST['id'], ENT_QUOTES);
} else { // No valid ID, kill the script.
        echo
        '<p class="text-center">This page has been accessed in error.</p>';
        include ('footer.php');
        exit();
}
require ('mysqli_connect.php');
// Has the form been submitted?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $errors = array();
        // Look for the first name:
        //Is the title present? If it is, sanitize it
        $title = filter_var( $_POST['title'], FILTER_SANITIZE_STRING);
        if ((!empty($title)) && (preg_match('/[a-z\.\s]/i',$title)) &&
                (strlen($title) <= 12)) {
                //Sanitize the trimmed title
                $titletrim = $title;
        }else{
                $titletrim = NULL; // Title is optional
        }
        // Sanitize the first name
        $first_name =
        filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
        if ((!empty($first_name)) && (preg_match('/[a-z\s]/i',$first_name)) &&
                (strlen($first_name) <= 30)) {
                //Sanitize the trimmed first name
                $first_nametrim = $first_name;
        }else{
                $errors[] =
        'First name missing or not alphabetic and space characters. Max 30';
        }
        //Is the last name present? If it is, sanitize it
        $last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
if ((!empty($last_name)) && (preg_match('/[a-z\-\s\']/i',$last_name)) &&
                (strlen($last_name) <= 40)) {
        //Sanitize the trimmed last name
    $last_nametrim = $last_name;
        }else{
        $errors[] = 'Last name missing or not alphabetic, dash, quote or space. Max 30.';
        }
        //Is the 1st address present? If it is, sanitize it
$address1 = filter_var( $_POST['address1'], FILTER_SANITIZE_STRING);
if ((!empty($address1)) && (preg_match('/[a-z0-9\.\s\,\-]/i', $address1)) &&
  (strlen($address1) <= 30)) {
        //Sanitize the trimmed 1st address
        $address1trim = $address1;
}else{
        $errors[] =
'Missing address. Numeric, alphabetic, period, comma, dash, space. Max 30.';
        }
        //If the 2nd address is present? If it is, sanitize it
        $address2 = filter_var( $_POST['address2'], FILTER_SANITIZE_STRING);
        if ((!empty($address2)) &&
                (preg_match('/[a-z0-9\.\s\,\-]/i', $address2)) &&
                (strlen($address2) <= 30)) {
                //Sanitize the trimmed 2nd address
                $address2trim = $address2;
        }else{
                $address2trim = NULL;
        }
        //Is the city present? If it is, sanitize it
        $city = filter_var( $_POST['city'], FILTER_SANITIZE_STRING);
        if ((!empty($city)) && (preg_match('/[a-z\.\s]/i', $city)) &&
                (strlen($city) <= 30)) {
                //Sanitize the trimmed city
                $citytrim = $city;
        }else{
                $errors[] =
                'Missing city. Only alphabetic, period and space. Max 30.';
        }
        //Is the state or country present? If it is, sanitize it
        $state_country =
                filter_var( $_POST['state_country'], FILTER_SANITIZE_STRING);
        if ((!empty($state_country)) &&
                (preg_match('/[a-z\.\s]/i', $state_country)) &&
                        (strlen($state_country) <= 30)) {
                //Sanitize the trimmed state or country
                $state_countrytrim = $state_country;
        }else{
                $errors[] =
        'Missing state/country. Only alphabetic, period and space. Max 30.';
        }
        //Is the zip code or post code present? If it is, sanitize it
        $zcode_pcode =
                filter_var( $_POST['zcode_pcode'], FILTER_SANITIZE_STRING);
        $string_length = strlen($zcode_pcode);
        if ((!empty($zcode_pcode)) &&
                (preg_match('/[a-z0-9\s]/i', $zcode_pcode))  &&
                        ($string_length <= 30) && ($string_length >= 5)) {
                //Sanitize the trimmed zcode_pcode
        $zcode_pcodetrim = $zcode_pcode;
        }else{
                $errors[] =
        'Missing zip-post code. Alphabetic, numeric, space. Max 30 characters';
        }
        //Is the phone number present? If it is, sanitize it
        $phone = filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
        if ((!empty($phone)) && (strlen($phone) <= 30)) {
                //Sanitize the trimmed phone number
                $phonetrim = (filter_var($phone, FILTER_SANITIZE_NUMBER_INT));
                $phonetrim = preg_replace('/[⁰-9]/', ", $phonetrim);
        }else{
                $phonetrim = NULL;
        }
        if (empty($errors)) { // If everything's OK.
                $query =
        'UPDATE users SET title=?, first_name=?, last_name=?, address1=?,';
                $query .=
                        ' address2=?, city=?, state_country=?, zcode_pcode=?,';
                $query .= ' phone=?';
                $query .= ' WHERE userid=? LIMIT 1';
                $q = mysqli_stmt_init($dbcon);
                mysqli_stmt_prepare($q, $query);

                // bind values to SQL Statement

mysqli_stmt_bind_param($q, 'ssssssssss', $titletrim, $first_nametrim,
        $last_nametrim, $address1trim, $address2trim, $citytrim,
        $state_countrytrim, $zcode_pcodetrim, $phonetrim, $id);
        // execute query

        mysqli_stmt_execute($q);

        if (mysqli_stmt_affected_rows($q) == 1) { // Update OK

        // Echo a message if the edit was satisfactory:
                echo '<h3 class="text-center">The user has been edited.</h3>';
        } else { // Echo a message if the query failed.
                echo
'<p class="text-center">The user could not be edited due to a system error.';
             echo
        ' We apologize for any inconvenience.</p>'; // Public message.
//echo '<p>' . mysqli_error($dbcon) . '<br />Query: ' . $q . '</p>';
// Debugging message.
// Message above is only for debug and should not display sql in live mode
                        }
        } else { // Display the errors.
        echo '<p class="text-center">The following error(s) occurred:<br />';
                foreach ($errors as $msg) { // Echo each error.
                        echo " - $msg<br />\n";
                }
                echo '</p><p>Please try again.</p>';
        } // End of if (empty($errors))section.
} // End of the conditionals
// Select the user's information to display in textboxes:

        $q = mysqli_stmt_init($dbcon);
        $query = "SELECT * FROM users WHERE userid=?";
        mysqli_stmt_prepare($q, $query);
        // bind $id to SQL Statement
        mysqli_stmt_bind_param($q, 'i', $id);
        // execute query
        mysqli_stmt_execute($q);
        $result = mysqli_stmt_get_result($q);
        $row = mysqli_fetch_array($result, MYSQLI_ASSOC);
        if (mysqli_num_rows($result) == 1) {
        // Valid user ID, display the form.
        // Get the user's information:
        // Create the form:
?>
<h2 class="h2 text-center">Edit User</h2>
<h3 class="text-center">Items marked with an asterisk * are required</h3>
<form action="edit_address.php" method="post"
        name="editform" id="editform">
<div class="form-group row">
        <label for="title" class="col-sm-4 col-form-label
                text-right">Title:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="title" name="title"
                placeholder="Title" maxlength="12"
                pattern='[a-zA-Z][a-zA-Z\s\.]*'
                title="Alphabetic, period and space max 12 characters"
                value=
                "<?php if (isset($row['title']))
                echo htmlspecialchars($row['title'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="first_name" class="col-sm-4 col-form-label
                 text-right">First Name*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="first_name"
                name="first_name"
                pattern="[a-zA-Z][a-zA-Z\s]*"
                title="Alphabetic and space only max of 30 characters"
                placeholder="First Name" maxlength="30" required
                value=
                "<?php if (isset($row['first_name']))
                echo htmlspecialchars($row['first_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="last_name" class="col-sm-4 col-form-label text-right">
                Last Name*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="last_name" name="last_name"
                pattern="[a-zA-Z][a-zA-Z\s\-\']*"
                title="Alphabetic, dash, quote and space only max of 40 characters"
                placeholder="Last Name" maxlength="40" required
                value=
                        "<?php if (isset($row['last_name']))
                echo htmlspecialchars($row['last_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="address1" class="col-sm-4 col-form-label
                text-right">Address*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="address1" name="address1"
            pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
            title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
            placeholder="Address" maxlength="30" required
            value=
                "<?php if (isset($row['address1']))
                echo htmlspecialchars($row['address1'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="address2" class="col-sm-4 col-form-label
                text-right">Address:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="address2" name="address2"
                pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
                title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
                placeholder="Address" maxlength="30"
                value=
                        "<?php if (isset($row['address2']))
                echo htmlspecialchars($row['address2'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="city" class="col-sm-4 col-form-label
                text-right">City*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="city" name="city"
                pattern="[a-zA-Z][a-zA-Z\s\.]*"
                title="Alphabetic, period and space only max of 30 characters"
                placeholder="City" maxlength="30" required
                value=
                        "<?php if (isset($row['city']))
                echo htmlspecialchars($row['city'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="state_country" class="col-sm-4 col-form-label text-right">
                Country/state*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="state_country"
                name="state_country"
                pattern="[a-zA-Z][a-zA-Z\s\.]*"
                title="Alphabetic, period and space only max of 30 characters"
                placeholder="State or Country" maxlength="30" required
                value=
                        "<?php if (isset($row['state_country']))
                echo htmlspecialchars($row['state_country'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="zcode_pcode" class="col-sm-4 col-form-label text-right">
                Zip/Postal Code*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="zcode_pcode"
                name="zcode_pcode"
                pattern="[a-zA-Z0-9][a-zA-Z0-9\s]*"
                title="Alphabetic, period and space only max of 30 characters"
                placeholder="Zip or Postal Code" minlength="5" maxlength="30"
                required
                value=
                        "<?php if (isset($row['zcode_pcode']))
                echo htmlspecialchars($row['zcode_pcode'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="phone" class="col-sm-4 col-form-label
                text-right">Telephone:</label>
<div class="col-sm-8">
        <input type="tel" class="form-control" id="phone" name="phone"
                placeholder="Phone Number" maxlength="30"
                value=
                        "<?php if (isset($row['phone']))
                echo htmlspecialchars($row['phone'], ENT_QUOTES); ?>" >
</div>
</div>
        <input type="hidden" name="id" value="<?php echo $id ?>" />
<div class="form-group row">
        <label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<div class="float-left g-recaptcha"
        data-sitekey="Yourdatakeygoeshere"></div>
</div>
</div>
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
        <input id="submit" class="btn btn-primary" type="submit" name="submit"
        value="Edit User">
</div>
</div>
</form>
<?php
} else { // The user could not be validated
        echo '<p class="text-center">
                This page has been accessed in error.</p>';
}
mysqli_stmt_free_result($q);
mysqli_close($dbcon);
}
catch(Exception $e)
{
        print "The system is busy. Please try later";
        //print "An Exception occurred.Message: " . $e->getMessage();
}catch(Error $e)
{
        print "The system is currently busy. Please try again later";
        //print "An Error occurred. Message: " . $e->getMessage();
}
?>

Listing 6-6Creating a Screen for Editing the Title, Address, and Phone Number (process_edit_address.php

)

代码的解释

大部分代码你以前都见过。不允许管理员查看用户的密码。密码在放入数据库之前已经过哈希处理。因此,从数据库中提取的密码将不是可读的格式。管理员不会从查看哈希密码中获得任何好处。

注意

本章不包括某些文件的列表,因为其中一些与第五章中的相同。为了检查代码,在你的文本编辑器中查看章节 6 的可下载文件。

摘要

在本章中,我们从第五章创建了数据库的副本。我们为数据库创建了一个新的名称和密码。然后,我们创建了两个表。本章描述了导入 SQL 文件的过程,并提供了创建表格的快速方法。我们还发现了如何在表格中插入额外的列(标题)。作为使用 SQL 文件的替代方法,提供了足够的信息,使您能够从头开始创建填充的数据库表。

接下来,我们整理了文件系统,将所有包含的文件放到一个名为 includes 的文件夹中。我们讨论了安全性,简要提到了净化,并研究了验证。然后我们创建了一个新的更安全的注册页面。我们学习了如何创建用于搜索和编辑标题、地址和电话号码的页面。

在下一章中,您将学习如何在最后一刻做出不可避免的更改,以及如何将数据库迁移到远程主机。将给出如何备份表的建议。我们还将在 catch 例程中提供电子邮件和日志功能。

七、迁移到主机并备份您的网站数据库

你可能认为在第六章中创建的数据库驱动的网站已经完成,可以移植到主机上了。然而,在现实世界中,客户可能会在最后一刻要求一些更改。为了减少最后一分钟的变更,客户应该始终参与开发过程。网站的每个阶段完成后,客户应该“签署”变更,以表明他们接受网站的当前版本。让客户参与进来至关重要。一个网页设计者/开发者不可能预见到一个客户可能期望在网站上实现的一切。即使客户没有最新的变更,当新法律实施时,仍然可能有必要的变更,需要额外的网站或数据库调整。在这一章中,我们会做一些最后的修改,这些修改可能是在我们的网站完成之前由客户建议的。我们还将上传我们的数据库和网站到远程主机,并备份所有信息。

完成本章后,您将能够

  • 创建允许用户更改自己的个人信息的网站

  • 创建安全的用户反馈表

  • 将数据库迁移到远程主机

  • 备份数据库

做最后的改变

在我们的示例中,我们将假设客户已经请求了一些最后的更改,如下所示:

  • 为了减轻会员秘书的工作量,我们被要求允许注册会员更新他们自己的详细资料。例如,电话号码、地址和电子邮件地址可以改变。另外,有些人可能需要更改他们的头衔或名字。我们同意允许注册会员更新他们的头衔、姓名、地址、电话号码和电子邮件地址。

  • 客户要求提供一份查询表,以便用户在注册成为会员之前可以询问有关该组织的更多信息。我们同意创建一个安全的查询表单。

让我们从创建一个新的数据库和表开始。

创建新数据库

我们在前一章中创建的数据库需要进行最后的修改,但是为了避免混淆,我们将在名为 migrate 的新版本数据库中实现这些修改。在 www.apress.com 页面下载第章第七章的文件,并加载到新的 migrate 文件夹,使用以下步骤:

img/314857_2_En_7_Fig1_HTML.jpg

图 7-1

phpMyAdmin 中的导入接口

  1. 在 XAMPP htdocs 或 EasyPHP eds-www 文件夹中,新建一个名为 migrate 的文件夹。

  2. 从这本书的第一页开始。下载并解压第七章的文件到你的新文件夹。

  3. 在浏览器的地址栏中,输入http://localhost/phpmyadmin/访问 phpMyAdmin。

  4. 单击 databases 选项卡,创建名为 migrate 的数据库,从下拉排序列表中选择 utf8_general_ci,然后单击 Go。

  5. 在左侧面板中,单击数据库 migrate 旁边的框。

  6. 不要在下一个屏幕上输入任何内容,而是单击 Import 选项卡。

  7. 点击浏览按钮(见图 7-1 )并导航到您的 SQL 转储文件 users.sql 所在的位置。

  8. 点击 users.sql 文件,然后点击打开按钮;该字段将填充转储文件的 URL。请注意,转储文件包含用户和价格表。

  9. 确保下拉菜单中的字符集是 utf-8,并且格式显示为 SQL。

  10. 单击开始。

  11. 在左侧面板中,单击数据库迁移,并在下一个屏幕中单击特权选项卡。

  12. 向下滚动并单击添加用户帐户。然后输入以下详细信息:

*   *用户名* : trevithick

*   *主机选择*:本地

*   *密码* : l0c0m0t1v3
  1. 向下滚动到“全局权限”,然后单击“全部选中”旁边的框。

  2. 向下滚动并在右侧单击“Go”。

用于连接数据库的下载文件的详细信息

本章的下载文件中已经为您创建了文件 mysqli_connect.php 。内容如下所示:

<?php
// This creates a connection to the migrate database and to MySQL,
// It also sets the encoding.
// Set the access details as constants:
Define ('DB_USER', 'trevithick');
Define ('DB_PASSWORD', 'l0c0m0t1v3');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'migrate');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');

警告

请记住以管理员身份登录以查看成员表。登录邮箱为jsmith@outcook.com,密码为 d0g3b0dy。

会员秘书已经要求改变,以便会员可以在有限的范围内更新他们自己的记录。这是数据库驱动网站的一个常见特征。例如,在 PayPal,会员可以更新他们的个人资料(他们自己的账户信息)。已经规定成员可以更改密码;因此,在随后的成员更新界面中将不再包含该功能。

允许成员更新他们自己的记录

当成员登录后,他们将被允许更新自己的帐户。他们将无法访问任何其他成员的帐户;这可以通过会话来控制。该成员的修正案将得到验证和清理。如果成员想要更改密码,他们必须使用标题菜单上的新密码按钮。出于安全原因,可以修改的项目有限,如下所示:

  • 头衔、名字、姓氏、电子邮件地址、邮政地址和电话号码

用户具有受限的访问权限,只允许他们编辑自己的信息。要做到这一点,我们必须具备识别个体成员的能力。成员秘书使用搜索页面来确定要编辑的记录。该屏幕不向个人成员提供,因为他们别无选择,只能编辑自己的记录。因此,我们需要在用户登录时识别他们,并将他们的身份保存在一个会话变量中,该变量可以在稍后显示的编辑您的帐户页面上使用。

在登录页面中,我们之前通过查看 user_level 来确定用户是成员还是管理员。user_level 中的值也决定了他们可以访问哪些页面。这个级别保存在一个会话变量中,然后由每个受限页面顶部的会话代码(安全防护)进行检查。我们可以对登录代码进行微小的更改,以便能够使用“编辑您的帐户”页面顶部的会话代码(security guard)来确定可以编辑的用户信息。

if (password_verify($p, $row[1])) {
session_start();
// Ensure that the user level is an integer.
$_SESSION['user_level'] = (int) $row[3];
$_SESSION['user_id'] = (int) $row[0]; // Determine individual user

唯一需要的更改是在前面的代码片段中添加最后一行。user_id 是数据库中的记录行号,保存在一个同名的会话变量中。这唯一地标识了用户。这种标识是安全的,因为会话变量只能在活动会话期间访问。用户必须登录,如该代码段中的 if 语句所示,以便创建会话用户 ID 和用户级别。我们现在可以在编辑帐户页面中使用这些信息。

但是,在我们这样做之前,让我们更改成员专用页面的标题,以便注册成员可以查看和修改他们的记录。修改图 7-2 中所示的割台以允许这种情况。

img/314857_2_En_7_Fig2_HTML.jpg

图 7-2

成员页面标题中的“新建您的帐户”按钮

清单 7-2 显示了修改后的标题代码。这是在包含文件夹中下载的代码。

<div class="col-sm-2">
    <img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
    <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
    <nav class="col-sm-2">
           <div class="btn-group-vertical btn-group-sm" role="group"
                   aria-label="Button Group">
                           <button type="button" class="btn btn-secondary"
                   onclick="location.href = 'logout.php'" >Logout</button>
                           <button type="button" class="btn btn-secondary"
                   onclick="location.href = 'edit_your_account.php'">
                           Your Account</button>
                           <button type="button" class="btn btn-secondary"
                   onclick="location.href =
                   'safer-register-password.php'">New Password</button>
</div>
    </nav>

Listing 7-2Adding a New Menu Button to the Header of the Members Page

点击“您的帐户”按钮,会显示一个新页面,如图 7-3 所示。

img/314857_2_En_7_Fig3_HTML.jpg

图 7-3

新的“编辑您的帐户详细信息”页面

会员页面和更新屏幕强调了注销的重要性。编辑字段并点击“编辑您的记录”按钮时,会出现确认信息,如图 7-4 所示。通过再次显示详细信息,用户在注销之前还有机会更改任何不正确的信息。

img/314857_2_En_7_Fig4_HTML.jpg

图 7-4

更新已确认

清单 7-4a 显示了图 7-3 和 7-4 所示屏幕的代码。

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        require("cap.php");
}
session_start();
//                                                                                       #1
if (isset($_SESSION['user_id']) && ($_SESSION['user_level'] == 0)){
$id = filter_var( $_SESSION['user_id'], FILTER_SANITIZE_STRING);
      define('ERROR_LOG',"errors.log");
} else {
      header("Location: login.php");
      exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Edit Your Account Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
       href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
<script src="verify.js"></script>
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
        padding:20px;">
<?php include('includes/header_members_account.php'); ?>

</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
        <ul class="nav nav-pills flex-column">
                <?php include('includes/nav.php'); ?>
        </ul>
  </nav>
<?php
try {
require ('mysqli_connect.php');
// Has the form been submitted?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $errors = array();
//Is the title present? If it is, sanitize it
    $title = filter_var( $_POST['title'], FILTER_SANITIZE_STRING);
        if ((!empty($title)) && (preg_match('/[a-z\.\s]/i',$title)) &&
                (strlen($title) <= 12)) {
                //Sanitize the trimmed title
                $titletrim = $title;
        }else{
                $titletrim = NULL; // Title is optional
        }
// Trim the first name
   $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
if ((!empty($first_name)) && (preg_match('/[a-z\s]/i',$first_name)) &&
               (strlen($first_name) <= 30)) {
               //Sanitize the trimmed first name

               $first_nametrim = $first_name;
               }else{
       $errors[] =
'First name missing or not alphabetic and space characters. Max 30';
       }
//Is the last name present? If it is, sanitize it
$last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
if ((!empty($last_name)) && (preg_match('/[a-z\-\s\']/i',$last_name)) &&
               (strlen($last_name) <= 40)) {
       //Sanitize the trimmed last name
       $last_nametrim = $last_name;
}else{
       $errors[] =
'Last name missing or not alphabetic, dash, quote or space. Max 30.';
       }
// Check that an email address has been entered
$emailtrim = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
if ((empty($emailtrim)) || (!filter_var($emailtrim, FILTER_VALIDATE_EMAIL))
                       || (strlen($emailtrim > 60))) {
               $errors[] = 'You forgot to enter your email address';
               $errors[] = ' or the e-mail format is incorrect.';
      }
//Is the 1st address present? If it is, sanitize it
$address1 = filter_var( $_POST['address1'], FILTER_SANITIZE_STRING);
if ((!empty($address1)) && (preg_match('/[a-z0-9\.\s\,\-]/i', $address1)) &&
  (strlen($address1) <= 30)) {
        //Sanitize the trimmed 1st address
        $address1trim = $address1;
}else{
        $errors[] =
'Missing address. Numeric, alphabetic, period, comma, dash, space. Max 30.';
}
//If the 2nd address is present? If it is, sanitize it
$address2 = filter_var( $_POST['address2'], FILTER_SANITIZE_STRING);
if ((!empty($address2)) && (preg_match('/[a-z0-9\.\s\,\-]/i', $address2)) &&
  (strlen($address2) <= 30)) {
        //Sanitize the trimmed 2nd address

        $address2trim = $address2;
}else{
        $address2trim = NULL;
}
//Is the city present? If it is, sanitize it
$city = filter_var( $_POST['city'], FILTER_SANITIZE_STRING);
if ((!empty($city)) && (preg_match('/[a-z\.\s]/i', $city)) &&
  (strlen($city) <= 30)) {
        //Sanitize the trimmed city
        $citytrim = $city;
}else{
        $errors[] =
'Missing city. Only alphabetic, period and space. Max 30.';
        }
//Is the state or country present? If it is, sanitize it
$state_country = filter_var( $_POST['state_country'], FILTER_SANITIZE_STRING);
if ((!empty($state_country)) && (preg_match('/[a-z\.\s]/i', $state_country))
       && (strlen($state_country) <= 30))      {
       //Sanitize the trimmed state or country
       $state_countrytrim = $state_country;
}else{
       $errors[] =
       'Missing state/country. Only alphabetic, period and space. Max 30.';
//Is the zip code or post code present? If it is, sanitize it
$zcode_pcode = filter_var( $_POST['zcode_pcode'], FILTER_SANITIZE_STRING);
$string_length = strlen($zcode_pcode);
if ((!empty($zcode_pcode)) && (preg_match('/[a-z0-9\s]/i', $zcode_pcode))  &&
   ($string_length <= 30) && ($string_length >= 5)) {
        //Sanitize the trimmed zcode_pcode
    $zcode_pcodetrim = $zcode_pcode;
}else{
        $errors[] =
'Missing zip code or post code. Alphabetic, numeric, space only max 30 chars';
}
//Is the phone number present? If it is, sanitize it
$phone = filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
if ((!empty($phone)) && (strlen($phone) <= 30)) {
       //Sanitize the trimmed phone number
       $phonetrim = (filter_var($phone, FILTER_SANITIZE_NUMBER_INT));
       $phonetrim = preg_replace('/[⁰-9]/', ", $phonetrim);
}else{
       $phonetrim = NULL;
}
if (empty($errors)) { // If everything's OK.
        //  make the query
        $q = mysqli_stmt_init($dbcon);
        $query =
                'SELECT userid FROM users WHERE email=? AND userid !=?';
       mysqli_stmt_prepare($q, $query);
        // bind $id to SQL Statement
        mysqli_stmt_bind_param($q, 'si', $emailtrim, $id);
        // execute query

        mysqli_stmt_execute($q);
        $result = mysqli_stmt_get_result($q);
        if (mysqli_num_rows($result) == 0) {
        // e-mail does not exist in another record
                // Make the update query:
                 $query =
'UPDATE users SET title=?, first_name=?, last_name=?, email=?, ';
                 $query .=
'address1=?, address2=?, city=?, state_country=?, zcode_pcode=?, ';
                 $query .=
'phone=?';
       $query .= ' WHERE userid=?';
        $q = mysqli_stmt_init($dbcon);
         mysqli_stmt_prepare($q, $query);
        // bind values to SQL Statement
               mysqli_stmt_bind_param($q, 'ssssssssssi', $titletrim,
        $first_nametrim, $last_nametrim,
        $emailtrim, $address1trim, $address2trim, $citytrim,
        $state_countrytrim,
        $zcode_pcodetrim, $phonetrim, $id);
       // execute query
       mysqli_stmt_execute($q);
               if (mysqli_stmt_affected_rows($q) == 1) { // Update OK
                       // Echo a message if the edit was satisfactory:
                       $errorstring = 'The user has been edited.';
                       echo "<p class=' text-center col-sm-2'
                       style='color:green'>$errorstring</p>";
               } else { // Echo a message if the query failed.
                       $errorstring =
       'The user could not be edited. Did you change anything?';
       $errorstring .=
       ' We apologize for any inconvenience.'; // Public message.
                       echo "<p class=' text-center col-sm-2'
                       style='color:red'>$errorstring</p>";
       //echo '<p>' . mysqli_error($dbcon) . '<br />Query: ' . $q . '</p>';
       // Debugging message.
       // Message above is only for debug and should not display sql
       }
}
} else { // Display the errors.
        // ---------------Process User Errors---------------
        // Display the users entry errors
        $errorstring = 'Error! The following error(s) occurred: ';
        foreach ($errors as $msg) { // Print each error.
                $errorstring .= " - $msg<br>\n";
                }
        $errorstring .= 'Please try again.';
        echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
        }// End of if (empty($errors)) IF.
} // End of the conditionals

// Select the user's information:
$query = "SELECT title, first_name, last_name, email, address1,
         address2, city, state_country, zcode_pcode, phone ";
$query .=" FROM users WHERE userid=?";
// id was retrieved from database prepared not needed
$q = mysqli_stmt_init($dbcon);
 mysqli_stmt_prepare($q, $query);
 // bind $id to SQL Statement
mysqli_stmt_bind_param($q, 'i', $id);
 // execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
if (mysqli_num_rows($result) == 1) { // Valid user ID, display the form.
        // Get the user's information:
        $row = mysqli_fetch_array ($result, MYSQLI_ASSOC);
        // Create the form:
?>
  <!-- Validate Input -->
<div class="col-sm-8">
<h2 class="h2 text-center">Edit Your Account Details</h2>
<h3 class="text-center">
For your own security, please remember to log out!</h3>
<form action="edit_your_account.php" method="post"                              <!-- #2 -->
name="editform" id="editform">
 <div class="form-group row">
    <label for="title" class="col-sm-4 col-form-label
        text-right">Title:</label>
 <div class="col-sm-8">
      <input type="text" class="form-control" id="title" name="title"
        placeholder="Title" maxlength="12"
        pattern='[a-zA-Z][a-zA-Z\s\.]*'
        title="Alphabetic, period and space max 12 characters"
        value=
             "<?php if (isset($row['title']))
             echo htmlspecialchars($row['title'], ENT_QUOTES); ?>" >
 </div>
</div>
<div class="form-group row">
    <label for="first_name" class="col-sm-4 col-form-label text-right">
               First Name*:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="first_name"
                name="first_name"
                pattern="[a-zA-Z][a-zA-Z\s]*"
                title="Alphabetic and space only max of 30 characters"
                placeholder="First Name" maxlength="30" required
                value=
                "<?php if (isset($row['first_name']))
                echo htmlspecialchars($row['first_name'], ENT_QUOTES); ?>" >
 </div>

 </div>
 <div class="form-group row">
    <label for="last_name" class="col-sm-4 col-form-label text-right">
               Last Name*:</label>
 <div class="col-sm-8">
      <input type="text" class="form-control" id="last_name" name="last_name"
        pattern="[a-zA-Z][a-zA-Z\s\-\']*"
        title="Alphabetic, dash, quote and space only max of 40 characters"
        placeholder="Last Name" maxlength="40" required
        value=
              "<?php if (isset($row['last_name']))
              echo htmlspecialchars($row['last_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="email" class="col-sm-4 col-form-label text-right">
                E-mail*:</label>
    <div class="col-sm-8">
      <input type="email" class="form-control" id="email" name="email"
        placeholder="E-mail" maxlength="60" required
        value=
              "<?php if (isset($row['email']))
              echo htmlspecialchars($row['email'], ENT_QUOTES); ?>" >
</div>

</div>
<div class="form-group row">
    <label for="address1" class="col-sm-4 col-form-label
        text-right">Address*:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="address1" name="address1"
        pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
        title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
          placeholder="Address" maxlength="30" required
          value=
               "<?php if (isset($row['address1']))
               echo htmlspecialchars($row['address1'], ENT_QUOTES); ?>" >
 </div>
</div>
<div class="form-group row">
    <label for="address2" class="col-sm-4 col-form-label text-right">Address:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="address2" name="address2"
        pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
        title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
        placeholder="Address" maxlength="30"
        value=
             "<?php if (isset($row['address2']))
             echo htmlspecialchars($row['address2'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="city" class="col-sm-4 col-form-label
        text-right">City*:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="city" name="city"
        pattern="[a-zA-Z][a-zA-Z\s\.]*"
        title="Alphabetic, period and space only max of 30 characters"
        placeholder="City" maxlength="30" required
        value=
             "<?php if (isset($row['city']))
             echo htmlspecialchars($row['city'], ENT_QUOTES); ?>" >
</div>
</div>

<div class="form-group row">
    <label for="state_country" class="col-sm-4 col-form-label text-right">
               Country/state*:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="state_country"
               name="state_country"
      pattern="[a-zA-Z][a-zA-Z\s\.]*"
          title="Alphabetic, period and space only max of 30 characters"
          placeholder="State or Country" maxlength="30" required
          value=
               "<?php if (isset($row['state_country']))
               echo htmlspecialchars($row['state_country'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="zcode_pcode" class="col-sm-4 col-form-label text-right">
               Zip/Postal Code*:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="zcode_pcode"
               name="zcode_pcode"
        pattern="[a-zA-Z0-9][a-zA-Z0-9\s]*"
        title="Alphabetic, period and space only max of 30 characters"
        placeholder="Zip or Postal Code" minlength="5"
              maxlength="30" required
        value=
             "<?php if (isset($row['zcode_pcode']))
             echo htmlspecialchars($row['zcode_pcode'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="phone" class="col-sm-4 col-form-label
               text-right">Telephone:</label>
    <div class="col-sm-8">
      <input type="tel" class="form-control" id="phone" name="phone"
        placeholder="Phone Number" maxlength="30"
        value=
             "<?php if (isset($row['phone']))
             echo htmlspecialchars($row['phone'], ENT_QUOTES); ?>" >
</div>
</div>

      <input type="hidden" name="id" value="' . $id . '">
<div class="form-group row">
  <label class="col-sm-4 col-form-label"></label>
  <div class="col-sm-8">
  <div class=
"float-left g-recaptcha" style="padding-left: 80px;"
data-sitekey="6LcrQ1wUAAAAAPxlrAkLuPdpY5qwS9rXF1j46fhq"></div>
</div>
</div>
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
    <div class="col-sm-8 text-center">
        <input id="submit" class="btn btn-primary" type="submit"
                name="submit" value="Edit Your Record">
</div>
</div>
</form>
</div>
<?php
   }
 if(!isset($errorstring)) {
        echo '<aside class="col-sm-2">';
        include('includes/info-col.php');
        echo '</aside>';
        echo '</div>';
        echo '<footer class="jumbotron text-center row col-sm-14"
                style="padding-bottom:1px; padding-top:8px;">';
 }
 else
 {
        echo '<footer class="jumbotron text-center col-sm-12"
        style="padding-bottom:1px; padding-top:8px;">';
 }
  include('includes/footer.php');
  echo "</footer>";
  echo "</div>";

}
catch(Exception $e) // We finally handle any problems here
   {
      // print "An Exception occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      // $date = date('m.d.y h:i:s’);
      // $errormessage = $e->getMessage();
      // $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
      // error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      // error_log(“Date/Time: $date – Exception Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Exception Error \nFrom: Error Log <errorlog@helpme.com>" . "\r\n");
   }
   catch(Error $e)

   {
       // print "An Error occurred. Message: " . $e->getMessage();
       print "The system is busy please try later";
       // $date = date('m.d.y h:i:s');
       // $errormessage = $e->getMessage();
       // $eMessage = $date . " | Error | " , $errormessage . |\n";
       // error_log($eMessage,3,ERROR_LOG);
       // e-mail support person to alert there is a problem
   //  error_log("Date/Time: $date – Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Error \nFrom: Error Log <errorlog@helpme.com>"."\r\n");
   }
?>
</body>
</html>

Listing 7-4aCreating an Interface for Members to Edit Their Accounts (edit_your_account.php)

代码的解释

本节解释代码。

<?php
session_start();
//                                                                                       #1
if (isset($_SESSION['user_id']) && ($_SESSION['user_level'] == 0)){
$id = filter_var( $_SESSION['user_id'], FILTER_SANITIZE_STRING);
} else {
header("Location: login.php");
exit();
}
?>

仅当会话变量 user_id 中有值并且用户级别为 0 时,才允许访问页面。当用户使用登录页面登录时,user_id 被设置为会话变量。因此,只要会话仍处于活动状态,该值就有效。然后,user_id 被保存在$id 变量中,因为它将用于显示和更新用户记录。

form action="edit_your_account.php" method="post"                                 <!--#2-->
        onsubmit="return checked();"
        name="regform" id="regform">
 <div class="form-group row">
        <label for="title" class="col-sm-4 col-form-label text-right">
                Title:</label>
 <div class="col-sm-8">
        <input type="text" class="form-control" id="title" name="title"
               placeholder="Title" maxlength="12"
               pattern='[a-zA-Z][a-zA-Z\s\.]*'
               title="Alphabetic, period and space max 12 characters"
               value=
                       "<?php if (isset($row['title']))
               echo htmlspecialchars($row['title'], ENT_QUOTES); ?>" >
  </div>
  </div>

这段代码显示了用于编辑成员帐户的粘性表单的一部分。\(row 数组是通过使用 SELECT 语句从数据库表中提取用户记录而创建的。代码与我们之前看到的类似,只是我们从\)row 而不是$_POST 中提取数据。

文件process _ edit _ your _ account中的所有清理和验证与第六章中所示的相同。所以这里就不讨论了。你可以用文本编辑器从下载的章节 7 文件中查看这个文件的内容。

让我们分开一会儿。在前面的章节中,我们没有花时间指出,在大多数情况下,我们的网页已经支持移动(小屏幕)。图 7-5 显示了移动尺寸版本的 safer-register-page。

img/314857_2_En_7_Fig5_HTML.jpg

图 7-5

移动尺寸注册页面

我们在前面的章节中使用的引导类给了我们的页面这种能力。在接下来的章节中,我们将对引导代码做一些细微的调整,以个性化我们的按钮,并隐藏一些从移动设备上看到的菜单。

会员网站和数据库迁移教程到此结束。但是,我们可以对这个网站(或任何网站)进行一些有用的添加和更改,接下来我们会看到。

安全反馈表

联系我们表单与数据库没有严格的关系;然而,它是一个交互式元素,提供了 PHP 代码的优秀应用。

电子邮件和反馈表格是最受欢迎的联系方式,可以让用户轻松地与网站所有者交流。不幸的是,这两种联系方式都可能被滥用。电子邮件地址被收集并出售给垃圾邮件发送者。这两种行为都会给网站所有者带来困扰和大量垃圾邮件。黑客可以劫持一个表单,并用它向网站所有者频繁发送垃圾邮件。这些通常包含可以通过文本区域中的链接激活的恶意软件。如上所述,任何人都不应该点击电子邮件中嵌入的链接,除非绝对确定该链接是有效的。本节描述了降低这些风险的一些选项。

设计反馈表时,我们需要考虑以下几点:

  • 盲人和视力严重受损的用户将使用屏幕阅读器来阅读和回复表单。必须遵守可访问性规则。

  • 表单处理程序中必须内置过滤器,以防止表单被恶意劫持。

    注意

    许多政府机构要求公司遵守网站的可访问性指南。许多国家不会向不遵守无障碍指南的公司提供政府合同。这些指导原则通常不难实施,并且通过为视障用户提供对网站信息的访问来增加组织的商誉。

反馈回复看起来像什么?

以下是从安全表单及其处理程序收到的真实电子邮件:

------------------------------------------------------------
Name of sender: Andrew Eastman
Email of sender: aeastman@myisp.co.uk
Telephone No: 01111 222333
XP?: No
Vista?: No
Windows7?: No
Windows 8: Yes
Laptop?: Yes
Desktop?; No
------------------------- MESSAGE ----------------------------
How can I change back to Windows 7?
-------------------------------------------------------------------

反馈表

图 7-6 显示了一个典型的反馈表。

img/314857_2_En_7_Fig6_HTML.jpg

图 7-6

反馈表

反馈页面的代码专注于防止 URL 被输入,因为这是机器人填写表单的主要问题。

清单 7-6a 提供了显示表单的代码。

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        require("cap.php");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Feedback Form</title>
  <meta charset="utf-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->

  <link rel="stylesheet"
        href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row col-sm-14"
        style="margin-bottom:2px; background:linear-gradient(white, #0073e6);
        padding:20px;">
        <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
       <ul class="nav nav-pills flex-column">
              <?php include('includes/nav.php'); ?>
       </ul>
  </nav>
  <!-- Validate Input -->
<div class="col-sm-8">
<h3 class="text-center">Contact Us!</h3>
<h5 class="text-center"><strong>Address:</strong>
1 The Street, Townsville, AA6 8PF, <strong>Tel:</strong> 01111 800777</h5>
<h5 class="text-center"><strong>To email us:</strong>
        Please use this form and click the Send button at the bottom.</h5>
<h4 class="text-center">Essential items are marked with an asterisk</h4>
<!--                                                                                  #1-->
<form action="feedback-handler.php" method="post" name="feedbackform"
        id="feedbackform">
<!--START OF TEXT FIELDS-->
<div class="form-group row">
        <label for="first_name" class="col-sm-4 col-form-label text-right">
               First Name*:</label>

<div class="col-sm-8">
        <input type="text" class="form-control" id="first_name"
               name="first_name"
               pattern="[a-zA-Z][a-zA-Z\s]*"
               title="Alphabetic and space only max of 30 characters"
               placeholder="First Name" maxlength="30" required
               value=
                       "<?php if (isset($row['first_name']))
               echo htmlspecialchars($row['first_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="last_name" class="col-sm-4 col-form-label text-right">
                Last Name*:</label>
<div class="col-sm-8">
         <input type="text" class="form-control" id="last_name" name="last_name"
                pattern="[a-zA-Z][a-zA-Z\s\-\']*"
                title="Alphabetic, dash, quote and space only max of 40 characters"
                placeholder="Last Name" maxlength="40" required
                value=
                        "<?php if (isset($row['last_name']))
                echo htmlspecialchars($row['last_name'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
         <label for="email" class="col-sm-4 col-form-label text-right">
                E-mail*:</label>
<div class="col-sm-8">
         <input type="email" class="form-control" id="email" name="email"
                placeholder="E-mail" maxlength="60" required
                value=
                        "<?php if (isset($row['email']))
                echo htmlspecialchars($row['email'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="phone" class="col-sm-4 col-form-label
                text-right">Telephone:</label>
<div class="col-sm-8">
       <input type="tel" class="form-control" id="phone" name="phone"
                placeholder="Phone Number" maxlength="30"
                value=
                        "<?php if (isset($row['phone']))
                echo htmlspecialchars($row['phone'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
    <label for="" class="col-sm-4 col-form-label text-right"></label>
<h5 class="col-sm-8 text-center">Would you like us to send a Brochure?
        (check box):</h5>
</div>
<div class="form-group row">

    <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="checkbox col-sm-8 text-center">Yes
        <input class="" type="checkbox" name="brochure" id="brochure" value="yes">
</div>
</div>
<div class="form-group row">
    <label for="" class="col-sm-4 col-form-label text-right"></label>
<h6 class="col-sm-8 text-center">
        Please enter address if you checked the brochure box above</h6>
</div>
<div class="form-group row">
        <label for="address1" class="col-sm-4 col-form-label
             text-right">Address*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="address1" name="address1"
               pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
               title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
               placeholder="Address" maxlength="30" required
               value=
                       "<?php if (isset($row['address1']))
               echo htmlspecialchars($row['address1'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="address2" class="col-sm-4 col-form-label
               text-right">Address:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="address2" name="address2"
               pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-]*"
               title="Alphabetic, numbers, period, comma, dash and space only max of 30 characters"
               placeholder="Address" maxlength="30"
               value=
                       "<?php if (isset($row['address2']))
               echo htmlspecialchars($row['address2'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="city" class="col-sm-4 col-form-label
               text-right">City*:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="city" name="city"
               pattern="[a-zA-Z][a-zA-Z\s\.]*"
               title="Alphabetic, period and space only max of 30 characters"
               placeholder="City" maxlength="30" required
               value=
                       "<?php if (isset($_POST['city']))
               echo htmlspecialchars($_POST['city'], ENT_QUOTES); ?>" >
</div>

</div>
<div class="form-group row">
        <label for="state_country" class="col-sm-4 col-form-label text-right">
               Country/state*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="state_country"
               name="state_country"
               pattern="[a-zA-Z][a-zA-Z\s\.]*"
               title="Alphabetic, period and space only max of 30 characters"
               placeholder="State or Country" maxlength="30" required
               value=
                       "<?php if (isset($_POST['state_country']))
               echo htmlspecialchars($_POST['state_country'], ENT_QUOTES); ?>" >
 </div>
 </div>
 <div class="form-group row">
         <label for="zcode_pcode" class="col-sm-4 col-form-label text-right">
                Zip/Postal Code*:</label>
<div class="col-sm-8">
        <input type="text" class="form-control" id="zcode_pcode"
               name="zcode_pcode"
               pattern="[a-zA-Z0-9][a-zA-Z0-9\s]*"
               title="Alphabetic, period and space only max of 30 characters"
               placeholder="Zip or Postal Code" minlength="5" maxlength="30"
               required
               value=
                       "<?php if (isset($_POST['zcode_pcode']))
               echo htmlspecialchars($_POST['zcode_pcode'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label text-right"></label>
<h5 class="col-sm-8 text-center">
        Would you like to receive emailed newsletters?</h5>
</div>
<fieldset class="form-group row">
                <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">                                               <!--#2 -->
<div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" name="letter"
              id="letter" value="yes" checked>
        <label class="form-check-label" for="letter">
            Yes
       </label>

</div>
<div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" name="noletter"
                id="noletter" value="no">
       <label class="form-check-label" for="noletter">
            No
       </label>
</div>
</div>
</fieldset>
<div class="form-group row">                                                      <!--#3-->
        <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
        <label for="comment">Please enter your message below</label>
        <textarea class="form-control" id="comment" name="comment" rows="12"
                cols="40"
                value=
                        "<?php if (isset($_POST['comment']))
                echo htmlspecialchars($_POST['comment'], ENT_QUOTES); ?>" >
        </textarea>
 </div>
 </div>
 <div class="form-group row">
       <label class="col-sm-4 col-form-label"></label>
 <div class="col-sm-8">
 <div class="g-recaptcha" style="margin-left: 80px;"
       data-sitekey=”placeyoursitekeyhere"></div>
 </div>
</div>
<div class="form-group row">
        <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8 text-center">
        <input id="submit" class="btn btn-primary" type="submit" name="submit"
                value="Send">
</div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
                <?php include('includes/info-col.php'); ?>
        </aside>
<!-- Footer Content Section -->
</div>
<footer class="jumbotron text-center row"
        style="padding-bottom:1px; padding-top:8px;">
        <?php include('includes/footer.php'); ?>
</footer>
</body>
</html>

Listing 7-6aCreating the Contact Us

Form (feedback-form.php)

代码的解释

本节解释代码。

<!--                                                                                  #1-->
<form action="feedback-handler.php" method="post" name="feedbackform"
      id="feedbackform">

来自反馈表单的虚假反馈将被表单调用的 PHP 处理文件阻止。如果在任何文本字段中输入了 URL,在单击提交按钮后,表单处理程序将显示一条错误消息。这将阻止虚假回复。清单 7-3b 显示了处理程序的 PHP 代码。

<div class="col-sm-8 text-center">                                               <!--#2 -->
<div class="form-check form-check-inline">
          <input class="form-check-input" type="radio" name="letter"
             id="letter" value="yes" checked>
        <label class="form-check-label" for="letter">
            Yes
       </label>
</div>
<div class="form-check form-check-inline">
        <input class="form-check-input" type="radio" name="noletter"
                id="noletter" value="no">
        <label class="form-check-label" for="noletter">
             No
        </label>
</div>
Radio buttons are used to determine if the user wants to join the e-mail newsletter.
<div class="form-group row">                                                      <!--#3-->
        <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
        <label for="comment">Please enter your message below</label>
        <textarea class="form-control" id="comment" name="comment" rows="12"
               cols="40"
               value=
                       "<?php if (isset($_POST['comment']))
               echo htmlspecialchars($_POST['comment'], ENT_QUOTES); ?>" >
        </textarea>
 </div>
 </div>

Textarea 用于收集要发送的消息。该邮件将在服务器端进行清理。所有其他代码类似于第六章所示的安全寄存器页面中的代码。让我们看看清单 7-6b 中的 PHP 代码。

请注意,使用了虚假的电子邮件地址和 URL。用你客户的详细资料代替。

<?php
// Feedback form handler
// set the error and thank you pages                                                     #1
$formurl = "feedback_form.php" ;
$errorurl = "feedback/error.php" ;
$thankyouurl = "feedback/thankyou.php" ;
$emailerrurl = "feedback/emailerr.php" ;
$errorcommenturl =  "feedback/commenterror.php" ;
// set to the email address of the recipient
$mailto = "none@noone.com" ;
// Is first name present? If it is, sanitize it                                          #2
   $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
if ((!empty($first_name)) && (preg_match('/[a-z\s]/i',$first_name)) &&
                   (strlen($first_name) <= 30)) {
          //Save first name
          $first_nametrim = $first_name;
}else{
          $errors = 'yes';
}
//Is the last name present? If it is, sanitize it
$last_name = filter_var( $_POST['last_name'], FILTER_SANITIZE_STRING);
if ((!empty($last_name)) && (preg_match('/[a-z\-\s\']/i',$last_name)) &&
                  (strlen($last_name) <= 40)) {
          //Save last name
          $last_nametrim = $last_name;
}else{
          $errors = 'yes';
}
// Check that an email address has been entered correctly                                #3
$emailtrim = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
if ((empty($emailtrim)) || (!filter_var($emailtrim, FILTER_VALIDATE_EMAIL))
         || (strlen($emailtrim > 60))) {
         // if email is bad display error page
         header( "Location: $emailerrurl" );
       exit ;
}
// Is the phone number present? if so, sanitize it
$phone = filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
if ((!empty($phone)) && (strlen($phone) <= 30)) {
          //Sanitize and validate phone number
          $phonetrim = (filter_var($phone, FILTER_SANITIZE_NUMBER_INT));
          $phonetrim = preg_replace('/[⁰-9]/', ", $phonetrim);
}else{
          $phonetrim = NULL; // if not valid or missing do not save
}
//Is the 1st address present? If it is, sanitize it
$address1 = filter_var( $_POST['address1'], FILTER_SANITIZE_STRING);
if ((!empty($address1)) && (preg_match('/[a-z0-9\.\s\,\-]/i', $address1)) &&
          (strlen($address1) <= 30)) {
          //Save the 1st address
          $address1trim = $address1;

}else{
          $errors = 'yes';
}
//If the 2nd address is present? If it is, sanitize it
$address2 = filter_var( $_POST['address2'], FILTER_SANITIZE_STRING);
if ((!empty($address2)) && (preg_match('/[a-z0-9\.\s\,\-]/i', $address2)) &&
          (strlen($address2) <= 30)) {
          //Save the 2nd address
          $address2trim = $address2;
}else{
          $address2trim = NULL; // If missing or not valid do not save
}
//Is the city present? If it is, sanitize it
$city = filter_var( $_POST['city'], FILTER_SANITIZE_STRING);
if ((!empty($city)) && (preg_match('/[a-z\.\s]/i', $city)) &&
          (strlen($city) <= 30)) {
          //Save the city
          $citytrim = $city;
}else{
          $errors = 'yes';
}
//Is the state or country present? If it is, sanitize it
$state_country = filter_var( $_POST['state_country'], FILTER_SANITIZE_STRING
if ((!empty($state_country)) && (preg_match('/[a-z\.\s]/i', $state_country))
          && (strlen($state_country) <= 30))      {
          //Save the state or country
          $state_countrytrim = $state_country;
}else{
          $errors = 'yes';
}
//Is the zip code or post code present? If it is, sanitize it
$zcode_pcode = filter_var( $_POST['zcode_pcode'], FILTER_SANITIZE_STRING);
$string_length = strlen($zcode_pcode);
if ((!empty($zcode_pcode)) && (preg_match('/[a-z0-9\s]/i', $zcode_pcode))  &&
          ($string_length <= 30) && ($string_length >= 5)) {
          //Save the zcode_pcode
          $zcode_pcodetrim = $zcode_pcode;
}else{
          $errors = 'yes';
          }

$brochure = filter_var( $_POST['brochure'], FILTER_SANITIZE_STRING);                   //#4
if($brochure != "yes") {$brochure = "no";} // if not yes, then no
$letter = filter_var( $_POST['letter'], FILTER_SANITIZE_STRING);
if($letter != "yes") {$letter = "no"; } // if not yes, then no

$comment = filter_var( $_POST['comment'], FILTER_SANITIZE_STRING);                     //#5
if ((!empty($comment)) && (strlen($comment) <= 480)) {
          // remove ability to create link in email
          $patterns = array("/http/", "/https/", "/\:/","/\/\//","/www./");
          $commenttrim = preg_replace($patterns," ", $comment);
}else{    // if comment not valid display error page
          header( "Location: $errorcommenturl" );
          exit;
}

if (!empty($errors)) { // if errors display error page
         header( "Location: $errorurl" );
         exit ; }
// everything OK send e-mail                                                             #6
$subject = "Message from customer " . $first_nametrim . " " . $last_nametrim;
$messageproper =
"------------------------------------------------------------\n" .
"Name of sender: $first_nametrim $last_nametrim\n" .
"Email of sender: $emailtrim\n" .
"Telephone: $phonetrim\n" .
"brochure?: $brochure\n" .
"Address: $address1trim\n" .
"Address: $address2trim\n" .
"City: $citytrim\n" .
"Postcode: $zcode_pcodetrim\n" .
"Newsletter?:$letter\n" .
"------------------------- MESSAGE -------------------------\n\n" .
$commenttrim .
"\n\n------------------------------------------------------------\n" ;
mail($mailto, $subject, $messageproper, "From: \"$first_nametrim $last_nametrim\" <$emailtrim>" );
header( "Location: $thankyouurl" );
exit ;
?>

Listing 7-6bCreating the Feedback Form Handler (feedback-handler.php)

警告

表单处理程序需要一个电子邮件服务器驻留在执行代码的机器上。除非您安装了自己的电子邮件服务器,否则这段代码不会在您的计算机上执行。然而,大多数虚拟主机公司都提供电子邮件服务器。您可以将此代码上传到托管站点。然后向托管公司验证代码以访问电子邮件服务器。有时需要一些微小但重要的改变来完成这个过程。

代码的解释

本节解释代码。

// set the error and thank you pages                                                     #1
$formurl = "feedback_form.php" ;
$errorurl = "feedback/error.php" ;
$thankyouurl = "feedback/thankyou.php" ;
$emailerrurl = "feedback/emailerr.php" ;
$errorcommenturl =  "feedback/commenterror.php" ;
// set to the email address of the recipient
$mailto = "none@noone.com" ;

虚拟的电子邮件地址和 URL 将需要被真实的地址和 URL 所取代。五个文件被分配给变量。四行是指包含错误信息的页面。

// Is first name present? If it is, sanitize it                                          #2
   $first_name = filter_var( $_POST['first_name'], FILTER_SANITIZE_STRING);
if ((!empty($first_name)) && (preg_match('/[a-z\s]/i',$first_name)) &&
                   (strlen($first_name) <= 30)) {
          //Save first name
          $first_nametrim = $first_name;
}else{
          $errors = 'yes';
}

大多数变量的清理和验证类似于第六章中讨论的进程-寄存器-页面代码。然而,不是创建一个错误数组,而是将一个 errors 变量设置为 yes 以指示存在问题。电子邮件和评论是需要收集的最重要的信息。因此,我们将区别对待这些变量。

// Check that an email address has been entered correctly                                #3
$emailtrim = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
if  ((empty($emailtrim)) || (!filter_var($emailtrim, FILTER_VALIDATE_EMAIL))
          || (strlen($emailtrim > 60))) {
          // if email is bad display error page
          header( "Location: $emailerrurl" );
       exit ;
}

如果电子邮件无效,则没有理由继续尝试发送邮件。因此,向用户显示电子邮件错误页面。

$brochure = filter_var( $_POST['brochure'], FILTER_SANITIZE_STRING);                   //#4
if($brochure != "yes") {$brochure = "no";} // if not yes, then no
$letter = filter_var( $_POST['letter'], FILTER_SANITIZE_STRING);
if($letter != "yes") {$letter = "no"; } // if not yes, then no

如果用户没有点击小册子的“是”框,我们假设他们不想要它,并将变量设置为“否”。这也排除了任何其他可能的无效值。我们也对 e-mail letter 变量做了同样的处理。

$comment = filter_var( $_POST['comment'], FILTER_SANITIZE_STRING);                     //#5
if ((!empty($comment)) && (strlen($comment) <= 480)) {
          // remove ability to create link in email
          $patterns = array("/http/", "/https/", "/\:/","/\/\//","/www./");
          $commenttrim = preg_replace($patterns," ", $comment);
}else{    // if comment not valid display error page
          header( "Location: $errorcommenturl" );
          exit;
}

如果评论的格式不正确,也没有理由尝试发送消息。因此,我们称之为与注释相关的错误页面。但是,如果注释格式正确,可能会尝试包含 URL 链接,我们将通过删除协议头(http,https)来停用 URL 链接。preg_replace 函数将用空格替换 patterns 数组中显示的任何格式,从而删除可能有害的条目。我们假设客户可能发送了一个链接来提供信息。然而,出于安全原因,我们不希望它是可点击的。

// everything OK send e-mail                                                             #6
$subject = "Message from customer " . $first_nametrim . " " . $last_nametrim;
$messageproper =
"------------------------------------------------------------\n" .
"Name of sender: $first_nametrim $last_nametrim\n" .
"Email of sender: $emailtrim\n" .
"Telephone: $phonetrim\n" .
"brochure?: $brochure\n" .
"Address: $address1trim\n" .
"Address: $address2trim\n" .
"City: $citytrim\n" .
"Postcode: $zcode_pcodetrim\n" .
"Newsletter?:$letter\n" .
"------------------------- MESSAGE -------------------------\n\n" .
$commenttrim .
"\n\n------------------------------------------------------------\n" ;
mail($mailto, $subject, $messageproper, "From: \"$first_nametrim $last_nametrim\" <$emailtrim>" );
header( "Location: $thankyouurl" );
exit ;

这个代码块使用经过清理和验证的变量来构造电子邮件。注意每一项后面的句号;这些都很重要。代码\n 在每个项目之间插入一个换行符(或回车)。发送电子邮件时需要这种格式。最后两行使用 PHP 函数 mail()发送电子邮件。电子邮件发出后,用户被重定向到“谢谢”页面。

“谢谢”页面和错误消息

“谢谢”页面确认电子邮件已成功发送。我们提供了一个返回按钮,允许用户继续使用该网站,如果他们这样选择。图 7-7 显示“谢谢”页面,清单 7-7a 显示代码。你可以选择用主导航菜单替换返回主页按钮。

img/314857_2_En_7_Fig7_HTML.jpg

图 7-7

“谢谢”页面消息

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Thank you for your inquiry</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
      "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
      href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
      integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
      crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
      style=
"margin-bottom:2px; background:linear-gradient(white, #0073e6);
       padding:20px;">
       <?php include('../includes/thankyou-header.php'); ?>
</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('../includes/nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->

<div class="col-sm-8">
<h3 class="text-center" >Thank you for your inquiry.</h3>
<h3 class="text-center" >We will email an answer to you shortly.</h3>
</div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
                 <?php include('../includes/info-col.php'); ?>
        </aside>
  </div>
<div class="row col-sm-12" style="padding-left: 0px;  padding-bottom: 20px;">
<div class="col-sm-5"></div>
<nav class="col-sm-4 text-center">
<ul class="nav nav-pills">
<li class="nav-item">
          <a class="nav-link active" href="index.php">Return to Home Page</a>
</li>
</ul>
</nav>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('../includes/footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 7-7aCreating the “Thank You” Page (thankyou.php)

如果电子邮件未成功发送,将会出现一条说明性的错误消息。

为什么要使用错误页面而不是将一段文本回显到屏幕上?我们发现,我们的许多客户更喜欢独特的信息和页面提供的帮助,而不是常见的红色小错误信息,这些信息可能会被忽略或过于简短。如果您更喜欢将消息回显到页面,可以创建一个$error 数组来保存消息,就像我们在注册页面中所做的那样。

清单 7-7b 提供了空字段或无效字段的错误消息代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Error Message</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:30px">
<!-- Header Section -->
<header class="jumbotron text-center row"
        style=
"margin-bottom:2px; background:linear-gradient(white, #0073e6);
        padding:20px;">
        <?php include('../includes/thankyou-header.php'); ?>
</header>
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                <?php include('../includes/nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8">
<h4 class="text-center">
One or more of the essential items in the form has not been filled in.</h4>
<h4 class="text-center">
Essential items have an asterisk like this <span>*</span></h4>
<h4 class="text-center">
Return to the form by clicking the back button on your browser<br>
        and then fill in the missing items.</h4>

</div>
<!-- Right-side Column Content Section -->
       <aside class="col-sm-2">
       <?php include('../includes/info-col.php'); ?>
       </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;">
  <?php include('../includes/footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 7-7bCreating the Page for Empty Field Errors (error.php)

其他错误页面包含与此示例类似的格式,此处未列出。您可以从本章的下载文件中查看它们。

注意

您需要在主菜单中再添加一个按钮(包含/nav.php )。给按钮贴上联系我们的标签,并将其链接到文件 feedback_form.php

公共标题

你可能已经注意到,我们已经为我们的单个文件开发了许多标题。通过创建一个包含 case 语句的通用标题,我们可以有选择地处理在一个标题中显示的所有按钮决定。让我们看看清单 7-8 中的代码。

<div class="col-sm-2">
<img class="img-fluid float-left" src="logo.jpg" alt="Logo">
</div>
<div class="col-sm-8">
         <h1 class="blue-text mb-4 font-bold">Header Goes Here</h1>
 </div>
     <nav class="col-sm-2">
           <div class="btn-group-vertical btn-group-sm" role="group"
                   aria-label="Button Group">
           <div class="btn-group-vertical btn-group-sm" role="group"
                   aria-label="Button Group">
  // Case statement to determine which buttons to display                                #1
          <?php
          switch ($menu) {
          case 1: //header.php
          ?>
                    <button type="button" class="btn btn-secondary"
                    onclick="location.href = 'login.php'" >Login</button>
                    <button type="button" class="btn btn-secondary"
                    onclick="location.href =
                            'safer-register-page.php'">Register</button>

          <?php
        break;

          case 2: //header_members_account.php

          ?>
                    <button type="button" class="btn btn-secondary"
                             onclick="location.href = 'logout.php'" >Logout</button>
                    <button type="button" class="btn btn-secondary"
                             onclick="location.href = 'edit_your_account.php'">
                                     Your Account</button>
          <button type="button" class="btn btn-secondary"
                          onclick="location.href =
                          'safer-register-password.php'">New Password</button>
   <?php
    break;

 case 3: // header-thanks.php
    ?>
                   <button type="button" class="btn btn-secondary"
                            onclick="location.href = 'index.php'" >Cancel</button>
  <?php
     break;

case 4: // login-header.php
        ?>
                   <button type="button" class="btn btn-secondary"
                            onclick="location.href = 'login.php'" >
                                    Erase Entries</button>
                   <button type="button" class="btn btn-secondary"
                   onclick="location.href =
                           'safer-register-page.php'">Register</button>
           <button type="button" class="btn btn-secondary"
                   onclick="location.href = 'index.php'">Cancel</button>
<?php
   break;

case 5: //; members-header.php
        ?>
                 <button type="button" class="btn btn-secondary"
                          onclick="location.href = 'logout.php'" >Logout</button>
            <button type="button" class="btn btn-secondary"
                     onclick="location.href = 'change-password.php'" >
                             New Password</button>

<?php
    break;

case 6: //password-header.php
        ?>
                 <button type="button" class="btn btn-secondary"
                          onclick="location.href = 'register-password.php'" >
                          Erase Entries</button>
          <button type="button" class="btn btn-secondary"
                          onclick="location.href = 'index.php'">Cancel</button>
<?php
    break;

case 7: //password-header.php
       ?>
                 <button type="button" class="btn btn-secondary"
                 onclick="location.href = 'index.php'" >Home Page</button>
<?php
   break;

case 8: //thankyou-header.php
        ?>
                 <!-- <button type="button" class="btn btn-secondary"
                 onclick="location.href = 'index.php'" >Home Page</button>-->
<?php
   break;
case 9: //register-header.php
        ?>
                   <button type="button" class="btn btn-secondary"
                            onclick="location.href = 'register-page'" >
                           Erase Your Entries</button>
                   <button type="button" class="btn btn-secondary"
                            onclick="location.href = 'index.php'">Cancel
                            </button>
<?php
    break;
}
?>
</div>
</nav>

Listing 7-8Creating a Universal Header (header1.php)

代码的解释

本节解释代码。

// Case statement to determine which buttons to display                                  #1
       <?php
       switch ($menu) {
       case 1: //header.php
         ?>
                 <button type="button" class="btn btn-secondary"
                 onclick="location.href = 'login.php'" >Login</button>
                 <button type="button" class="btn btn-secondary"
                 onclick="location.href =
                         'safer-register-page.php'">Register</button>

       <?php
        break;

检查变量$menu 以确定在标题中显示哪个按钮组合。要使用这个公共头文件,每个文件中必须有两处更改。

  1. 必须在文件中创建一个变量,保存与要显示的一组按钮相对应的数字。例如,在文件index.php中,可以将以下代码添加到文件的顶部:

    <?php
            $menu = 1;
    ?>
    
    
  2. 拉入标头的 include 语句也必须更新,以包含新的标头。例如,在index.php文件(以及所有要更新的文件)中,更新代码如下所示:

    include("include/header1.php");
    
    

本章中的文件尚未更新为包括通用标题。但是,用户可以使用前面的步骤更新它们。关于通用标题的使用示例,请查看从第十一章下载的文件。

记录异常和错误

在几个章节中,我们已经包含了使用 catch 块处理异常和错误的能力。然而,我们除了显示一条通用消息之外什么也没做。为了完成我们的应用,我们应该提供在出现问题时得到通知的能力。提供这种能力的最简单方法是将错误消息放在日志文件中。PHP 应用可以将消息记录到默认的 PHP 错误文件或特定于应用的文件中。对于一个应用来说,为信息日志、身份验证(登录)日志、错误日志和安全日志创建单独的文件是很常见的。您可以在程序代码的顶部创建一个常数来指示日志文件的位置。这允许任何支持您的代码的人快速确定文件的位置,并允许他们在需要时轻松地更改位置。

define(ERROR_LOG,"errors.log");

使用这个常量,我们可以使用 error_log 函数将错误信息发送到日志文件。

error_log("A general error",3,ERROR_LOG);

这将把显示的消息直接发送到 errors.log 文件。然而,这并没有给我们足够的有意义的信息。例如,我们可能希望包含事件发生的时间和日期,以及一个指示消息类型的标题。

$date = date('m.d.y h:i:s');
$errormessage = "This is the error";
$eMessage = $date . " | User Error | " . $errormessage . "\n";
error_log($eMessage,3,ERROR_LOG);

该代码将产生以下内容:

07.04.2018 03:00:55 | User Error | This is the error

可以使用标准文本编辑器(Notepad++)或日志监控软件程序来查看日志文件的内容。

PHP 还使得在日志文件中写入内容时发送电子邮件警报变得容易。当然,您的文件必须存在于包含电子邮件服务器的服务器上。我们可以将电子邮件信息添加到另一个 error_log 语句中,如下所示:

error_log("Date/Time: $date – Serious Problem. Check error log for details", 1 , noone@helpme.com, "Subject: User Errors \nFrom: Error Log <errorlog@helpme.com>" . "\r\n");

第一个参数指定电子邮件的消息。第二个参数通知 error_log 通过电子邮件发送该信息。第三个参数提供“收件人”电子邮件地址。第四个参数是一个额外的头字段。该字段通常用于包含电子邮件的主题和发送邮件的电子邮件地址。必须包含“发件人”地址,否则邮件将不会被发送。

在本章的下载代码中,我们包含了 error_log 代码,用于将错误发送到 error_log,并在出现问题时发送电子邮件。下面显示了这段代码:

catch(Exception $e) // We finally handle any problems here
   {
     // print "An Exception occurred. Message: " . $e->getMessage();
     print "The system is busy please try later";
     $date = date('m.d.y h:i:s');
     $errormessage = $e->getMessage();
     $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
     error_log($eMessage,3,ERROR_LOG);
     // e-mail support person to alert there is a problem
     error_log("Date/Time: $date – Exception Error, Check error log for
           details", 1, noone@helpme.com, "Subject: Exception Error \nFrom: Error Log
           <errorlog@helpme.com>" . "\r\n");
   }

Error catch 块的格式是类似的,不同之处在于更改了标题以指示它是一个错误而不是异常。我们还可以更改它,将错误保存在不同的日志文件中。这段代码在下载文件中被注释掉,直到您准备好使用它。

既然您已经有了内置的异常处理和错误处理,那么在将您的程序升级到产品之前,您需要编辑 php.ini 文件来关闭对用户的错误报告。

准备好之后,您需要找到 php.ini 配置文件。在 XAMPP,你可以通过进入控制面板并点击 Apache 服务器右侧的 config 按钮来轻松找到该文件,如图 7-8 所示。

img/314857_2_En_7_Fig8_HTML.jpg

图 7-8

查找 php.ini 配置文件

点击 php.ini 菜单选项将其打开。对于 easyPHP,您必须深入开发服务器文件夹才能找到 php.ini 文件。它应该位于与此处所示链接类似的位置:

C:\Users\yourcomputername\EasyPHP-Devserver-17\EasyPHP-Devserver-17\eds-binaries\php\php713vc14x86x170718155219

当然,您的版本名称和内部版本号将与显示的不同。找到文件后,用 Notepad++或其他文本编辑器打开它。

立即用另一个名称保存当前版本的配置文件(并记住保存的位置),以防出错并需要恢复到原始设置。

找到这一行:display_errors = On。将设置更改如下:display_errors = Off。保存文件。然后关闭再打开 Apache 服务器。您的错误消息现在应该被定向到错误日志,而不是网页。当然,这不包括为用户创建的带有 echo 语句的任何错误消息(如您的电子邮件丢失)。

现在,我们可能已经准备好将文件移动到远程生产服务器。让我们来看看实现这一点的过程。

将数据库和表迁移到远程主机

使用 phpMyAdmin 创建了数据库和表之后,我们如何将它们转移到托管公司?将数据库从 XAMPP 或 easyPHP 迁移到远程主机的过程比数据库开发的任何其他方面都更让初学者担心。这是因为 MySQL/MariaDB 手册和互联网教程很少对该过程提供适当的解释。

对于本教程,我们选择使用 finalpostal 数据库,它包含在本章附带的 users.sql 文件中。

警告

您需要确定您选择的主机软件包中使用的操作系统。Linux 和 Windows 需要稍微不同的迁移过程。

令人困惑的错误信息

尝试将数据库传输到另一台计算机时,您可能会偶尔遇到问题。有时会出现一条错误消息,指出该表已经存在。这很奇怪,因为空数据库中肯定没有表。解决方法是访问第一台电脑上的 XAMPPeasyPHP 文件夹,然后深入到 MySQL/MariaDB 文件夹,然后是数据文件夹。在闪存盘(记忆棒)或云中保存一份 somefilename.frm 文件的副本。现在将该文件复制到目标计算机上的相同数据文件夹中,问题就解决了。你应该定期保存 *的副本。frm 文件以及 SQL 转储,以防在数据库损坏后需要恢复数据。对于其他错误消息,将消息复制并粘贴到搜索引擎(如 Google)中,以发现建议的解决方案。

创建和导出 SQL 文件

迁移数据库的第一阶段包括创建 SQL 导出文件。您将能够导出两种类型的转储:仅针对表,或者针对数据库和表。我们将使用只包含表格的文件。如果数据库很大,应该单独导入表。我们将使用 phpMyAdmin 来导出文件。导出的 SQL 文件是一个简单的文本文件,名称格式为 filename.sql ,尽管也可以为导出的文件选择其他文件类型。接下来将解释文件的内容和过程。

创建一个或多个表的转储文件

打开浏览器,在地址栏输入http://localhost/phpmyadmin/

在 phpMyAdmin 的左侧面板中,单击包含要导出的表的数据库。然后点击导出选项卡,显示如图 7-9 所示的页面。

img/314857_2_En_7_Fig9_HTML.jpg

图 7-9

转储数据库表的屏幕

选择“快速”选项,并确保格式为 SQL。然后单击“开始”按钮。根据您的浏览器和设置,会询问您是打开还是保存文件,如图 7-10 所示。

img/314857_2_En_7_Fig10_HTML.jpg

图 7-10

选择保存 SQL 文件

选择保存文件,然后单击确定。将文件保存在您可以访问的文件夹中,以便稍后将其上传到主机。在 Windows 中,所有下载文件的默认位置是下载文件夹。

SQL 转储看起来像什么?

在您的文本编辑器(例如 Notepad++)中检查 users.sql 文件。

您将看到许多注释掉的行。SQL 支持如下三种注释样式:

  • 以如下散列符号开头的行:# 一些文本

  • 一行以双破折号开头,后面跟一个空格,像这样:- 一些文本

  • 标签之间的文本块,如下所示:

    /*some text
    some text
    some text*/
    
    

下面显示了 finalpostal 数据库中表的转储示例(您的转储可能略有不同)。这是章节文件提供的 users.sql 文件中的代码。

-- phpMyAdmin SQL Dump
-- version 4.8.0
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1
-- Generation Time: Dec 31, 2017 at 06:40 PM
-- Server version: 10.1.22-MariaDB
-- PHP Version: 7.1.4

此代码指定原始计算机上应用的版本。这是仅供参考,并在评论中。

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

请注意,utf8 版本包含在这些注释中:

--
-- Database: `finalpost`
--

-- --------------------------------------------------------

--
-- Table structure for table `prices`

--

CREATE TABLE `prices` (
  `oneyeargb` decimal(6,0) UNSIGNED NOT NULL,
  `oneyearus` decimal(6,0) UNSIGNED NOT NULL,
  `fiveyeargb` decimal(6,0) UNSIGNED NOT NULL,
  `fiveyearus` decimal(6,0) UNSIGNED NOT NULL,
  `militarygb` decimal(6,0) UNSIGNED NOT NULL,
  `militaryus` decimal(6,0) UNSIGNED NOT NULL,
  `u21gb` decimal(6,0) UNSIGNED NOT NULL,
  `u21us` decimal(6,0) UNSIGNED NOT NULL,
  `minpricegb` decimal(6,0) UNSIGNED NOT NULL,
  `minpriceus` decimal(6,0) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这是创建价格表的实际 SQL 代码:

--
-- Dumping data for table `prices`
--

INSERT INTO `prices` (`oneyeargb`, `oneyearus`, `fiveyeargb`, `fiveyearus`, `militarygb`, `militaryus`, `u21gb`, `u21us`, `minpricegb`, `minpriceus`) VALUES
('30', '40', '125', '140', '5', '8', '2', '3', '15', '20');

这是将数据加载到价格表中的 SQL 代码:

-- --------------------------------------------------------

--
-- Table structure for table `users`
--

CREATE TABLE `users` (
  `userid` mediumint(6) UNSIGNED NOT NULL,
  `title` tinytext NOT NULL,
  `first_name` varchar(30) NOT NULL,
  `last_name` varchar(40) NOT NULL,
  `email` varchar(50) NOT NULL,
  `password` char(60) NOT NULL,
  `registration_date` datetime NOT NULL,
  `class` char(20) NOT NULL,
  `user_level` tinyint(2) UNSIGNED NOT NULL,
  `address1` varchar(50) NOT NULL,
  `address2` varchar(50) DEFAULT NULL,
  `city` varchar(50) NOT NULL,
  `state_country` char(25) NOT NULL,
  `zcode_pcode` char(10) NOT NULL,
  `phone` char(15) DEFAULT NULL,
  `paid` enum('No','Yes') NOT NULL,
  `secret` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

下面是创建用户表的代码:

--
-- Dumping data for table `users`
--

INSERT INTO `users` (`user_id`, `title`, `first_name`, `last_name`, `email`, `password`, `registration_date`, `class`, `user_level`, `address1`, `address2`, `city`, `state_country`, `zcode_pcode`, `phone`, `paid`) VALUES
(1, 'Mr', 'Mike', 'Rosoft', 'miker@myisp.com', '$2y$10$UiiBhmXca.0/bwopveFq8uInuX.EVrecinUQYQG546WjAWwZLJNoe', '2017-12-06 08:43:41', '30', 0, '4 The Street', 'The Village', 'Townsville', 'USA', 'WA', '0123777888', 'Yes'),
 (32, 'Mr', 'James', 'Smith', 'jsmith@myisp.co.uk', '$2y$10$Yu.c/cw/TSFa9vcMBGAfAe5vzyOwp3SZarBVc/9vEksfp.F8BzSiW', '2017-12-29 11:58:51', '30', 0, '2 The Street', ", 'Townsville', 'UK', 'EX24 6PS', '01234777888', 'Yes');

前面的代码将数据加载到 users 表中。为了节省空间,只显示第一条和最后一条记录。

--
-- Indexes for dumped tables
--

--
-- Indexes for table `users`
--
ALTER TABLE `users`
  ADD PRIMARY KEY (`userid`);

这段代码将 user_id 定义为主键:

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
  MODIFY `user_id` mediumint(6) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=35;COMMIT;

此代码将主键优化为原始设计中要求的设置:

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

接下来的步骤完成了以下工作:

  • 检查目标服务器(主机)并准备接收 SQL 文件和 htdocseds-www 文件夹的内容

  • 将 SQL 文件(或使用远程主机上的导入功能)和网站文件上传到主机

  • 在主机中使用 phpMyAdmin 导入 SQL 文件

调查远程主机的服务器

我们将假设您已经为您的网站购买了空间,并向您的主机提供商或另一方注册了域名。许多提供商允许您选择使用 Linux/Unix 或 Microsoft web 服务器的托管包。虽然许多网站托管在 Unix 服务器上,但这是一种常见的偏好;这不是一个要求。

在选择您的主机提供商之前,确保他们提供的包包括 PHP、phpMyAdmin 和 MySQL(或 MariaDB)。此外,特别是如果您选择 Unix 操作系统,请确保提供了 Apache。PHP 和 MySQL 现在也可以与微软的网络服务器接口。但是,主机提供商可以选择将 Apache 与 Microsoft 服务器一起使用,就像您可能已经在计算机上实现的那样。如果您不确定主机上是否安装了 PHP 和 MySQL(或 MariaDB),请在文本编辑器中键入以下内容,并将其另存为phpinfo.php:

<?php
phpinfo();
?>

您可能希望将这个 phpinfo 文件保存在一个安全的位置,或者在测试后删除它,以防止任何人看到您的服务器上安装了什么。

使用您的 FTP 客户端,或主机提供的上传文件的过程,加载此文件,然后在浏览器中打开它(输入类似 http://yourwebsitename.com/phpinfo.php 的 URL)。你会看到一个如图 7-11 所示的表格。向下滚动表格,您应该会看到主机服务器上 MySQLi 安装的详细信息。如果你看到安装了 MySQLi 的一个版本并且运行了这个程序,那么你应该能够上传并运行这本书里的程序。

img/314857_2_En_7_Fig11_HTML.jpg

图 7-11

在服务器中打开 phpinfo.php 的结果

警告

一些基本的主机包不接受数据库。在这种情况下,您必须升级到更贵的产品包。继续之前,请检查您的目标主机上的产品范围。

在远程主机的服务器上使用 GUI

阅读文档或联系您的主机提供商的服务代表,以确定如何访问 phpMyAdmin 或类似类型的程序来创建、上传和修改 MySQL/MariaDB 数据库。您的主机提供商可能包括一个控制面板,如图 7-12 所示,或者可能提供另一种方式来访问 phpMyAdmin。

img/314857_2_En_7_Fig12_HTML.jpg

图 7-12

控制面板的数据库部分

要安装数据库,请按照下列步骤操作:

  • 打开 phpMyAdmin 并创建一个空数据库。在本例中,我们将创建 finalpostal 数据库。

当创建 finalpostal 数据库时,要么生成的数据库将具有名称 finalpostal,要么主机将向数据库名称添加前缀。添加前缀的主机几乎总是附加网站所有者的用户 ID 来访问控制面板。我们假设所有者的用户 ID 是 mywebsite 主机可能会将数据库名称创建为 mywebsite_finalpostal 。在这个例子中,我们将假设数据库名有前缀我的用户名

  • 现在,您必须立即通过创建用户 ID 和密码来保护新创建的数据库。如前几章所述,通过使用 phpMyAdmin 来实现这一点。(有些主机会自动提供用户 ID,如果是这种情况,请使用那个。)

使用以下详细信息创建用户:

  • 用户:站长(或主持人提供的名字)

  • 密码 : c0ff33p0t

  • 主机:(您的网站 URL 的名称)

真实世界数据库中的密码肯定比 c0ff33p0t 复杂得多。仔细记下数据库名称、密码和用户。

连接到远程主机上的数据库

现在我们知道了空数据库的名称(在本例中,它是 myusername_finalpostal),我们可以修改连接文件以适合远程主机。不要在你电脑上的 XAMPP 的 htdocs 或 easyPHP 的 eds-www 文件夹中创建这个文件。如果这样做,您将无法再从计算机服务器上的数据库驱动页面访问 finalpostal 数据库。使用记事本++等文本编辑器创建文件,并将新的 mysqli_connect.php 文件存储在除 htdocseds-www 文件夹之外的任何位置。修改后的文件不包含在可下载文件中,因为它对于您的数据库来说是唯一的。代码可能如下一段代码所示。

连接到新数据库的代码片段(mysqli_connection.php)

<?php
// Create a connection to the admintable database and set the encoding
// The user name might be provided by the host.
// If not, use the name from your XAMPP version
Define ('DB_USER', 'webmaster');
Define ('DB_PASSWORD', 'c0ff33p0t');
Define ('DB_HOST', 'www.yourwebsite.com');
Define ('DB_NAME', 'myusername_admintable');
// Make the connection
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');

现在,您已经具备了将数据库驱动的网站迁移到远程主机所需的以下所有项目:

  • 向远程主机注册的帐户和域名

  • 位于主机服务器上的新创建的空数据库

  • 表的 SQL 转储文件

  • 包含的文件夹和包含图像的文件夹

  • 修改后的 mysqli_connect.php 文件

主 PHP 和 HTML 文件在上传之前需要修改,这个修改将在下一节描述。

安全上传 mysqli_connection.php 文件

为了获得最高的安全性,数据库连接文件应该位于根文件夹之外。然后,该文件不能通过网站直接访问。请确保它所在的文件夹是安全的,不会被外部访问。

大多数托管公司都提供使用 FTP 程序上传和更改服务器上文件的能力。有很多免费的 FTP 程序可用,包括 FileZilla ( https://filezilla-project.org/ )。适用于您网站的 FTP 程序设置将取决于托管公司提供的安全设置。您还需要一个用户 ID、密码(该密码可能与访问您的主机帐户的普通用户 ID 和密码不同),以及您的 URL 或 TCP/IP 地址。您将被限制只能访问您的文件夹和文件。图 7-13 显示了一个示例 FTP 窗口,它包括一个 Linux/Unix 远程主机的典型文件夹结构。

img/314857_2_En_7_Fig13_HTML.jpg

图 7-13

FTP 程序窗口中显示的 Linux/Unix 环境的典型文件和文件夹

在图 7-13 中,数据库连接文件(圈起来的)已经被重命名为【greenh.php】的并上传到 public_html 根文件夹(圈起来的)之外。该文件夹存在于 Linux/Unix 操作系统中,用于存放向公众显示的任何网站。在微软网络服务器上,这个文件夹可能被命名为 wwwhtdocs 或类似的名称。您网站的用户将无法访问不在公共文件夹中的任何文件( public_html )。您的主机也可能只允许您访问该公共文件夹,而不允许您访问根级别,如图 7-13 所示。

如果你可以在主机上的公共文件夹( public_html )之外找到连接文件(greenh.php),那么你当前的交互页面都找不到它。要解决这个问题,使用您的代码编辑器打开每个包含一个 mysqli_connect.php 链接的文件,并运行一个查找并替换所有链接的操作来修改它的位置,如下所示。

找到 mysqli_connect.php ,替换成../greenh.php 。符号../ 告诉页面该文件位于当前文件夹的上一层。为了连接到您自己的计算机/服务器上的数据库,您可能使用了 require(' mysqli _ connect . PHP ');如本书的示例文件中所提供的。请记住,用户通常不会看到连接文件的链接,因为任何 PHP 代码都是在服务器上执行的,只有结果(比如从数据库中提取的信息)才会显示给用户。然而,我们确实希望尽可能地安全,以防意外发生。

现在,连接文件更安全了,交互页面也相应地进行了修改,您可以将 HTML、CSS 和 PHP 页面上传到远程主机。

将交互式页面上传到主机

最后的步骤如下:

  • 保存您网页的公共文件夹不包括 htdocseds-www 文件夹。这些文件夹名称特定于用于创建页面的开发包(XAMPP 或 EasyPHP)。在真实环境中,实际的文件夹名称不是必需的。web 服务器知道所有公共可用的网页都驻留在公共文件夹中( public_htmlwwwhtdocs )。因此,您需要使用 FTP 客户端(FileZilla)将 HTML、CSS 和 PHP 页面从 finalpostal 的 htdocseds-www 文件夹上传到主机服务器的 public 文件夹中。因为您已经上传了一个经过修改并重命名的 mysqli_connect.php 文件,所以请注意不要上传未经修改的版本。现在,使用您的 FTP 客户端在您的公共文件夹中创建一个子文件夹(如 SQL)。上传将用于将表安装到数据库中的 SQL 文件。

  • 在远程服务器上打开并登录 phpMyAdmin(或类似的主机提供的程序)。

  • 在 phpMyAdmin 的左侧面板中,单击新创建的空数据库的名称,然后单击 Import 选项卡。

  • 将要求您选择要导入的 SQL 文件。浏览到 public_html 文件夹,然后到子文件夹( SQL ,找到每个上传的 SQL 文件。打开它(或它们),然后点按“前往”。

  • 您的公共文件夹可能有一个默认的index.html文件。你的新的 index.php文件必须替换 HTML 文件;否则,【index.html】的将代替你的index.php文件载入。将主机上的index.html文件重命名为old-index.html或将其删除。web 服务器将搜索具有不同扩展名的所有索引文件。如果找到带有的索引文件。html 扩展名,它将尝试将该文件显示为网站的主页。如果该文件不存在,它将寻找其他索引文件,如index.php

现在,通过在浏览器的地址栏中键入您网站的 URL,可以在线查看您上传的交互式网站。请注意,如果您决定将文件加载到子文件夹中,而不是直接加载到公共文件夹中,请在 URL 中包含该文件夹的名称,例如 http://yourwebsite.com/finalpostal/ 。你应该看到你的主页,从那里你可以测试网站。确保测试所有与数据库交互的页面,以确保数据库连接正确。

请注意,您可能需要等待一些主机更新它们的服务器缓存(内存)来显示您的网页更改。使用域名系统(DNS)服务器显示网页。这些服务器将网站名称转换为实际的 TCP/IP 数字地址,该地址唯一地标识了您网站的位置。如果您更改了您的 URL 地址或索引页面,DNS 服务器也会逐渐更新这些信息。

与任何计算机工作一样,备份是预防硬件或软件故障的必要措施。如果您发现一些主要的编码问题,这些备份文件还将提供回滚到文件的早期版本的能力。

备份您的数据库

当你学习创建和使用数据库和使用数据库的网站时,这四个步骤是必不可少的:

  1. 继续在你的笔记本或软件笔记系统上写东西。

  2. 请务必更新您的流程图。

  3. 使用 phpMyAdmin 中的 Export 选项卡来备份您的表。

  4. 备份你的 htdocseds-www 文件夹。

记录用户名和密码等重要步骤和细节可以省去很多悲伤。当创建许多实验数据库和这些数据库的变体时,您不可能记住每个数据库的细节。此外,你不会记得每天(或每周)所做的所有改变。记录下你已经完成的每个阶段的细节,以及你下一步打算做什么。如果你必须停下来一段时间(可能是为了更新其他客户的网站),这一点尤其重要。客户似乎从不单独请求更新;他们成群结队而来,四五个客户同时吵着要更新。

还要记录有用的网站网址。如果你在一本书或博客上看到一个有用的提示,把它记下来。这可以让你不用再翻遍书架或博客来寻找物品。回环笔记本是理想的选择,因为你可以插入分隔条来帮助你找到你写下重要信息的地方。但如果你不是老派,电子笔记本也将是一个不错的选择。

对于备份,使用 phpMyAdmin(或者您的主机提供的数据库管理系统)从每个数据库中导出表的 SQL 转储。然后将它们保存到 USB 闪存驱动器或其他云位置,以便在表损坏的情况下,可以从已知的良好时间框架中恢复表。

在闪存驱动器或云驱动器上制作一份 htdocseds-www 文件夹的副本,这样你就有了最新版本的成功且经过测试的网页。将它们(以及您的 SQL 文件)存储在多个位置,这样当硬盘或操作系统出现故障时,您就不会受到影响。此外,考虑在另一台计算机上创建一个 XAMPP 或 easyPHP 环境。额外的工作是保持网站的每个副本都是最新的,但是如果一台电脑坏了,你可以在另一台电脑上继续工作。内心的平静是一件美好的事情。

你还应该设置每个网站主机提供的自动备份系统,以确保你的数据自动备份,并可以在发生意外时自动恢复。

摘要

在这一章中,我们加入了客户和会员秘书建议的一些最后的改进。这些措施包括允许注册会员更新自己的资料。客户还要求提供网站的询价单。对网站的所有这些新增内容都进行了描述和列出。本章提供了一些你可以添加到你的网站的额外功能,包括记录错误的能力。本章最后完整描述了将数据库迁移到主机和备份数据库表的过程。

在下一章中,我们将学习如何为房地产代理商创建产品目录。

八、创建产品目录

在前面的章节中,你已经学习了 PHP 和 MySQL/MariaDB 的基础知识。这些概念提供了一个基础,可以用来创建一个完全数据库驱动的网站。在本章中,我们将使用这些技巧和一个示例产品目录来创建一个全功能的数据库驱动的站点。大多数电子商务网站都有一个目录来展示产品和服务。一些网站使用目录来显示无法通过互联网购买的项目,如旅游景点或房屋。本章中的教程假设一家房地产代理公司在一个有限的区域内提供各种类型的房屋。房地产网站是一个很好的例子,它有一个目录供用户浏览,但并不实际购买任何东西。通过互联网买房(对大多数人来说)是不切实际的。明智的买家会在考虑购买之前先查看房产及其位置。

因为一所房子的买卖牵涉到这么多人,他们的行动都必须仔细协调。所涉及的当事人包括卖方、买方、房地产经纪人、买卖双方各自的律师、买卖双方的银行或抵押贷款公司以及买方的验船师。此外,可能会涉及一系列事件,例如,卖方在找到并同意购买另一所房子之前,不能继续交易。同样,买家在得到当前房屋的实盘之前不能继续交易。这些依赖性突出了为什么在房地产网站上只提供房屋目录是合理的。试图提供完成销售所必须发生的所有不同交易将是复杂和不切实际的。

完成本章后,您将能够

  • 设计并准备数据库和管理计划

  • 创建房地产数据库和表格

  • 创建一个可搜索的主页,用户可以在那里找到合适的房子

  • 为管理员创建一个页面,以查看所有的房屋或搜索并查看特定的房屋

  • 创建显示页面的代码,这些页面提供列出的每个属性的完整规范

  • 为有兴趣看房的用户创建一个查询表单

准备数据库和管理计划

与任何项目一样,我们必须让用户(所有者)参与开发过程。参与开发过程的完整生命周期的用户更有可能对最终产品感到满意。要开始这个过程,我们必须首先收集所有重要的信息,这些信息将决定整个网站的设计和局限性。

在我们与房地产代理业主会面后,我们就以下优先事项达成一致:

  • 最重要的页面是搜索页面。这必须通过让用户快速找到他们感兴趣的房子来吸引他们。

  • 以下是找房子时的首要任务:

    • 位置

    • 价格

    • 卧室数量

    • 房屋类型

因此,搜索页面将包含这四个项目的字段:

  • 房子的大概位置会被透露,但是街道名和门牌号一定不能透露。这就避免了用户绕过房产中介,直接去找房主。此外,这为房屋所有者和代理人提供了安全性,因为除非由代理人陪同,否则没有人能够进入房屋位置。出于安全和财务方面的原因,任何潜在客户在被带去看房子之前都会经过房地产办公室的筛选。

  • 将不会使用术语空置占有。一所空房子对歹徒来说是一块磁铁。空置房屋可能被破坏、被非法占用,或者给销售代理和他们的客户带来安全风险。

  • 房产中介的摄影师会提供一个缩略图()。jpg。gif ,或者。png 文件)。缩略图的宽度为 150 像素,最大高度为 120 像素。摄影师还将提供照片的放大版本,用于完整的规格页面;这些将被标准化为 350 像素宽。摄影师会格外小心,确保图像本身没有嵌入纬度、经度或其他方向指示。

    警告

    目录总是包含图像,但房地产管理员可能无法处理它们。房地产公司管理员需要通过 FTP 客户端将新图像放在远程主机上的适当文件夹中。管理员还需要基本的 HTML 和 CSS 技能来格式化房屋描述的文本。我们将假设管理员不具备这些技能。因此,我们将采用的解决方案是请网站管理员管理网站的内容。

首先,我们需要创建新的数据库。

创建新数据库

要创建新数据库,请按照下列步骤操作:

  1. xampp 内的 htdocs 文件夹中或者在 EasyPHP 文件夹 eds_www 中,新建一个名为 estate 的文件夹。

  2. 从本书在出版社的链接下载第 8 章的文件。并将它们解压到新的地产文件夹中。

  3. 在浏览器的地址栏中,输入 localhost/phpmyadmin/以访问 phpmyadmin。

  4. 单击 Databases 选项卡,创建一个名为 estatedb 的数据库。从下拉排序列表中选择 utf8_general_ci,然后单击创建。

  5. 单击权限选项卡,然后向下滚动并单击添加新用户。

  6. 输入这些详细信息:

    • 用户名 : smeeton

    • 密码 : L1ghth0us3

    • 主机:本地主机

  7. 在左侧面板中,单击数据库 estatedb 旁边的框。

  8. 不要在下一个屏幕上输入任何内容,而是单击 Import 选项卡。

  9. 点击浏览按钮,导航到地产文件夹和 SQL 转储文件 houses.sql

  10. 点击 houses.sql 文件,然后点击打开按钮。该字段将填充转储文件的 URL。

  11. 确保下拉菜单中的字符集是 utf-8,并且格式显示为 SQL。

  12. 单击开始。

创建用于连接到数据库的文件

将文件命名为 mysqli_connect.php 。如果您正在使用下载文件,这个文件已经为您创建好了,可以在名为庄园的文件夹中找到。

<?php
// Create a connection to the migrate database and to MySQL
// Set the encoding to utf-8
// Set the database access details as constants
Define ('DB_USER', 'smeeton');
Define ('DB_PASSWORD', 'L1ghth0us3');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'estatedb');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');
?>

houses 表及其内容(以及一个 users 表)是在您之前导入并运行 houses.sql 文件时创建的。验证创建的房屋表具有表 8-1 中所示的列。

表 8-1

房屋数据库表的属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 参考编号 | 中位 | six | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 位置 | 蒂尼因特 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 价格 | 小数 | 9,2 | 没有人 |   | -是吗 |   | -是吗 |
| 类型 | 微小文本 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 迷你 _ 描述 | 可变长字符串 | One hundred | 没有人 |   | -是吗 |   | -是吗 |
| 卧室 | 蒂尼因特 | Two | 没有人 |   | -是吗 |   | -是吗 |
| 拇指 | 可变长字符串 | Forty-five | 没有人 |   | -是吗 |   | -是吗 |
| 完整 _ 描述 | 可变长字符串 | Six hundred | 没有人 |   | -是吗 |   | -是吗 |
| 完整 _ 图片 | 可变长字符串 | Forty-five | 没有人 |   | -是吗 |   | -是吗 |
| 状态 | 微小文本 | Thirty | 没有人 |   | -是吗 |   | -是吗 |

观众在查询房产时会用到房屋参考号。当用户单击 details 链接时,该值还将用于显示房屋的详细信息。

列类型 MEDIUMINT(无符号时)允许从 0 到 8,388,606 的整数。这个规模将为房地产公司提供大量的潜在询价空间。

TINYTEXT 最多允许 255 个字符。这为描述卧室提供了很大的灵活性。

DECIMAL 允许价格达到表中第一个数字设定的限制。第二个数字表示小数位数。大小为 9,2 意味着当输入九位数字时,如 123456789,记录的结果将是 1234567.89。这使得查询量高达 4.1 亿次!thumb 列保存图像的 URL 例如,img/thumbs/house _ 01 . jpg。我们允许在缩略图列中有 45 个字符,因为缩略图的文件名通常很长——例如,img/thumbs/平房 _South_Devon_150px.png

full_description 列将包含房屋的详细描述。管理员可以输入 HTML 和 CSS 来控制该字段中文本的格式。full_picture 列包含每个房屋放大图像的 URL 链接。

“状态”栏将显示房屋是否可用、正在出售、退出市场或已出售。

安全

如前所述,不熟练的管理员将无法处理映像的准备,并且可能无法使用 FTP 程序将新映像放入远程主机的适当文件夹中。因为这些任务必须由网站管理员来执行,所以网站管理员就是管理员。熟练的网站管理员可以使用 phpMyAdmin 进行任何更改。

创建的任何管理页面也需要使用用户 ID 和密码来保护。这些页面将包括会话代码(安全防护),类似于我们在前面章节中看到的例子。本章的可下载文件包括一个用户表、一个注册页面和一个登录页面。

网站中没有提供登录菜单按钮。只有管理员知道这个存在,他们会输入 localhost/estate/login.php 来访问登录页面。

“用户”( users)表格包括前一章中的管理员杰克·史密斯,他的信息可用于访问任何管理页面。杰克的登录信息如下:

  • 电子邮件:jsmith@outcook.com

  • 密码 : D0g3b@dy

根据商定的规范,所需页面如下:

  • 主页,包括用户的房屋搜索功能(index.php)。

  • 搜索结果页面,显示用户根据搜索条件选择的房屋。

  • 一个完整的规格页面,显示每个单独的房子的细节。用户通过单击搜索结果页面上的链接来访问这些信息。

  • “联系我们”页面,将用于用户查询。

  • 一个管理页面,将提供这样的网站管理员可以方便地查看所有的房子。

  • 另一个管理页面,这将允许网站管理员搜索和查看特定的房子。

  • 管理员还可以添加新的房屋。只有网站管理员知道该页面的 URL,并且无法从网站访问该页面。作为额外的预防措施,管理页面不会在页面标题中包含单词admin;它有一个不太明显的名字【advert.php 。

前面的页面都不允许编辑或删除房屋列表。管理员将使用 phpMyAdmin 来编辑和删除房屋。我们现在可以为房地产网站创建一个主页。

创建具有搜索功能的主页

图 8-1 显示主页。

img/314857_2_En_8_Fig1_HTML.jpg

图 8-1

一个房地产网站的主页

本教程中页面的绿色背景是 CSS3 渐变。白色边框内的区域具有透明背景,因此可以看到渐变。

主页有四个下拉菜单,以消除用户输入错误,并确保只将可接受的数据输入数据库表。为了节省空间和提供更好的安全性,本教程中的数据库通过限制这些下拉菜单中的选择来简化。菜单中使用英国价格和术语。在美国,使用不同的术语;例如,术语半分离可能会被并排双工替换。在英国,平房是单层建筑,而房子有两层或两层以上。对于一个美国房地产经纪人,你也可以替换货币实体&英镑;与美元实体&美元;。

虽然我们严格限制了下拉选择以节省空间和代码并增加安全性,但真实世界的房屋目录将采用相同的原则,但使用更广泛的位置、价格和房屋类型选择。

在主页的主体中,主菜单(menu.php)的代码在下面的代码片段中给出:

<div style="padding-top: 10px; padding-bottom: 10px; padding-right: 15px;">
 <nav class="float-right navbar navbar-expand-md navbar-dark">
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#collapsibleMenu1">
                <span class="navbar-toggler-icon"></span>
        </button>
       <div class="btn-group-vertical btn-group-sm collapse navbar-collapse" id="collapsibleMenu1"
               role="group" aria-label="Button Group">
               <ul class="navbar-nav flex-column" style="width: 140px;">
                      <li class="nav-item">
                      <a class="btn btn-primary"
                      style="background:#559a55; border: 5px outset #559a55;" href="#"
                      role="button">About Us</a>
                      </li>
               <li class="nav-item">
                      <a class="btn btn-primary" style="background:#559a55; border: 5px outset #559a55;"
                      href="#" role="button">FAQs</a>
               </li>
               <li class="nav-item">
                      <a class="btn btn-primary" style="background:#559a55; border: 5px outset #559a55;"
                      href="contact.php" role="button">Contact Us</a>
               </li>
               <li class="nav-item">
                     <a class="btn btn-primary" style="background:#559a55; border: 5px outset #559a55;"
                     href="index.php" role="button">Home Page</a>
               </li>
              </ul>
         </div>
 </nav>
</div>

在可下载的菜单文件中,“关于我们”和“常见问题”链接是死的,因为在本教程中没有为这两个链接提供目标页面。

我们现在将检查主页和大多数网页的标题。

大多数页面的页眉

头文件代码更改为包含头文件,如下所示:

<header>
<?php include('includes/header.php'); ?>
</header>

主样式表格式化标题并提供背景图像。

主页代码

现在让我们检查一下主页,它也是一个搜索页面(清单 8-1 )。

<!DOCTYPE html>
<html lang="en">
<head>
<title>Estate Home Page</title>
      <meta charset="utf-8">
      <meta name="viewport" s
content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <!-- Bootstrap CSS File -->
      <link rel="stylesheet"
href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
             integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
             crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="transparent.css">

</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header>
<?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
      <div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Center Column Content Section -->
      <div class="col-sm-12 text-center"
style="padding:0px; margin-top: 5px;">
      <!--Start of admin add paintings content-->
<div class="row">
             <div class="col-sm-4">
                   <h4>Search for your dream house</h4>
                   <h6>IMPORTANT: Select an item in
                   ALL fields otherwise the search will not succeed</h6>
<form action="found_houses.php" method="post"
name="searchform" id="searchform">
<div class="form-group row form-control-sm no-gutters"
      style="padding: 0px;">
<div class="col-sm-3"></div>
<div class="col-sm-6" style="padding: 0px;">
                   <label for="location" class="col-form-label text-right">
                         Location:</label>
                   <select id="location" name="location" class="form-control"
                         required>
                         <option selected value="">- Select -</option>
                         <option value="South_Devon">South Devon</option>
                         <option value="Mid_Devon">Mid Devon</option>
                         <option value="North_Devon">North Devon</option>
                   </select>
             </div>
             </div>
<div class="form-group row form-control-sm no-gutters"
      style="padding: 0px;" >
<div class="col-sm-3"></div>
<div class="col-sm-6" style="padding: 0px;">
                   <label for="price" class=" col-form-label text-right">
                         Maximum Price:</label>
                   <select id="price" name="price" class="form-control"
                         required>
                         <option selected value="">- Select -</option>
                         <option value="200000">&pound;200,000</option>
                         <option value="300000">&pound;300,000</option>
                         <option value="400000">&pound;400,000</option>
                   </select>
             </div>
             </div>
<div class="form-group row form-control-sm no-gutters"
      style="padding: 0px;" >
<div class="col-sm-3"></div>
<div class="col-sm-6" style="padding: 0px;" >
                   <label for="type" class="col-form-label text-right">
                         Type:</label>
                   <select id="type" name="type" class="form-control"
                         required>
                         <option selected value="">- Select -</option>
                         <option value="Det-bung">Detached Bungalow</option>
                         <option value="Semi-det-bung">
Semi-detached Bungalow</option>
                         <option value="Det-house">Detached House</option>
                         <option value="Semi-det-house">

Semi-detached House</option>
                   </select>
             </div>
             </div>
<div class="form-group row form-control-sm no-gutters"
        style="padding: 0px;">
<div class="col-sm-3"></div>
<div class="col-sm-6" style="padding: 0px;">
                   <label for="bedrooms" class="col-form-label text-right">
                         No. of Bedrooms:</label>
<select id="bedrooms" name="bedrooms" class="form-control"
style="margin: 0px;" required>
                                <option selected value="">- Select -</option>
                                <option value="1">1</option>
                                <option value="2">2</option>
                                <option value="3">3</option>
                                <option value="4">4</option>
                   </select>
             </div>
             </div>
<div class="form-group row form-control-sm ">
                   <label for="" class="col-sm-3 col-form-label"></label>
             <div class="col-sm-6 text-center "style="padding: 0px;" >
                   <input id="submit" class="btn btn-primary" type="submit"
                         name="submit" value="Search">
             </div>
      </div>
</div>
<div class="col-sm-4">
<h6>All houses are situated in the beautiful green rolling countryside
       of Devon, England, UK</h6>
       <img alt="SW England"  src="img/devon-map-crop.jpg" >
</div>
<div class="col-sm-4">
<?php include ('includes/menu.php'); ?>
</div>
</div>
</div>
</div>
</div>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
<footer>
<?php include ('includes/footer.php'); ?>
</footer>
</div>
</div>
</div>
</body>
</html>

Listing 8-1Creating the Home Page (index.php)

索引页面的代码类似于我们在其他章节中看到的代码。不需要额外的解释。

显示目录

用户在首页字段输入搜索条件后(index.php,如图 8-1 ,点击搜索按钮将显示所选房屋,如图 8-2 。

在主页中输入以下详细信息,可以看到选中的房屋,如图 8-2 所示:

  • 地点:南德文郡

  • 最高价格 : 40 万

  • 类型:独立式住宅

  • 卧室 : 4 间

请注意,所有缩略图都将存储在名为拇指图像文件夹下的子文件夹中。在完整描述页面中使用的所有全尺寸图片(稍后解释)将被存储在图片文件夹下的图片子文件夹中。当用户单击 Details 链接时,将显示所选房屋的完整描述。

img/314857_2_En_8_Fig2_HTML.jpg

图 8-2

将显示按搜索标准选择的房屋

图 8-2 中显示的搜索标准如下:

  • 地点:南德文郡

  • 最高价格 : 40 万

  • 类型:独立式住宅

  • 卧室 : 4 间

    注意

    如果您单击 1003 号房屋的详细信息链接,您将看到该房屋的完整详细信息。如果你点击房子 1007 的细节链接,你会看到一个默认图片的完整细节的例子。

清单 8-2 给出了显示找到的房屋的代码。

主菜单被删除,以便表格可以跨越页面的宽度。为了使用户能够返回到主页,在标题中添加了一个主页按钮。

<?php
session_start();
// Data from valid source?                                                               #1
if ((empty($_SESSION['user_level'])) && (!isset($_SESSION['previous_url']) or ($_SESSION['previous_url'] != "index")))
{ header("Location: index.php");
exit();
}
else
{
if (!empty($_SESSION['user_level'])) {
      $user_level = $_SESSION['user_level'];

if(($user_level == 1) && (!empty($_POST['ref_number'])))
{
      $ref_number = htmlspecialchars($_POST['ref_number'], ENT_QUOTES);
}
} else { $user_level = 0; }
}
define('ERROR_LOG',"errors.log");
?>
 <!DOCTYPE html>
<html lang="en">
<head>
<title>Found Houses Page</title>
      <meta charset="utf-8">
      <meta name="viewport" content=
"width=device-width, initial-scale=1, shrink-to-fit=no">
      <!-- Bootstrap CSS File -->
      <link rel="stylesheet"
href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
             integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
             crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="transparent.css">
</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header>
<?php
if ($user_level == 0)
{
include("includes/header_found_houses.php");
}
else if($user_level==1)
{
include('includes/header_4btn.php');
}
?>
</header>
<!-- Body Section -->

<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Center Column Content Section -->
<div class="col-sm-12 text-center"
style="padding:20px; margin-top: 5px;">
<!--Start of admin add paintings content-->
<div class="row">
<h3>To arrange a viewing please use the Contact Us button
on the menu and quote the reference number.</h3>
<?php
// This script retrieves all the records from the houses table
try {
require ('mysqli_connect.php'); // Connect to the database.
// Make the query:                                                                       #2
$query = "SELECT ref_number, location, thumb, price, ";
$query .= "mini_description, type, bedrooms, ";
$query .= "status FROM houses ";
if(($user_level == 1) && (!empty($_POST['ref_number']))) {
$query .= "WHERE ref_number=? ";
} else {
$query .= "WHERE location= ? AND ";
$query .= "(price <= ?) AND (price >= (? - 100000)) AND ";
$query .= "type= ? AND bedrooms= ?  ORDER BY ref_number ASC ";
}
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind values to SQL Statement
if(($user_level == 1) && (!empty($_POST['ref_number']))) {
             mysqli_stmt_bind_param($q, 's', $ref_number);
} else {
$location = htmlspecialchars($_POST['location'], ENT_QUOTES);
$price = htmlspecialchars($_POST['price'], ENT_QUOTES);
$type = htmlspecialchars($_POST['type'], ENT_QUOTES);
$bedrooms = htmlspecialchars($_POST['bedrooms'], ENT_QUOTES);
mysqli_stmt_bind_param($q, 'sssss', $location, $price, $price,
$type, $bedrooms);
}
// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
// SELECT is safe execution - read only
if ($result) { // If it ran OK, display the records.
// Table header.
?>
<table class="table table-responsive table-striped"
style="background: white;color:black;">
<tr>
<th scope="col">Ref.</th>
<th scope="col">Location</th>
<th scope="col">Thumb</th>
<th scope="col">Price</th>
<th scope="col">Features</th>
<th scope="col">Bedrooms</th>
<th scope="col">Details</th>
<th scope="col">Status</th>
</tr>
<?php

while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
      // Remove special characters that might already be in table to
      // reduce the chance of XSS exploits
      $ref_number = htmlspecialchars($row['ref_number'], ENT_QUOTES);
      $thumb = htmlspecialchars($row['thumb'], ENT_QUOTES);
      $price = htmlspecialchars($row['price'], ENT_QUOTES);
      $mini_description =
htmlspecialchars($row['mini_description'], ENT_QUOTES);
      $bedrooms = htmlspecialchars($row['bedrooms'], ENT_QUOTES);
      $status = htmlspecialchars($row['status'], ENT_QUOTES);

      echo '<tr>
      <td scope="row">' . $row['ref_number'] . '</td>
      <td scope="row">' . $row['location'] . '</td>';
      if ($row['thumb'] == "")                                                         //#3
             {
echo '<td scope="row"><img src="img/thumbs/default.jpg">';
}
      else {
echo'<td scope="row">  <img src='.$row['thumb'] . '></td>';
}
      echo'<td scope="row">' . $row['price'] . '</td>
      <td scope="row">' . $row['mini_description'] . '</td>
      <td scope="row">' . $row['bedrooms'] . '</td>
      <td scope="row">
<a href="house_details.php?ref_number=' . $row['ref_number'] .
                   '">Details</a></td>
      <td scope="row"> ' . $row['status'] . '</td>
      </tr>';
      }
      echo '</table>'; // Close the table.
      mysqli_free_result ($result); // Free up the resources.
      } else { // If it did not run OK.
// Public message:
      echo '<p class="center-text">
The current users could not be retrieved.';
      echo 'We apologize for any inconvenience.</p>';
      // Debugging message:
      //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
      //Show $q is debug mode only
} // End of if ($result). Now display the total number of records/members.
mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e) // We finally handle any problems here
   {
// print "An Exception occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      //  $date = date('m.d.y h:i:s');

      //  $errormessage = $e->getMessage();
      //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
      //   error_log($eMessage,3,ERROR_LOG);
// e-mail support person to alert there is a problem
      //  error_log("Date/Time: $date – Exception Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Exception Error \nFrom:
// Error Log <errorlog@helpme.com>" . "\r\n");
   }
   catch(Error $e)
   {
      // print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      // $date = date('m.d.y h:i:s');
      // $errormessage = $e->getMessage();
      // $eMessage = $date . " | Error | " , $errormessage . |\n";
      // error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      //  error_log("Date/Time: $date – Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Error \nFrom: Error Log
// <errorlog@helpme.com>" . "\r\n");
   }
?>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
<h5 class="text-center">No houses displayed? Sorry we have nothing
that matches your requirements at the moment</h5>
</div>
</div>
</div><!-- End of table display content -->
</div>
</div>
</div>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
<footer>
<?php include ('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 8-2Creating the Results Page

(found_houses.php)

代码的解释

本节解释代码。

<?php
session_start();
// Data from valid source?                                                          #1
if ((empty($_SESSION['user_level'])) && (!isset($_SESSION['previous_url']) or ($_SESSION['previous_url'] != "index")))
{ header("Location: index.php");
exit();
}
else
{
if (!empty($_SESSION['user_level'])) {
      $user_level = $_SESSION['user_level'];

if(($user_level == 1) && (!empty($_POST['ref_number'])))
{
      $ref_number = htmlspecialchars($_POST['ref_number'], ENT_QUOTES);
}
} else { $user_level = 0; }
}
define('ERROR_LOG',"errors.log");
?>

该安全防护检查 previous_url 会话变量和 index 值是否存在。该检查仅允许索引文件调用该页面并向其传递内容。索引页要求选择所有下拉框。它还通过使用这些下拉列表来限制传递到此页面的值。

// Make the query:                                                                  #2
$query = "SELECT ref_number, location, thumb, price, ";
$query .= "mini_description, type, bedrooms, ";
$query .= "status FROM houses ";
if(($user_level == 1) && (!empty($_POST['ref_number']))) {
$query .= "WHERE ref_number=? ";
} else {
$query .= "WHERE location= ? AND ";
$query .= "(price <= ?) AND (price >= (? - 100000)) AND ";
$query .= "type= ? AND bedrooms= ?  ORDER BY ref_number ASC ";
}

如果用户不是管理员,下拉菜单提供的条目将被分配给 SQL 查询。如果用户是管理员,WHERE 子句将包含房屋参考号,我们很快将在一个管理表单中提供该参考号。

价格需要一些解释。声明如下:

(price <= ?) AND (price >= (?'-100000)

如果价格只是< =?,使用最高价格 400,000 的搜索将显示每栋价值 400,000 或更低的房屋。寻找最高价格为 40 万英镑的房子的人不会对 28 万英镑或 12 万英镑的房子感兴趣。因此,以下语句用于给出比搜索人的最高价格低 100,000 的最低价格:

AND (price >= (?-100000)

该显示按升序参考数字排序;但是,您可以将其更改为按价格降序排序。

if ($row['thumb'] == "")                                                               //#3
             {
echo '<td scope="row"><img src="img/thumbs/default.jpg">';
}
      else {
echo'<td scope="row">  <img src='.$row['thumb'] . '></td>';

}

如果数据库中没有缩略图的链接,则使用默认图片。如果代理人在摄影师有机会拍照之前就决定发布待售房屋,这种情况可能会发生。

搜索结果页面的页眉

图 8-3 为割台。

img/314857_2_En_8_Fig3_HTML.jpg

图 8-3

这两个按钮中的一个是“联系我们”按钮,这样用户可以请求预约看房

清单 8-3 给出了标题的代码。

<div class="jumbotron text-center row mx-auto" id="includeheader">
<div class="col-sm-10">
      <h1 class="text-left"><strong>Devon Real Estate</strong></h1>
      <h2 class="text-center">Try our award winning service</h2>
</div>
  <nav class="col-sm-2">
       <div class="btn-group-vertical btn-group-sm" role="group"
            style="width: 140px;" aria-label="Button Group">
                  <button type="button" class="btn btn-secondary" id="buttons"
                         onclick="location.href = 'contact.php'" >Contact Us</button>
                  <button type="button" class="btn btn-secondary" id="buttons"
                         onclick="location.href = 'index.php'">Home Page</button>
      </div>
  </nav>
</div>
<img id="rosette1" alt="Rosette" title="Rosette" height="127"
      src="img/rosette-128.png" width="128">

Listing 8-3Creating the Search Result Header

(header_found_houses.php)

现在,让我们创建详细信息页面。点击 found_houses.php 页面中为单个房屋提供的链接即可访问该页面。

创建房屋详细信息页面

房屋详细信息页面提供了更全面的房屋描述。当用户单击 Details 链接时,页面会从数据库中提取这些附加信息。该页面由索引页面或管理员搜索页面调用,稍后将对此进行解释。图 8-4 显示页面。

img/314857_2_En_8_Fig4_HTML.jpg

图 8-4

房屋 1003 的房屋详情(house_details.php)

found_houses 页面将房屋参考号(通过 GET)传递给 house_details 页面。这个值然后被用来检索房子的详细描述。

如果数据库中没有图片链接,则使用默认图片。如果代理人在摄影师有机会拍照之前就决定发布待售房屋,这种情况可能会发生。

清单 8-4 显示了房屋详细信息页面的代码。

<?php
session_start();
// Data from valid source?                                                               #1
if ((!empty($SESSION['user_level'])) &&
(!isset($_SESSION['previous_url'])
      or ($_SESSION['previous_url'] != "index")))
{ header("Location: index.php");
exit();
}
define('ERROR_LOG','errors.log');

?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>House Details Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,
initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
       integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
  <link rel="stylesheet" type="text/css" href="transparent.css">
</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header>
<?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 10px; height: auto;">
<!-- Center Column Content Section -->
<div class="col-sm-12 text-center" style="padding:20px; margin-top: 5px;">
<!--Start of admin add paintings content-->
<div class="row">
<div class="col-sm-5" style="background-color: white; padding-top: 10px;">
<?php
try {
$ref_number = htmlspecialchars($_GET['ref_number'], ENT_QUOTES);
require ('mysqli_connect.php'); // Connect to the database.
// Make the query:                                                                       #2
$query = "SELECT price, full_description, full_picture ";
$query .= "FROM houses WHERE ref_number=?";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind values to SQL Statement
mysqli_stmt_bind_param($q, 's', $ref_number);
// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
if ($result) { // If it ran OK, display the records.
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
?>
<h5 style="color:green"><strong>
Details for House Reference No
<?php echo $ref_number; ?>
</strong></h5>
<?php
echo '<img class="img-fluid float-left" alt="house reference ' .
             $ref_number;
echo '" src="';                                                                        //#3
if ($row['full_picture']=="")
{echo 'img/pictures/default.jpg"/>';}
else { echo $row['full_picture'];
echo '">';
}
?>
</div>
<div class="col-sm-4"
style=" background-color: white; color:black; padding-top: 10px;">
<h4 style="color:green;">
To arrange a viewing please click the Contact Us button
      and quote the reference number
<?php echo $ref_number . '</h4>';

echo '<p>&pound;';
echo $row['price'] . '</p>';
echo $row['full_description'];
?>
</div>
<?php
mysqli_free_result ($result); // Free up the resources.
}
else { // If it did not run OK.
// Message:
      echo '<p class="error">The record could not be retrieved. ';
      echo 'We apologize for any inconvenience.</p>';
      // Debugging error message:
//echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
}
mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e) // We finally handle any problems here
   {
// print "An Exception occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      // $date = date('m.d.y h:i:s');
      // $errormessage = $e->getMessage();
      // $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
      // error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      // error_log("Date/Time: $date – Exception Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Exception Error \n
//From: Error Log <errorlog@helpme.com>" . "\r\n");
   }
   catch(Error $e)
   {
      // print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      // $date = date('m.d.y h:i:s');
      // $errormessage = $e->getMessage();
      // $eMessage = $date . " | Error | " , $errormessage . |\n";
      // error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      // error_log("Date/Time: $date – Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Error \nFrom:
// Error Log <errorlog@helpme.com>" . "\r\n");
   }
?>
<div class="col-sm-3">
<?php include ('includes/menu.php'); ?>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

Listing 8-4House Details Page (house_details.php)

代码解释

本节解释代码。

<?php
session_start();
// Data from valid source?                                                               #1
if ((!empty($SESSION['user_level'])) && (!isset($_SESSION['previous_url'])
       or ($_SESSION['previous_url'] != "index")))
{ header("Location: index.php");
exit();
}
define('ERROR_LOG','errors.log');
?>

安全警卫再次要求所选择的数据来自索引页面,该页面又被传递到 found houses 页面。普通用户只有从索引页面开始并单击 found_houses 页面中的详细信息链接才能到达这里。一旦管理员登录到登录页面,就会将他们重定向到广告页面。代码没有关闭会话,以允许用户返回到“找到的房屋”页面并单击另一个链接(如果可用)。

// Make the query:                                                                       #2
$query = "SELECT price, full_description, full_picture ";
$query .= "FROM houses WHERE ref_number=?";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind values to SQL Statement
mysqli_stmt_bind_param($q, 's', $ref_number);

房屋参考号是通过 found_houses 页面中的链接传递的。house_details 页面使用这个参考号从数据库中提取房子的完整描述。

注意

管理员负责控制数据的格式。管理员将使用 HTML 和/或 CSS 来确保数据正确显示。由于房地产员工可能不知道这些技能,因此这项任务被分配给了网站管理员。

echo '" src="';                                                                        //#3
if ($row['full_picture']=="")
{echo 'img/pictures/default.jpg"/>';}
else { echo $row['full_picture'];
echo '">';

如果数据库中不存在全尺寸图片的链接,则提供默认图片。

现在让我们添加一些页面来帮助我们的管理员添加和查看房子。

创建管理/添加房屋页面

图 8-5a 显示管理员页面。

img/314857_2_En_8_Fig5a_HTML.jpg

图 8-5a

管理和添加房屋页面

图 8-5a 显示管理员页面(advert.php)。这是用于向数据库中的房屋表添加新房屋的页面。

让我们检查一下管理员页面上的元素。该页面包含四个下拉菜单,其中三个是索引页面中下拉菜单的副本;这些是卧室的位置、类型和数量。状态菜单是第四个下拉菜单,用于通知用户房子是可用的、报价中的还是已经售出的。价格字段不是下拉菜单,因为房价很少精确地设定在 40 万、30 万或 20 万英镑。

关于状态,你可能想知道为什么我们会进入销售。房子卖了为什么不从数据库里删除?房地产经纪人这样做有两个原因。

  • 潜在买家可能在某个较早的日期使用过该网站,并被某个特定的房子所吸引。后来,当他们看到房子被卖掉了,他们就没有必要去联系代理人,看看它是否还在市场上。

  • 如果潜在的访问者看到网站上列出了几个已出售的房屋,他们会确信代理人正在积极地出售房屋。

管理员将使用 phpMyAdmin 在适当的时间间隔后删除已售房屋。

假设缩略图在文件夹图像下的子文件夹拇指中,管理员必须按以下格式输入图像的 URL:

c://estaimg/house06.gif

必须以 300000 的格式输入价格(不含货币符号)。

全尺寸图片在图片文件夹下的图片子文件夹中,网址必须按以下格式输入:

c://estaimg/fullhouse06.gif

这些字段由管理员填写,当他们单击 Add 按钮时,详细信息被插入到数据库的 houses 表中。给出确认信息,如图 8-5b 所示。

img/314857_2_En_8_Fig5b_HTML.jpg

图 8-5b

显示确认消息

清单 8-5 给出了管理员页面的代码。

<?php
session_start();
if (!isset($_SESSION['user_level']) || ($_SESSION['user_level'] != 1))
{
      header("Location: login.php");
      exit();
}
define('ERROR_LOG','errors.log');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
      //require("cap.php");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Add Home Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
      integrity=
      "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
      crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="transparent.css">
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header>
      <?php include('includes/header_advert.php'); ?>
</header>
<?php
// This script is a query that INSERTs a record in the houses table.
// Check that form has been submitted:
if ( ($_POST['submit'] == 'Add')) {
       // only accept values from same site via post
try {
       $errors = array(); // Initialize an error array.
       require('mysqli_connect.php'); // Connect to the db.
       // Check for a location
       $location = filter_var($_POST['location'], FILTER_SANITIZE_STRING);
       if ((empty($location)) || ($location == '- Select -')) {
             $errors[] = 'You forgot to enter the location.';
       } else {
             if (($location == "South_Devon") ||
                    ($location == "Mid_Devon") ||
                    ($location == "North_Devon"))
             {
                    // OK
             } else {
                    $errors[] = "Invalid location";
             }
      }
// Has a price been entered?
$price = filter_var( $_POST['price'], FILTER_SANITIZE_NUMBER_INT);
if ((empty($price)) || (strlen($price) > 15)) {
      $errors[] ='You forgot to enter the price.' ;
}
// check type
$type = (filter_var($_POST['type'], FILTER_SANITIZE_STRING));
      if ((empty($_POST['type'])) || ($_POST['type'] == '- Select -')) {
             // user could choose - Select - by mistake
             $errors[] = 'You forgot to enter the type of house.';
      } else {
             if (($type == "Det-bung") ||
                    ($type == "Sem-det-bung") ||
                    ($type == "Det-house") ||
                    ($type == "Semi-det-house"))
             {
                    //OK
             } else {
                    $errors[] = "Invalid type";
             }
      }
// Check for brief description

$mini_descriptiontrim = filter_var( $_POST['mini_description'], FILTER_SANITIZE_STRING);
if ((!empty($mini_descriptiontrim)) && (preg_match('/[a-z0-9\.\!\?\s\,\-]/i', $mini_descriptiontrim)) &&
       (strlen($mini_descriptiontrim) <= 120)) {
       $mini_description = $mini_descriptiontrim;
}else{
       $errors[] = 'Missing description. Only numeric, alphabetic, period, comma, dash and space. Max 120.';
}
       // Check for number of bedrooms
       $bedrooms = filter_var( $_POST['bedrooms'], FILTER_SANITIZE_NUMBER_INT);
       if ((empty($bedrooms)) || ($bedrooms == '- Select -')) {
             $errors[] = 'You forgot to enter the number of bedrooms';
       } else {
             if (($bedrooms == "1") ||
                    ($bedrooms == "2") ||
                    ($bedrooms == "3") ||
                    ($bedrooms == "4"))
             {
                    // OK
             } else {
                    $errors[] = "Invalid number of bedrooms";
             }
      }
// Check if a thumbnail url has been entered
$thumb = filter_var( $_POST['thumb'], FILTER_SANITIZE_URL);
if ((empty($thumb)) || (strlen($thumb > 45))) {
      // thumbnail link is optional
      $thumb = NULL;
}
// Check if full description has been entered
$full_descriptiontrim =
      filter_var( $_POST['full_description'], FILTER_SANITIZE_STRING);
if ((!empty($full_descriptiontrim)) &&
      (preg_match('/[a-z0-9\.\!\?\s\,\-]/i', $full_descriptiontrim)) &&
      (strlen($full_descriptiontrim) <= 400)) {
      $full_description = $full_descriptiontrim;
}else{
      $errors[] =
      'Missing description. Only numeric, alphabetic, period, comma, dash and space. Max 30.';
      }
      // full picture
      $full_picture = filter_var( $_POST['full_picture'], FILTER_SANITIZE_URL);
             if ((empty($full_picture)) || (strlen($full_picture) > 45)){
             // optional
                     $full_picture = NULL;
             }
      // Check for status of the house

      $status = filter_var( $_POST['status'], FILTER_SANITIZE_STRING);
             if ((empty($status)) || ($status == '- Select -')) {
                     $errors[] = 'You forgot to select a status';
             } else {
             if (($status == "Available") ||
                    ($status == "Under offer") ||
                    ($status == "Withdrawn") ||
                    ($status == "Sold"))
             {
                    // OK
             } else {
                    $errors[] = "Invalid status";
             }
      }
if (empty($errors)) { // If everything's OK.
      // Register the house in the database
      // Make the query:
      $query = "INSERT INTO houses (ref_number, location, price, type, mini_description, bedrooms, ";
      $query .= "thumb, status, full_description, full_picture) ";
      $query .= " VALUES ";
      $query .= "(' ', ?, ?,?,?,?,?,?,?,? )";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// use prepared statement to ensure that only text is inserted
// bind fields to SQL Statement
mysqli_stmt_bind_param($q, 'sssssssss', $location, $price, $type, $mini_description, $bedrooms,
      $thumb, $status, $full_description, $full_picture);
// execute query
mysqli_stmt_execute($q);
if (mysqli_stmt_affected_rows($q) == 1) {
      // Good
      header ("location: another.php");
} else { // If it did not run OK.
      // Message:
      $errorstring = 'System Error ';
      $errorstring .= 'The house could not be added due to a system error. ';
      $errorstring .= 'We apologize for any inconvenience.';
      // Debugging message:
      // echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
      } // End of if ($r) IF.
mysqli_close($dbcon); // Close the database connection.
exit();
} else { // Report the errors.
      $errorstring = 'Error!';
      $errorstring .= ' The following error(s) occurred:<br>';
      foreach ($errors as $msg) { // Print each error.
             $errorstring .= " - $msg<br>\n";
      }
             $errorstring .= 'Please try again.';
}// End of if (empty($errors)) IF.
} // try
catch(Exception $e) // We finally handle any problems here
   {
      // print "An Exception occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      //  $date = date('m.d.y h:i:s');
      //  $errormessage = $e->getMessage();
      //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
      //   error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      //  error_log("Date/Time: $date – Exception Error, Check error log for
      //details", 1, noone@helpme.com, "Subject: Exception Error \nFrom: Error Log
      //<errorlog@helpme.com>" . "\r\n");
   }
   catch(Error $e)

   {
      // print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      // $date = date('m.d.y h:i:s');
      // $errormessage = $e->getMessage();
      // $eMessage = $date . " | Error | " , $errormessage . |\n";
      // error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      //  error_log("Date/Time: $date – Error, Check error log for
      // details", 1, noone@helpme.com, "Subject: Error \nFrom:
      // Error Log <errorlog@helpme.com>" . "\r\n");
   }
} // End of the main Submit conditional.
?>
<div class="content mx-auto" id="contents" style="padding-top:10px">
<!-- Body Section -->
  <div class="row" style="padding-left: 0px;">
<div class="col-sm-8">
<form action="advert.php" method="post" name="advert" id="advert">
<!--START OF TEXT FIELDS-->
<div class='form-group row'>
      <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
      <h3>Add a House</h3>
      <h5>
      <?php
      If (!empty($errorstring)) {
            echo $errorstring;
      }
      ?></h5>
</div>
</div>
<div class="form-group row">
      <label for="location" class="col-sm-4 col-form-label text-right">
        Location:</label>
        <div class="col-sm-8">
      <select id="location" name="location" class="form-control" required>
             <option value="">- Select -</option>
             <option value="South_Devon">South Devon</option>
             <option value="Mid_Devon">Mid Devon</option>
             <option value="North_Devon">North Devon</option>
      </select>
</div>
</div>
<div class="form-group row">
      <label for="price" class="col-sm-4 col-form-label text-right">Price:</label>
<div class="col-sm-8">
      <input type="num" class="form-control" id="price" name="price"
             placeholder="Price" maxlength="15"
             pattern="[0-9\.]*"
             title="Numbers only max of 120 characters"
             value=
                    "<?php if (isset($_POST['price']))
             echo htmlspecialchars($_POST['price'], ENT_QUOTES); ?>" >
    </div>
</div>
<div class="form-group row">

      <label for="type" class="col-sm-4 col-form-label text-right">
        Type:</label>
        <div class="col-sm-8">
     <select id="type" name="type" class="form-control" required>
      <option value="">- Select -</option>
      <option value="Det-bung">Detached Bungalow</option>
      <option value="Sem-det-bung">Semi-detached Bungalow</option>
      <option value="Det-house">Detached House</option>
      <option value="Semi-det-house">Semi-detached House</option>
      </select>
</div>
</div>
<div class="form-group row">
    <label for="thumb" class="col-sm-4 col-form-label text-right">Thumbnail URL</label>
    <div class="col-sm-8">
      <input type="url" class="form-control" id="thumb" name="thumb"
        placeholder="Thumbnail URL" maxlength="45"
         value=
            "<?php if (isset($_POST['thumb']))
            echo htmlspecialchars($_POST['thumb'], ENT_QUOTES); ?>" >
    </div>
  </div>
 <div class="form-group row">
      <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
      <label for="comment">Please enter your Brief Description below</label>
      <textarea class="form-control" id="mini_description"
            name="mini_description" rows="3" cols="40"
            pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-\?\!]*"
            title="Alphabetic, numbers, comma, ., -, ?, !, space only max of 120 characters"
            value=
            " <?php if (isset($_POST['mini_description']))
            echo htmlspecialchars($_POST['mini_description'], ENT_QUOTES); ?>" >
      </textarea>
 </div>
 </div>
<div class="form-group row">
      <label for="bedrooms" class="col-sm-4 col-form-label text-right">
             Bedrooms:</label>
<div class="col-sm-8">
      <select id="bedrooms" name="bedrooms" class="form-control" required>
             <option value="">- Select -</option>
             <option value="1">1</option>
             <option value="2">2</option>
             <option value="3">3</option>
             <option value="4">4</option>
      </select>
</div>
</div>
<div class="form-group row">

      <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
      <label for="comment">Please enter your Full Description below</label>
      <textarea class="form-control" id="full_description" name="full_description"
             rows="10" cols="40"
             pattern="[a-zA-Z0-9][a-zA-Z0-9\s\.\,\-\?\!]*"
             title="Alphabetic, numbers, comma, ., -, ?, !, space only max of 400 characters"
             value=
                     "<?php if (isset($_POST['full_description']))
             echo htmlspecialchars($_POST['full_description'], ENT_QUOTES); ?>" >
      </textarea>
</div>
</div>
<div class="form-group row">
     <label for="full_picture" class="col-sm-4 col-form-label text-right">Full Picture URL</label>
      <div class="col-sm-8">
      <input type="url" class="form-control" id="full_picture" name="full_picture"
             placeholder="Full Picture URL" maxlength="45"
             value=
                     "<?php if (isset($_POST['full_picture']))
             echo htmlspecialchars($_POST['full_picture'], ENT_QUOTES); ?>" >
 </div>
 </div>
<div class="form-group row">
       <label for="status" class="col-sm-4 col-form-label text-right">
              Status:</label>
<div class="col-sm-8">
       <select id="status" name="status" class="form-control" required>
              <option value="">- Select -</option>
              <option value="Available">Available</option>
              <option value="Under offer">Under offer</option>
              <option value="Withdrawn">Withdrawn</option>
              <option value="Sold">Sold</option>
       </select>
</div>
</div>
<div class="form-group row">
       <label class="col-sm-4 col-form-label"></label>
  <div class="col-sm-8">
  <div class="float-left g-recaptcha" style="padding-left: 50px;"
       data-sitekey="6LcrQ1wUAAAAAPxlrAkLuPdpY5qwS9rXF1j46fhq"></div>
  </div>
  </div>
<div class="form-group row">
       <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8 text-center">
       <input id="submit" class="btn btn-primary" type="submit" name="submit" value="Add">
</div>
</div>
</form><!-- End of the add house content. -->
</div>
<!-- Left-side Column Menu Section -->
        <nav class="col-sm-4">
              <?php include('includes/menu.php'); ?>
       </nav>
</div>
</div>
<div>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
      <footer>
             <?php include ('includes/footer.php'); ?>
      </footer>
</div>
</div>
</div>
</div>
</body>
</html>

Listing 8-5Creating the Admin

istator’s Page (advert.php)

HTML5 代码使用 required 属性来确保用户在必填字段中输入信息。下拉框允许用户仅从项目列表中进行选择。这提供了良好的验证,因为不能输入无效的条目。用户在表单中输入信息后,从文本框接收的信息将被验证。使用的编码类似于前一章中使用的编码。因此,我们不会在这里重复解释。

您可能已经注意到,管理员页面的标题不同于一般的标题。我们接下来将对此进行研究。

管理员页面的页眉

三个管理页面需要两个额外的按钮,因为宽表显示填满了内容区域,没有给主菜单留下空间。

图 8-6 显示了两个额外的按钮。

img/314857_2_En_8_Fig6_HTML.jpg

图 8-6

显示了两个额外的菜单按钮

清单 8-6 给出了管理员头的代码。

<div class="jumbotron text-center row mx-auto" id="includeheader">
<div class="col-sm-10">
<h1 class="text-left"><strong>Devon Real Estate</strong></h1>
<h2 class="text-center">Try our award winning service</h2>
</div>
  <nav class="col-sm-2">
       <div class="btn-group-vertical btn-group-sm" role="group"
                   style="width: 140px; margin-top:-25px;"
                   aria-label="Button Group">
                   <button type="button" class="btn btn-secondary" id="buttons"
                   onclick="location.href = 'advert_houses.php'" >
                          View Houses</button>
                   <button type="button" class="btn btn-secondary" id="buttons"
                          onclick="location.href = 'advert_search.php'">
                                  Search</button>
                   <button type="button" class="btn btn-secondary" id="buttons"
                          onclick="location.href = 'index.php'">Home Page</button>
</div>
</nav>
</div>
<img id="rosette1" alt="Rosette" title="Rosette" height="127"
        src="img/rosette-128.png" width="128">

Listing 8-6Creating the Header with One Extra Button (header_3btn.php)

管理员可以使用分页显示来查看整个库存。这将在下面讨论。

管理员对所有待售房屋的查看

图 8-7 显示了全库存视图的第一页。

img/314857_2_En_8_Fig7_HTML.jpg

图 8-7

全库存展示中的一页

清单 8-7a 显示了显示房屋存量的代码。

全库存显示的代码使用了与前面章节中描述的类似的表格和分页。因此,不对代码进行解释。

<?php
session_start();
if (!isset($_SESSION['user_level']) || ($_SESSION['user_level'] != 1))
{
      header("Location: login.php");
      exit();
}
define('ERROR_LOG', 'errors.log');
?>
<!DOCTYPE html>
<html lang="en">
<head>
      <title>Admin View All Houses</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
       <!-- Bootstrap CSS File -->
       <link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
              integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
             crossorigin="anonymous">
      <link rel="stylesheet" type="text/css" href="transparent.css">
      <script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:10px">

<!-- Header Section -->
<header>
      <?php include('includes/header_3btn.php'); ?>
</header>
<div class="content mx-auto" id="contents" style="padding-top:10px">
<!-- Body Section -->
 <div class="row" style="padding: 10px;">
<div class="col-sm-12">
      <h4 class="text-center">Houses displayed four at-a-time</h4>
      <h5>
             <?php
             If (!empty($errorstring)) {
                   echo $errorstring;
             }
             ?>
      </h5>
<?php
      try {
      // This script retrieves all the records from the users table.
      require ('mysqli_connect.php'); // Connect to the database.
      //set the number of rows per display page
      $pagerows = 4;
      // Has the total number of pages already been calculated?
      if (isset($_GET['pages'])) {
            $pages = (filter_var($_GET['pages'], FILTER_SANITIZE_NUMBER_INT));
      } else {
            //use the next block of code to calculate the number of pages
            //First, check for the total number of records
            $query = "SELECT COUNT(ref_number) FROM houses";
            $result = mysqli_query ($dbcon, $query);
            $row = mysqli_fetch_array ($result, MYSQLI_NUM);
            $records = $row[0];
            //Now calculate the number of pages
            if ($records > $pagerows){ //if the number of records will fill more than one page
                   //Calculate the number of pages and round the result up to the nearest integer
                   $pages = ceil ($records/$pagerows);
            }else{
                   $pages = 1;
            }
      }//page check finished. Declare which record to start with
      If (isset($_GET['start'])) {
            $start = (filter_var($_GET['start'], FILTER_SANITIZE_NUMBER_INT));
      } else {
            $start = 0;
      }
// Make the query:
$query = "SELECT ref_number, location, thumb, price, mini_description, bedrooms, status ";
$query .= "FROM houses ORDER BY ref_number DESC LIMIT ?, ?";
 $q = mysqli_stmt_init($dbcon);
 mysqli_stmt_prepare($q, $query);
 // use prepared statement to ensure that only text is inserted
 // bind fields to SQL Statement
 mysqli_stmt_bind_param($q, 'ii', $start, $pagerows );
 // execute query
 mysqli_stmt_execute($q);

$result = mysqli_stmt_get_result($q);
if ($result) { // If it ran OK, display the records.
      // Table header.
      echo '<table class="table table-striped table-sm" style="color: black; background-color:white;">
      <tr>
            <th scope="col">Ref-Num</th>
            <th scope="col">Location</th>
            <th scope="col">Thumb</th>
            <th scope="col">Price</th>
            <th scope="col">Features</th>
            <th scope="col">Bedrooms</th>
            <th scope="col">Status</th>
      </tr>';
      // Fetch and print all the records:
      while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
            // Remove special characters that might already be in table to
            // reduce the chance of XSS exploits
            $ref_number = htmlspecialchars($row['ref_number'], ENT_QUOTES);
            $location = htmlspecialchars($row['location'], ENT_QUOTES);
            $thumb = htmlspecialchars($row['thumb'], FILTER_FLAG_NO_ENCODE_QUOTES);
            $price = htmlspecialchars($row['price'], ENT_QUOTES);
            $mini_description = htmlspecialchars($row['mini_description'], ENT_QUOTES);
            $bedrooms = htmlspecialchars($row['bedrooms'], ENT_QUOTES);
            $status = htmlspecialchars($row['status'], ENT_QUOTES);
            echo '<tr>
                  <td scope="row">' . $ref_number . '</td>
                  <td scope="row">' . $location . '</td>
                  <td scope="row"><img src='. $thumb . '></td>
                  <td scope="row">' . $price . '</td>
                  <td scope="row">' . $mini_description . '</td>
                  <td scope="row">' . $bedrooms . '</td>
                  <td scope="row">' . $status . '</td>
            </tr>';
      }
      echo '</table>'; // Close the table.
      mysqli_free_result ($result); // Free up the resources.
} else { // If it did not run OK.
      // Message:
      $errorstring = '<p class="text-center">The record could not be retrieved. ';
      $errorstring .= 'We apologize for any inconvenience.</p>';
      // Debugging message:
      //echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
} // End of if ($result). Now display the total number of records/houses
$q = "SELECT COUNT(ref_number) FROM houses";
$result = mysqli_query ($dbcon, $q);
$row = mysqli_fetch_array ($result, MYSQLI_NUM);
$houses = (filter_var($row[0], FILTER_SANITIZE_NUMBER_INT));
mysqli_close($dbcon); // Close the database connection.
echo "<p class='text-center' style='color:black'>Total found: $houses</p>";
if ($pages > 1) {
      echo '<h5 class="text-center">';
      //What number is the current page?
      $current_page = ($start/$pagerows) + 1;
      //If the page is not the first page then create a Previous link

      if ($current_page != 1) {
            echo '<a href="advert_houses.php?start=' . ($start - $pagerows) . '&pages=' .
                   $pages . '">Previous</a> ';
      }
      //Create a Next link
      if ($current_page != $pages) {
            echo '<a href="advert_houses.php?start=' . ($start + $pagerows) . '&pages=' .
                   $pages . '">Next</a> ';
      }
echo '</h5>';
}
}
catch(Exception $e) // We finally handle any problems here
   {
      // print "An Exception occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      //  $date = date('m.d.y h:i:s');
      //  $errormessage = $e->getMessage();
      //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
      //   error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      //  error_log("Date/Time: $date – Exception Error, Check error log for
      // details", 1, noone@helpme.com, "Subject: Exception Error \nFrom: Error Log
      // <errorlog@helpme.com>" . "\r\n");
   }
   catch(Error $e)
   {
      // print "An Error occurred. Message: " . $e->getMessage();
      print "The system is busy please try later";
      // $date = date('m.d.y h:i:s');
      // $errormessage = $e->getMessage();
      // $eMessage = $date . " | Error | " , $errormessage . |\n";
      // error_log($eMessage,3,ERROR_LOG);
      // e-mail support person to alert there is a problem
      //  error_log("Date/Time: $date – Error, Check error log for
      // details", 1, noone@helpme.com, "Subject: Error \nFrom: Error Log <errorlog@helpme.com>" . "\r\n");
   }
?>
</div><!-- End of table display content -->
</div>
</div>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
      <footer>
             <?php include ('includes/footer.php'); ?>
      </footer>
</div>
</div>
</div>
</body>
</html>

Listing 8-7aCreating a Paginated Table of the Entire Stock of Houses (advert_houses.php)

管理员还可以通过使用房屋参考号来搜索单个记录,如下一节所述。

管理员的搜索页面

图 8-8 显示了管理员的搜索页面。

img/314857_2_En_8_Fig8_HTML.jpg

图 8-8

管理员可以搜索特定的房屋

请注意,搜索页面的标题中添加了一个额外的菜单选项。您可以通过在编辑器中打开 header_4btn.php 文件来查看新头文件的代码。清单 8-8 给出了管理员搜索页面的代码。

<?php
session_start();
if (!isset($_SESSION['user_level']) || ($_SESSION['user_level'] != 1))
{
      header("Location: login.php");
      exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
      <title>Admin Search Page</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <!-- Bootstrap CSS File -->
      <link rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
      integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
      Crossorigin="anonymous">
      <link rel="stylesheet" type="text/css" href="transparent.css">
      <script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:10px">

<!-- Header Section -->
<header>
      <?php include('includes/header_4btn.php'); ?>
</header>
<div class="content mx-auto" id="contents" style="padding-top:10px">
<!-- Body Section -->
<div class="row" style="padding-left: 0px;">
<div class="col-sm-10">
<form action="found_houses.php" method="post" name="find" id="find">
<!--START OF TEXT FIELDS-->
<div class='form-group row'>
      <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
      <h3>Search for a record</h3>
      <h5>Enter the Reference Number</h5>
      <h5>
      <?php
      If (!empty($errorstring)) {
            echo $errorstring;
      }
      ?>
      </h5>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"></div>
      <label for="ref_number" class="col-sm-4 col-form-label text-right">Reference Number:</label>
      <div class="col-sm-4">
      <input type="num" class="form-control" id="ref_number" name="ref_number"
            placeholder="Reference Number" maxlength="30"
                    pattern="[0-9]*"
            title="Numbers only max of 30 characters"
            value=
                    "<?php if (isset($_POST['ref_number']))
            echo htmlspecialchars($_POST['ref_number'], ENT_QUOTES); ?>" >
  </div>
  </div>
<div class="form-group row">
      <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8 text-center">
      <input id="submit" class="btn btn-primary" type="submit" name="submit" value="Search">
</div>
</div>
</form><!-- End of the add house content. -->
</div>
</div>
</div>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
<footer>
      <?php include ('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 8-8The Administrator’s Search Page (advert_search.php

)

当点击搜索按钮时,显示与房屋参考号相关的记录。

搜索的结果

图 8-9 显示管理员可以显示任何指定的房屋。

img/314857_2_En_8_Fig9_HTML.jpg

图 8-9

选择并显示记录

前面解释过的 found_houses.php 页面被调用来通过参考号显示单个房屋。

$query = "SELECT ref_number, location, thumb, price, mini_description, type, bedrooms, ";
$query .= "status FROM houses ";
if(($user_level == 1) && (!empty($_POST['ref_number']))) {
$query .= "WHERE ref_number=? ";
} else {
$query .= "WHERE location= ? AND ";
$query .= "(price <= ?) AND (price >= (? - 100000)) AND ";
$query .= "type= ? AND bedrooms= ?  ORDER BY ref_number ASC ";
}

这个来自 found houses 页面的代码片段将使用参考号而不是多个字段来提取管理员指定的特定记录。

<?php
if ($user_level == 0)
{
include("includes/header_found_houses.php");
}
else if($user_level==1)
{
include('includes/header_4btn.php');
}
?>

位于 found houses 页面顶部的这段代码将使显示的页面个性化,包括管理员标题,而不是所有用户的通用标题。这将为管理员提供查看其他页面的按钮。代码的其余部分与站点访问者看到的一样。

注意

“找到的房屋”页面不包含编辑或删除房屋的方法。该页面的存在纯粹是为了让用户可以查看房子的详细信息。网站管理员是管理员,是唯一被允许编辑或删除房屋的人。许多数据库管理系统(DBMSs)能够通过将表字段拖放到表单上来快速创建管理员可以使用的输入和编辑页面。MySQL 和 MariaDB 的免费版本没有这个功能。但是,您会发现 Microsoft Access 确实提供了表单生成功能。如果不能快速生成表单,管理员还可以使用 phpMyAdmin 来输入或修改数据。

“联系我们”页面

图 8-10 显示了一个合适的联系我们页面的示例。这是第七章第七章的联系我们页面的精简版。

img/314857_2_En_8_Fig10_HTML.jpg

图 8-10

“联系我们”页面

清单 8-10a 给出了显示表单的代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Contact Us Form</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
      integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
      crossorigin="anonymous">
<script src='https://www.google.com/recaptcha/api.js'></script>
<link rel="stylesheet" type="text/css" href="transparent.css">
</head>
<body>
<div class="container" style="margin-top:30px">

<!-- Header Section -->
<header>
      <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
 <div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Center Column Content Section -->
  <div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
  <div class="row" style="padding-left: 0px;">
<!-- Left-side Column Menu Section -->
<div class="col-sm-8">
<form action="contact-handler.php" method="post" name="feedbackform" id="feedbackform">
 <!-- Validate Input -->
 <div class="form-group row">
<div class="col-sm-4"></div>
<div class="col-sm-8">
      <h4 class="text-center">Contact us to Arrange a Viewing</h4>
      <h5 class="text-center"><strong>Address:</strong>
            1 The Street, Townsville, AA6 8PF, <strong>Tel:</strong> 01111 800777</h5>
      <h5 class="text-center"><strong>To contact us:</strong>
            Please use this form and click the Send button at the bottom.</h5>
      <h5 class="text-center">Essential items are marked with an asterisk</h5>
</div>
</div>
<!--START OF TEXT FIELDS-->
<div class="form-group row">
      <label for="username" class="col-sm-4 col-form-label text-right">Your Name*:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="username" name="username"
            pattern="[a-zA-Z][a-zA-Z\s]*" title="Alphabetic and space only max of 30 characters"
            placeholder="Your Name" maxlength="30" required
            value=
                    "<?php if (isset($_POST['username']))
            echo htmlspecialchars($_POST['username'], ENT_QUOTES); ?>" >
 </div>
 </div>
<div class="form-group row">
      <label for="useremail" class="col-sm-4 col-form-label text-right">E-mail*:</label>
<div class="col-sm-8">
      <input type="email" class="form-control" id="useremail" name="useremail"
            placeholder="Your E-mail" maxlength="60" required
            value=
                    "<?php if (isset($_POST['useremail']))
            echo htmlspecialchars($_POST['useremail'], ENT_QUOTES); ?>" >
 </div>
 </div>
 <div class="form-group row">
       <label for="phone" class="col-sm-4 col-form-label text-right">Telephone*:</label>
 <div class="col-sm-8">
       <input type="tel" class="form-control" id="phone" name="phone"
             placeholder="Phone Number" maxlength="30"
             value=
                     "<?php if (isset($_POST['phone']))
             echo htmlspecialchars($_POST['phone'], ENT_QUOTES); ?>" >
</div>
</div>
<div class="form-group row">
      <label for="" class="col-sm-4 col-form-label text-right"></label>
      <h5 class="col-sm-8 text-center">To request a viewing please enter the reference number of the house below</h5>
</div>
<div class="form-group row">
      <label for="address1" class="col-sm-4 col-form-label text-right">

            House Reference Number*:</label>
<div class="col-sm-8">
      <input type="num" class="form-control" id="ref_number" name="ref_number"
            pattern="[0-9]*"
            title="Numbers only max of 30 characters"
            placeholder="Reference Number" maxlength="30" required
            value=
                    "<?php if (isset($_POST['ref_number']))
            echo htmlspecialchars($_POST['ref_number'], ENT_QUOTES); ?>" >
  </div>
  </div>
<div class="form-group row">
      <label for="" class="col-sm-4 col-form-label text-right"></label>
<div class="col-sm-8 text-center">
      <label for="comment" style="color:white;">Please enter your message below</label>
      <textarea class="form-control" id="comment" name="comment" rows="8" cols="40"
             value="<?php if (isset($_POST['comment']))
             echo htmlspecialchars($_POST['comment'], ENT_QUOTES); ?>" >
      </textarea>
 </div>
 </div>
 <div class="form-group row">
      <label class="col-sm-4 col-form-label"></label>
 <div class="col-sm-8">
 <div class="g-recaptcha" style="margin-left: 80px;"
      data-sitekey="6LcrQ1wUAAAAAPxlrAkLuPdpY5qwS9rXF1j46fhq"></div>
 </div>
 </div>
<div class="form-group row">
      <label for="" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8 text-center">
      <input id="submit" class="btn btn-primary" type="submit" name="submit" value="Send">
</div>
</div>
</form>
</div>
<div class="col-sm-4">
      <?php include ('includes/menu.php'); ?>
</div>
</div>
</div>
</div>
</div>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12 text-center" style="padding:0px; margin-top: 5px;">
      <footer>
             <?php include ('includes/footer.php'); ?>
      </footer>
</div>
</div>
</div>
</body>
</html>

Listing 8-10aCreating the Contact Us Page (contact.php)

表格的格式和信息的验证类似于第七章中解释的代码。因此,本章不再重复解释。

我们现在将检查清单 8-10b 中的联系人表单处理程序。

<?php
// Feedback form handler
// set the error and thank you pages
$formurl = "feedback/feedback_form.html" ;
$errorurl = "feedback/error.html" ;
$thankyouurl = "feedback/thankyou.html" ;
$emailerrurl = "feedback/emailerr.html" ;
$errorcommenturl =  "feedback/commenterror.html" ;
// set to the email address of the recipient
$mailto = "none@noone.com" ;
// Is first name present? If it is, sanitize it
$username = filter_var( $_POST['username'], FILTER_SANITIZE_STRING);
if ((!empty($username)) && (preg_match('/[a-z\s]/i',$username)) &&
            (strlen($username) <= 30)) {
             //Save user name
             $usernametrim = $username;
}else{
      $errors = 'yes';
}
// Check that an email address has been entered correctly
$useremailtrim = filter_var( $_POST['useremail'], FILTER_SANITIZE_EMAIL);
if  ((empty($useremailtrim)) || (!filter_var($useremailtrim, FILTER_VALIDATE_EMAIL))
                   || (strlen($useremailtrim > 60))) {
                   // if email is bad display error page
                   header( "Location: $emailerrurl" );
                   exit ;
}
// Is the phone number present? if so, sanitize it
$phone = filter_var( $_POST['phone'], FILTER_SANITIZE_STRING);
if ((!empty($phone)) && (strlen($phone) <= 30)) {
       //Sanitize and validate phone number
       $phonetrim = (filter_var($phone, FILTER_SANITIZE_NUMBER_INT));
       $phonetrim = preg_replace('/[⁰-9]/', “, $phonetrim);
}else{
       $phonetrim = NULL; // if not valid or missing do not save
}
       /
$ref_number = filter_var( $_POST['ref_number'], FILTER_SANITIZE_STRING);
if ((!empty($ref_number)) && (preg_match('/[0-9]/', $ref_number)) &&
  (strlen($ref_number) <= 30)) {
       //Save the 1st address
       $ref_numbertrim = $ref_number;
}else{
       $errors = 'yes';
}
$comment = filter_var( $_POST['comment'], FILTER_SANITIZE_STRING);
if ((!empty($comment)) && (strlen($comment) <= 320)) {
       // remove ability to create link in email
       $patterns = array("/http/", "/https/", "/\:/","/\/\//","/www./");
       $commenttrim = preg_replace($patterns," ", $comment);
       }else{ // if comment not valid display error page

       header( "Location: $errorcommenturl" );
       exit;
}
if (!empty($errors)) { // if errors display error page
      header( "Location: $errorurl" );
      exit ; }
// everything OK send e-mail
$subject = "Message from customer " . $usernametrim;
$messageproper =
      "------------------------------------------------------------\n" .
      "Name of sender: $usernametrim\n" .
      "Email of sender: $useremailtrim\n" .
      "Telephone: $phonetrim\n" .
      "Ref Number: $ref_numbertrim\n" .
      "------------------------- MESSAGE -------------------------\n\n" .
      $commenttrim .
      "\n\n------------------------------------------------------------\n" ;
      mail($mailto, $subject, $messageproper, "From: \"$usernametrim\" <$useremailtrim>" );
      header( "Location: $thankyouurl" );
      exit ;
?>

Listing 8-10bCreating the Contact Form Handler

(contact_handler.php)

邮件发出后,会出现一个“谢谢”页面。如果信息输入不正确或所需信息缺失,将显示一条错误消息。由于该代码类似于第七章中的代码,请查看那里的解释。请记住,除非该代码驻留在能够通过程序代码发送电子邮件的服务器上,否则它不会执行。

注意

“谢谢”页面和错误信息页面包含在可下载文件中,它们与第七章中的页面相似。

摘要

在本章中,您学习了如何为房地产目录规划数据库。我们创建了一个主页,用户可以在其中搜索合适的房子。我们制作了一个房屋详细信息页面,为用户提供来自数据库的更多细节。我们为管理员提供了一个页面,以便他们可以添加新的房屋。我们制作了一个页面,允许管理员查看所有的房屋,或者搜索和查看特定的房屋。我们为想要亲自检查房子的用户创建了一个查询表单。

在下一章中,您将学习如何通过连接多个表来提取数据,如何创建允许通过支票付款的表单,以及如何实现打印在线表单的经济方法。

九、连接多个表和其他增强功能

数据库驱动的网站可以从本章描述的三个实用的改进中获益。例如,多个表可以给出更具体的搜索结果,并且对于论坛和电子商务网站的管理是必不可少的,会员费可以通过支票以及使用 PayPal 和信用卡/借记卡来支付,支票付款可以附带可打印的申请表。

完成本章后,您将能够

  • 使用 phpMyAdmin 创建多个表

  • 理解内部连接和外部连接的区别

  • 使用 phpMyAdmin 连接多个表

  • 使用程序代码连接多个表

  • 提供可打印的在线表格

多表介绍

前面的章节一次显示一个表中的信息。有时数据库使用几个可以相互关联的表。这些数据库被称为关系数据库。可以将每个表中的数据连接(组合)起来,形成可以在浏览器中显示的虚拟表。在本教程中,我们将完全专注于连接表的过程。为了简单明了,这些教程的网站将去掉几个功能,比如登录和管理。这些功能的按钮将出现在标题上,但它们将是死链接。

你可能想知道为什么我们需要不止一张桌子。为什么不把所有数据放在一个表中?我们可以用一张大桌子,但这会带来无穷无尽的麻烦。大量的信息将被多次输入,导致数据库效率低下,并且管理将是耗时和乏味的。

举个例子,假设我们有一个销售电话和对讲机的电子商务网站。我们可以有一组记录,如表 9-1 所示。

表 9-1

有许多重复条目的表

|

订单 id

|

用户姓名

|

地址

|

产品

|

股票

|
| --- | --- | --- | --- | --- |
| One thousand and six | 查理·史密斯 | 汤斯维尔公园路 3 号 TV77 99JP | 电话 123 | one |
| One thousand and seven | 查理·史密斯 | 汤斯维尔公园路 3 号 TV77 99JP | 对讲机 456 | four |
| One thousand and eight | 罗伯特·布鲁斯 | 4 林登街城市 UT88 66XY | 对讲机 456 | five |
| One thousand and nine | 内莉·迪恩 | 密尔沃基市榆树大道 7 号,邮编:78 88WZ | 电话 456 | six |
| One thousand and ten | 罗伯特·布鲁斯 | 4 林登街城市 UT88 66XY | 电话 678 | one |
| One thousand and eleven | 内莉·迪恩 | 米尔敦镇榆树大道 7 号 MT78 88WZ | 对讲机 396 | three |
| One thousand and twelve | 查理·史密斯 | 汤斯维尔公园路 3 号 TV77 99JP | 电话基座 A2 | four |

这个包含最新订单的单个表有许多问题。

在不同的日期,查理·史密斯点了三样东西,他的地址重复了三次。如果他改变了地址,我们就需要修改表中的三行。查理的名字和地址应该保存在一个单独的表中;这将允许我们只更改他的地址一次。库存水平通常会变化。我们目前必须滚动浏览记录,以确定特定商品的最低库存水平。库存水平和产品描述应该保存在另一个表中,这样当库存水平改变时,只需要更新一个表。当显示产品时,用户和管理员可以容易地看到库存水平的准确数字。

正常化

消除重复和其他维护问题的过程称为规范化。表 9-1 包含了说明非常不良做法的项目。名字和姓氏应该在不同的列中。地址应该按照街道、城镇和邮政编码分成单独的列。这个过程被称为“原子性”这个相当笨拙的名字,因为像原子一样,数据被分成最小的成分。于是原子不可分

规范化是通过应用严格的原子性并将数据拆分成几个表来实现的,而不是只使用一个表,这样每个表都有一个特定的单一用途。此外,每个表中的信息必须有密切相关的数据,如名字、姓氏和电子邮件地址;如果客户更改了他或她的电子邮件地址,您只需修改一条记录。规范化允许数据库被容易地扩展,以便它能保存更多类型的信息。例如,可以添加一个表来保存手机的颜色,或者可以有一个表来保存发货日期。

开始时规范化可能很难理解,但是如果应用原子性,然后将密切相关的数据分组到单独的表中,您将自动规范化您的表。通过将数据分解到最低级别,您可以考虑未来的增长和更好的数据控制,并在未来为自己留下更多修改和操作数据的选项。能够在多个表上建立一对多的关系可以减少数据冗余。

注意

在大多数情况下,正确设计的数据库表(至少)是第三范式(3NF)。

当一个表具有由以下要求定义的关系时,它被认为是第一范式(1NF)。

  • 每个单元格保存一个值。

  • 所有列值都属于同一类型。

  • 每个列都是唯一标识的(列名)。

  • 这些列可以按任何顺序排列。

  • 这些行可以按任何顺序排列。

  • 每行都包含唯一的数据。

如果一个表是 1NF 格式的,并且包含唯一标识每一行的主键,那么这个表就是第二范式(2NF)。如果一个表在 2NF 中,并且没有定义(依赖)于其他列的列,则该表为第三范式。数据是原子的或 ?? 的不可见的。另外,表可以被分类为第四范式(4NF)和第五范式(5NF)。然而,这些分类超出了本书的范围。

在某些情况下,数据库管理员可能会降低规范化程度,并跨表复制数据。这可以提高频繁使用的数据库中的数据检索效率,例如大型百货商店的库存数据库。

现在让我们创建一个包含两个表的数据库。

创建数据库和表

对于本教程的第一部分,我们将创建一个包含两个小表的数据库。在 phpMyAdmin 中,创建一个名为 birdsdb 的数据库,然后设置一个用户和密码,如下所示:

  1. 在 XAMPP 的 htdocs 文件夹或 EasyPHP 文件夹 eds_www 中,新建一个名为 birds 的文件夹。

  2. 出版社的页面下载第 9 章的文件。并将它们解压到新的鸟类文件夹中。

  3. 启动 XAMPP 或 EasyPHP,在浏览器的地址栏输入 localhost/phpmyadmin/ 访问 phpmyadmin。

  4. 单击 Databases 选项卡并创建一个名为 birdsdb 的数据库。从下拉排序规则列表中,选择 utf8_general_ci,然后单击创建。

  5. 单击权限选项卡,然后向下滚动并单击添加新用户。

  6. 输入这些详细信息:

    • 用户名:法拉第

    • 密码 : Dynam01831

    • 主机:本地主机

    • 数据库名称:birdsdb

  7. 单击开始。

查看连接文件

文件 mysqli_connect.php 的代码如下:

<?php
// Create a connection to the migrate database and to MySQL
// Set the encoding to utf-8
// Set the database access details as constants
define ('DB_USER', 'faraday');
define ('DB_PASSWORD', 'Dynam01831');
define ('DB_HOST', 'localhost');
define ('DB_NAME', 'birdsdb');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');

我们没有为 birds、location 和 rsvinfo 表提供转储文件。因此,您必须手动创建和填充这些表。这不会很乏味,因为表很小。

单击 phpMyAdmin 左侧面板中的数据库 birdsdb,手动创建一个名为 birds 的表,该表有四列,属性如表 9-2 所示。

表 9-2

鸟桌

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 伯德 _id | 中位 | four | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 鸟名 | 微小文本 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 稀薄 | 微小文本 | 60` | 没有人 |   | -是吗 |   | -是吗 |
| 最佳时间 | 微小文本 | Sixty | 没有人 |   | -是吗 |   | -是吗 |

birds 表包含一个名为 bird_id 的列。该列被配置为主键。

你会看到如图 9-1 所示的弹出对话框;记住不要输入大小,因为您已经在列描述中定义了它。

img/314857_2_En_9_Fig1_HTML.jpg

图 9-1

设置主索引

我们现在需要创建名为 location 的附加表。

创建第二个表

单击 phpMyAdmin 左侧面板中的数据库 birdsdb,手动创建一个名为 location 的表,该表有四列,属性如表 9-3 所示。将显示一个弹出窗口(如图 9-1 )让您确认 location_id 是主键。只需点击 Go 确认信息。

表 9-3

位置表的属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 位置标识 | 中位 | four | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 位置 | 微小文本 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 位置类型 | 微小文本 | 60` | 没有人 |   | -是吗 |   | -是吗 |
| 伯德 _id | 中位 | four | 没有人 | 无符号的 | -是吗 |   | -是吗 |

重要的

在位置表中,确保项目 bird_id 与 birds 表中的 bird_id 具有相同的名称、长度和类型。在我们的例子中,它们具有相同的名称(bird_id)、相同的长度(4)和相同的类型(MEDIUMINT)。这对于连接和显示多个表中的数据至关重要。

外键

在正常情况下,您不会期望在位置详细信息表中看到名为 bird_id 的项目。位置表中的 bird_id 项被称为外键。通过在两个表中包含 bird_id 列,我们将能够组合并显示从两个表中选择的数据;我们通过一个 JOIN 语句来实现这一点,稍后我们将对此进行解释。

警告

不要在表格中输入数据。当表准备好进行连接时,我们将稍后执行此操作。

同时,我们必须开始做一些准备。

准备用于连接的表格

现在需要为位置表设置索引。列 location_id 已经被索引,因为它是主键。location 表中名为 bird_id 的外键将被索引。在 phpMyAdmin 中,单击左侧面板中的位置表。选择 Structure 选项卡,并在右侧面板中选中 bird_id 列旁边的框。向下滚动到 Indexes 按钮,您将看到一条消息,表明定义了主索引,但没有定义外键。

向上滚动,然后点击图 9-2 右下角的索引(显示为圆圈)。如果您选中了 bird_id 左侧的框,那么将自动为您创建索引。向下滚动到索引区域。现在您应该可以看到主键和 bird_id 索引。

img/314857_2_En_9_Fig2_HTML.jpg

图 9-2

为外键创建索引

推广两个表

如前所述,因为我们没有用于注册鸟类或位置的页面,所以我们需要使用 phpMyAdmin 手动填充它们。这不会很乏味,因为表中包含的条目很少。此外,对于您自己对该书网站的改编,您将不会获得鸟类和位置表的 SQL 转储文件。因此,学习如何使用 phpMyAdmin 填充表是必不可少的。

要填充 birds 表,在 phpMyAdmin 中,单击左栏中的数据库 birdsdb(图 9-3 中的圆圈),然后单击 Structure 选项卡。您将在右窗格中看到这两个表。

img/314857_2_En_9_Fig3_HTML.jpg

图 9-3

使用 phpMyAdmin 填充 birds 表

选中 birds 表旁边的框(在图 9-3 中圈出),然后在 birds 记录(行)中点击插入图标(在图 9-3 中圈出)。现在将出现插入页面,如图 9-4 所示。

img/314857_2_En_9_Fig4_HTML.jpg

图 9-4

“插入”选项卡显示为黄金眼鸭插入的详细信息

在左侧,您会看到表格列有:鸟名、稀有度和最佳时间。在右侧的文本字段中,您可以填充这些项目。例如,你将在图 9-3 中看到,我们已经进入黄金眼、普通和冬季。

使用表 9-4 中的数据对其他鸟重复该程序。

表 9-4

鸟桌

|

伯德 _id

|

鸟名

|

稀薄

|

最佳时间

|
| --- | --- | --- | --- |
| one | 黄金眼 | 普通 | 冬天的 |
| Two | 歪脖 | 罕见的 | 夏天 |
| three | Avocet | 普通 | 冬天的 |
| four | 黑水鸡 | 普通 | 随时 |

现在继续使用 phpMyAdmin 并单击左侧面板中的位置表。使用 phpMyAdmin 填充位置表;该过程与您刚才用来填充 birds 表的过程相同。使用表 9-5 中所示的数据。

表 9-5

位置表

|

位置标识

|

位置

|

位置类型

|

伯德 _id

|
| --- | --- | --- | --- |
| one | 南方公园 | 池塘 | four |
| Two | 湿地(wetlands) | 河口 | three |
| three | 莱克兰 | 湖 | one |
| four | 摩尔菲尔德 | 沼地 | Two |
| five | 希斯维尔 | 灌木丛生的荒地 | Two |

现在这两个表都被填充和索引了,我们可以学习如何连接它们,以便数据可以被组合并显示在屏幕上。

注意

位置表中的 bird_id 值必须与 birds 表中现有的 bird_id 值相匹配,才能正确检索数据。如果您在 birds 表中为 bird_id 自动增加的数字与表 9-4 中的不同,请在 location 表中的 bird_id 列中进行调整以匹配您的值。bird_id 列是根据鸟类的栖息地填充的。黑水鸡栖息在池塘中,白鳍豚喜欢在河口生活,黄金眼鸭主要生活在湖泊中,而黄喉拟水鸭生活在高沼地和荒野中。

连接两个表中的数据

phpMyAdmin 应用为连接两个或多个表中的数据提供了一个简洁的过程。尝试以下步骤:

img/314857_2_En_9_Fig6_HTML.jpg

图 9-6

用于指定关系类型的弹出对话框

  1. 打开 phpMyAdmin 并在左侧面板中单击 birdsdb。

  2. 单击设计器选项卡;如果没有看到设计器选项卡,请单击更多下拉选项卡,然后单击设计器。

  3. 您应该看到这两个表;使用鼠标拖动它们,使它们大致处于图 9-5 所示的位置。

    img/314857_2_En_9_Fig5_HTML.jpg

    图 9-5

    放置桌子准备接合

  4. 在垂直工具栏上,单击创建关系图标,该图标在图 9-5 中用圆圈表示。

  5. 单击鸟类表中的 bird_id,然后单击位置表中的 bird_id。如果您以错误的顺序执行此操作(即,首先单击位置),您将会看到一条错误消息。

  6. 你会看到一个弹出面板,如图 9-6 所示。

从下拉菜单中,在两个字段中选择限制,然后单击确定。

注意

MySQL 和 MariaDB 在创建表之间的关系时允许几个选项。这些选项基于父表(birds)发生更新或删除时该做什么,以及这会如何影响子表(locations)。读取数据时(选择),实际关系并不重要。如何从表中提取信息的实际决定是由 SQL 语句本身决定的。

以下选项可用:

  • 级联:根据需要,所有表格都会发生变化。例如,如果 birds 表中的 bird_id 值被更改,它也会更改 locations 表中相应的 id。根据外键关系,这些更改可以通过多个表级联。如果从 birds 表中删除 bird_id 值,locations 表中的相应记录也将被删除。

  • Set NULL :对父表 birds 的任何更改都会导致在子表 locations 的相关主键中放置一个 NULL 值。如果 birds 表中的 bird_id 值被更改或删除,locations 表中相应的 bird_id 值将被更改为 NULL。

  • Restrict:DMBS 不会进行更新或删除,除非同一个 SQL 语句同时处理父表 birds 和子表 locations。如果 SQL 语句试图只更改父表的外键值,而不同时更改子表中的相关主键,该语句将被拒绝。此外,如果 SQL 语句试图删除父表记录或外键,而没有从子表中删除(或更改)相关记录(或主键),该语句将被拒绝。

我们的示例将 delete 和 update 值都设置为 restrict,如果 birds 表发生了变化,而 locations 表没有发生相应的变化,这将为管理员提供反馈。虽然本章介绍了如何从多个表中读取数据,但是您可以通过在 phpMyAdmin 中更改表并尝试更新或删除记录来测试这些关系如何影响表。

  1. 你会看到这些表格被链接(相关),如图 9-7 所示。

    img/314857_2_En_9_Fig7_HTML.jpg

    图 9-7

    这些表现在是相关的

  2. 点击图 9-7 中圈出的保存图标保存链接。

我们现在将使用 phpMyAdmin 创建第三个名为 reserves_info 的表;该表将包含有关保护区的信息,如进入鸟笼(庇护所)和门票(如有)。

创建第三个表

单击 phpMyAdmin 左侧面板中的数据库 birdsdb,手动创建一个名为 reserves_info 的表,该表包含五列以及表 9-6 中的属性。如上所示,验证主键。

表 9-6

reserves_info 表的结构和属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| reserses _ id | 中位 | four | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 鸟皮 | ENUM | 是','否' | 没有人 |   | -是吗 |   | -是吗 |
| 入口 _ 成员 | 微小文本 | 60` | 没有人 |   | -是吗 |   | -是吗 |
| 条目 _ 非成员 | 微小文本 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 位置标识 | 中位 | four | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |

重要的

遵循与前面位置表中 bird_id 相同的过程,将 location_id 作为索引。

使用 phpMyAdmin 中的 Insert 选项卡,用表 9-7 中给出的数据填充 reserves_info 表。

表 9-7

reserves_info 表的数据(门票是指门票)

|

储量 _id

|

鸟皮

|

入口 _ 成员

|

条目 _ 非成员

|

位置标识

|
| --- | --- | --- | --- | --- |
| one | 是 | 自由的 | one | one |
| Two | 是 | one | £2 | Two |
| three | 是 | 自由的 | one | three |
| four | 不 | 自由的 | 自由的 | four |
| five | 不 | 自由的 | 自由的 | five |

注意

reserves_info 表中的 location_id 值必须与 location 表中的 location_id 值相匹配。如果您的值与表 9-5 和表 9-7 中显示的不同,进行必要的调整,使值匹配。

创建并填充 reserves_info 表后,在 phpMyAdmin 中单击页面左侧的 birdsdb 数据库名称。然后单击 Designer 选项卡显示表之间的当前关系。您将看到前两个表及其链接,但可能看不到第三个表。

在这种情况下,单击垂直工具栏上最顶端的图标(图 9-8 中的圆圈),表格列表将出现在右侧的新面板中。将列出第三个表,但不会选中其复选框。选中第三个表格的框以显示该表格。将第三个工作台左右滑动,使其大致位于另外两个工作台的左上方,如图 9-8 所示。

img/314857_2_En_9_Fig8_HTML.jpg

图 9-8

在设计器视图中显示和定位第三个表

第三个表不会被链接,如图 9-8 所示。

我们现在需要在 location 表和 reserves_info 表之间创建一个关系。

  1. 在垂直工具栏中,单击创建关系图标(在图 9-9 中用圆圈显示)。

  2. 单击位置表中的位置标识,然后单击储量信息表中的位置标识。如果您以错误的顺序执行此操作(即,如果您首先在 reserves_info 表中单击 location_id),您将会看到一条错误消息。

  3. 当外键弹出窗口出现时,在两个字段中选择限制,然后单击开始。

现在你应该看到这些表格被链接起来,如图 9-9 所示。

img/314857_2_En_9_Fig9_HTML.jpg

图 9-9

创建并保存第二个和第三个表之间的关系

创建页面以显示连接表中的数据

连接的表是数据库管理系统(DBMS)中的虚拟表,公众看不到。

我们现在将创建一些页面,以便您的表格可以在公共浏览器中显示。除了主页之外,我们还将创建页面来显示鸟类表、保护区的位置、两个连接表和三个连接表。这些页面包含在章节 9 的可下载文件中。

主页

图 9-10 显示了我们网站的主页。

img/314857_2_En_9_Fig10_HTML.jpg

图 9-10

主页

清单 9-10 显示了主页的代码。

<!DOCTYPE html>
<html lang="en">
<head>
<title>Birds Home Page</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,
       shrink-to-fit=no">
<!-- Bootstrap CSS File -->
<link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="birds.css">
</head>
<body>
<div class="container" style="margin-top:30px;border: 3px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
        style="margin-bottom:2px; padding:20px;background-color:#CCFF99;">
               <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
               <?php include('includes/nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8 row" style="padding-left: 30px;">
<h2 style="padding-left: 50px; padding-top: 20px;">
         Help Save Our Devon Birds From Extinction</h2>
<div class="col-sm-8 text-left">
<p>The Devon bird reserves were established in an effort to combat the massive decline in the bird population. Farmers (the self proclaimed Guardians of the Countryside!) spray insecticides, weed killers and pesticides that kill the birds' main source of food. They also rip out the hedges that provide the birds with nesting sites and their means of travelling safely from field to field. Any birds that survive will probably be shot to satisfy a blood lust for living targets</p>
</div>
<div class="col-sm-4">
<h4 class="text-center"><strong>
         Become a member and support our cause</strong></h4>
<p class="text-left">
         The annual membership fee includes free or reduced entrance fees to the reserves, a free quarterly magazine, news updates and more.
</p>
</div>
</div>
<!-- Right-side Column Content Section -->
       <aside class="col-sm-2" style="padding-top: 20px;
               padding-right: 0px;">
               <?php include('includes/info-col.php'); ?>
        </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;
        background-color:#CCFF99;">
        <?php include('includes/footer.php'); ?>
</footer>
</div>
</body>
</html>

Listing 9-10The Code for the Home Page (index.php)

这段代码类似于我们讨论过的其他索引页面。因此,我们在这里没有提供额外的解释。

清单 9-10a 显示了网站的 CSS 样式表。

body {text-align:center; background-color:#CCFF99; color:green;
font-family: "times new roman";
font-size: 120%; margin: auto; }
#container {margin:auto; border:5px black solid; }

header {color:white; background-color:#CCFF99;}
}
label { color: black; }
#submit {margin: 0px; background:#559a55; border: 5px
    outset #559a55; width: 140px;}
#includemenu {padding-top: 10px; padding-bottom: 10px;
    padding-right: 0px;}
#includefooter {background:#68CE53; padding-top: 5px;
    padding-bottom: 5px; margin: 0px;}
#includeheader { height:auto; background:#95b522;
    margin-bottom: 0px; padding:0px;
    background:url('img/header3.jpg');
    background-repeat:no-repeat;}
#contents {background-color:transparent ;margin-top: -7px;
    color: black; margin: 0px; padding: 0px;}
   #buttons {background:#559a55; border: 5px outset #559a55;}

Listing 9-10aThe Code for the Style Sheet (birds.css)

页面的主菜单

主菜单上的按钮将使公众能够查看单个表和连接的表。图 9-10 显示了该菜单。菜单存储在包含文件夹中,清单 9-10b 给出代码。

<div style="padding-top: 10px; padding-bottom: 10px; padding-right: 15px;">
          <nav class="float-left navbar navbar-expand-md navbar-dark">
          <button class="navbar-toggler" type="button"
                  data-toggle="collapse" data-target="#collapsibleMenu1">
                  <span class="navbar-toggler-icon"></span>
          </button>
<div class="btn-group-vertical btn-group-sm collapse navbar-collapse"
          id="collapsibleMenu1"  role="group" aria-label="Button Group">
          <ul class="navbar-nav flex-column" style="width: 140px;">
                   <li class="nav-item">
                             <a class="btn btn-primary" id="buttons"
                             title="Page Two" href="#" role="button">
                             About Us</a>
                   </li>
                   <li class="nav-item">
                             <a class="btn btn-primary" id="buttons"
                             title="The Birds" href="birds.php"
                             role="button">The Birds</a>
                   </li>
                   <li class="nav-item">
                             <a class="btn btn-primary" id="buttons"
                             title="The Reserves" href="reserves.php"
                             role="button">The Reserves</a>
                   </li>
                   <li class="nav-item">
                             <a class="btn btn-primary" id="buttons"
                             title="Page Five" href="join-2.php"
                             role="button">Join 2 Tables</a>
                   </li>
                   <li class="nav-item">
                             <a class="btn btn-primary" id="buttons"
                             title="Page Six" href="join-3.php"
                             role="button">Join 3 Tables</a>
                   </li>
                   <li class="nav-item">
                             <a class="btn btn-primary" id="buttons"
                             title="Return to Home Page" href="index.php"
                             role="button">Home Page</a>
                   </li>
          </div>
    </nav>
</div>

Listing 9-10bThe Main Menu (nav.php)

所有页面的页眉

所有页面的标题都包含两个菜单按钮。你可以在图 9-10 中看到这个标题。

清单 9-10c 给出了标题的代码。

<meta name="viewport" content="width=device-width, initial-scale=1">
       <script src=
       "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
       </script>
       <script src=
"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js">
       </script>
       <script src=
"https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js">
       </script>
<div class="col-sm-8" style="color: white; padding-top: 5%;">
 <div class="h1 display-4 text-left" >The Devon Bird Reserves</div>
</div>
<div class="col-sm-4" style="padding-top: 10px; padding-bottom: 10px;">
     <nav class="float-right navbar navbar-expand-md navbar-dark">
               <button class="navbar-toggler" type="button"
               data-toggle="collapse" data-target="#collapsibleMenu">
                       <span class="navbar-toggler-icon"></span>
               </button>
<div class="btn-group-vertical btn-group-sm collapse navbar-collapse"
        id="collapsibleMenu" role="group" aria-label="Button Group">
        <ul class="navbar-nav flex-column" style="width: 140px;">
               <li class="nav-item">
        <a class="btn btn-primary" id="buttons" href="#"
                          role="button">Login</a>
        </li>
        <li class="nav-item">
                  <a class="btn btn-primary" id="buttons"
                           href="member_reg.php" role="button">Register</a>
        </li>
</ul>
</div>
</nav>
</div>

Listing 9-10cThe Code for the Header (includes/header.php)

现在让我们看一些鸟。

查看鸟类的页面

图 9-11 显示了查看鸟表的显示。

img/314857_2_En_9_Fig11_HTML.jpg

图 9-11

鸟的桌子

清单 9-11 给出了鸟表显示的代码。

<?php
        define('ERROR_LOG','errorlog.log');
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Birds Home Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content=
               "width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
                  href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                  integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                  crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="birds1.css">
</head>
<body>
<div class="container" style="margin-top:30px;border: 3px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
style="margin-bottom:2px; padding:20px;background-color:#CCFF99;">
        <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
      <ul class="nav nav-pills flex-column">
                  <?php include('includes/nav.php'); ?>
      </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8 row" style="padding-left: 30px;">
           <h2>The Birds that can be seen on our Reserves</h2>
           <?php
           try
           {
           // This script retrieves all the records from the birds table
           require ('mysqli_connect.php'); // Connect to the database
           // Make the query:
           $q = "SELECT bird_name, rarity, best_time FROM birds ORDER BY bird_name ASC";
           $result = mysqli_query ($dbcon, $q); // Run the query
           if ($result) { // If it ran OK, display the records
                     // Table header
                     ?>
                     <table class="table table-striped" style=
                              "background: white;color:black;">
                     <tr>
                              <th scope="col">Birds Name</th>
                              <th scope="col">Rarity</th>
                              <th scope="col">Best Time</th>
                     </tr>
<?php
           // Fetch and print all the records
           while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
                    $bird_name = htmlspecialchars($row['bird_name'], ENT_QUOTES);
                    $rarity = htmlspecialchars($row['rarity'], ENT_QUOTES);
                    $best_time = htmlspecialchars($row['best_time'], ENT_QUOTES);
                    echo '<tr>
                             <td scope="row">' . $bird_name . '</td>
                             <td scope="row">' . $rarity . '</td>
                             <td scope="row">' . $best_time . '</td>
                    </tr>';
           }
           echo '</table>'; // Close the table
           mysqli_free_result ($result); // Free up the resources
           } else { // If it did not run OK
                    // Message
                    echo '<p class="text-center">
                    The current birds could not be retrieved. ';
                    echo 'We apologize for any inconvenience.</p>';
                    // Debugging message
                    //echo '<p>' . mysqli_error($dbcon) . '<br><br />Query: ' .
                             $q . '</p>';
           } // End of if ($result)
           mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e) // We finally handle any problems here
{
           // print "An Exception occurred. Message: " . $e->getMessage();
           print "The system is busy please try later";
           //  $date = date('m.d.y h:i:s');
           //  $errormessage = $e->getMessage();
           //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
           //   error_log($eMessage,3,ERROR_LOG);
           // e-mail support person to alert there is a problem
           //  error_log("Date/Time: $date – Exception Error,
           // Check error log for details", 1, noone@helpme.com,
           // "Subject: Exception Error \nFrom: Error Log
           // <errorlog@helpme.com>" . "\r\n");
   }
   catch(Error $e)
   {
           // print "An Error occurred. Message: " . $e->getMessage();
           print "The system is busy please try later";
           // $date = date('m.d.y h:i:s');
           // $errormessage = $e->getMessage();
           // $eMessage = $date . " | Error | " , $errormessage . |\n";
           // error_log($eMessage,3,ERROR_LOG);
           // e-mail support person to alert there is a problem
           //  error_log("Date/Time: $date – Error, Check error log for
           //details", 1, noone@helpme.com, "Subject: Error \nFrom:
           // Error Log <errorlog@helpme.com>" . "\r\n");
   }
?>
</div><!-- End of the view birds page content. -->
<!-- Right-side Column Content Section -->
           <aside class="col-sm-2" style=
                    "padding-top: 20px; padding-right: 0px;">
                    <?php include('includes/info-col.php'); ?>
           </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
           style="padding-bottom:1px;
           padding-top:8px;background-color:#CCFF99;">
           <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 9-11Creating a Display for the Bird’s Table (birds.php)

点击主页上的鸟类菜单按钮可以查看此页面。

代码的解释

您已经在前面的章节中看到了所有这些代码。因此,我们在这里不提供任何额外的解释。

我们现在将检查 locations 表,这是本教程的第二个表。可以通过点击菜单按钮 reservations 来查看。

用于查看保护区位置和栖息地的页面

图 9-12 显示了查看保护区位置表的画面。

img/314857_2_En_9_Fig12_HTML.jpg

图 9-12

显示保护区的位置和类型

列表 9-12 给出了显示保护区位置和栖息地的代码。

<?php
    define('ERROR_LOG','errorlog.log');
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Reserves Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
            "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
            integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
            crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="birds1.css">
</head>
<body>
<div class="container" style="margin-top:30px;border: 3px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
    style="margin-bottom:2px; padding:20px;
            background-color:#CCFF99;">
    <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Left-side Column Menu Section -->
  <nav class="col-sm-2">
        <ul class="nav nav-pills flex-column">
                  <?php include('includes/nav.php'); ?>
        </ul>
  </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8 row" style="padding-left: 30px;">
<h2>The location and habitat of the Devon Bird Reserves</h2>
<?php
    try {
    // This script retrieves all the records from the users table
              require ('mysqli_connect.php'); // Connect to the database
              // Make the query:
              $q = "SELECT location, location_type FROM location ORDER BY ";
              $q .= "location_id ASC";
              $result = mysqli_query ($dbcon, $q); // Run the query
              if ($result) { // If it ran OK, display the records
              // Table header
    ?>
        <table class="table table-striped"
            style="background: white;color:black;">
            <tr>
                     <th scope="col">Location</th>
                     <th scope="col">Location Type</th>
            </tr>
    <?php
    // Fetch and print all the records
             while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
                      $location = htmlspecialchars($row['location'], ENT_QUOTES);
                      $location_type = htmlspecialchars($row['location_type'], ENT_QUOTES);
                      echo '<tr>
                      <td scope="row">' . $location . '</td>
                      <td scope="row">' . $location_type . '</td>
                      </tr>';
             }
             echo '</table>'; // Close the table
             mysqli_free_result ($result); // Free up the resources
    } else { // If it did not run OK
             // Message
             echo '<p class="text-center">
                      The current locations could not be retrieved. ';
             echo 'We apologize for any inconvenience.</p>';
             // Debugging message
             //echo '<p>' . mysqli_error($dbcon) . '<br><br />Query: ' .
                      // $q . '</p>';
    } // End of if ($result)
    mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e) // We finally handle any problems here
{
    // print "An Exception occurred. Message: " . $e->getMessage();
    print "The system is busy please try later";
    //  $date = date('m.d.y h:i:s');
    //  $errormessage = $e->getMessage();
    //  $eMessage = $date . " | Exception Error | " , $errormessage .
//|\n";
    // error_log($eMessage,3,ERROR_LOG);
    // e-mail support person to alert there is a problem
    // error_log("Date/Time: $date – Exception Error, Check error log for
    //details", 1, noone@helpme.com, "Subject: Exception Error
    //\nFrom: Error Log <errorlog@helpme.com>" . "\r\n");

}
catch(Error $e)
{
    // print "An Error occurred. Message: " . $e->getMessage();
    print "The system is busy please try later";
    // $date = date('m.d.y h:i:s');
    // $errormessage = $e->getMessage();
    // $eMessage = $date . " | Error | " , $errormessage . |\n";
    // error_log($eMessage,3,ERROR_LOG);
    // // e-mail support person to alert there is a problem
    //  error_log("Date/Time: $date – Error, Check error log for
    //details", 1, noone@helpme.com, "Subject: Error \nFrom:
    // Error Log <errorlog@helpme.com>" . "\r\n");
}
?>
</div><!-- End of the view birds page content. -->
<!-- Right-side Column Content Section -->
     <aside class="col-sm-2" style="padding-top: 20px;
              padding-right: 0px;">
     <?php include('includes/info-col.php'); ?>
     </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
    style="padding-bottom:1px; padding-top:8px;
            background-color:#CCFF99;">
            <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 9-12Creating the Page for Displaying the Locations and Habitats of the Reserves (reserves.php)

我们现在将显示两个连接表的显示。在本章的后面,我们将更深入地讨论如何连接表。

显示连接表中的数据

当这两个表连接在一起时,将显示每个表 birds 和 location 的一些数据,如图 9-13 所示。

img/314857_2_En_9_Fig13_HTML.jpg

图 9-13

显示两个连接的表

清单 9-13 显示了显示从两个连接的表中选择的数据的代码。

<?php
       define('ERROR_LOG','errorlog.log');
?>
 <!DOCTYPE html>
<html lang="en">
<head>
       <title>Two Tables Page</title>
       <meta charset="utf-8">
       <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
       <!-- Bootstrap CSS File -->
       <link rel="stylesheet"
       ref="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
              integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
              crossorigin="anonymous">
       <link rel="stylesheet" type="text/css" href="birds1.css">
</head>
<body>
       <div class="container" style="margin-top:30px;border: 3px black solid;">
       <!-- Header Section -->
       <header class="jumbotron text-center row" id="includeheader"
               style="margin-bottom:2px; padding:20px;background-color:#CCFF99;">
                        <?php include('includes/header.php'); ?>
       </header>
       <!-- Body Section -->
       <div class="content mx-auto" id="contents">
       <div class="row mx-auto" style="padding-left: 0px; height: auto;">
       <!-- Left-side Column Menu Section -->
       <nav class="col-sm-2">
               <ul class="nav nav-pills flex-column">
                       <?php include('includes/nav.php'); ?>
               </ul>
       </nav>
       <!-- Center Column Content Section -->
       <div class="col-sm-8 row" style="padding-left: 30px;">
               <h2>The location and habitat of the Devon Bird Reserves</h2>
       <?php
       try {
       // This script retrieves all the records from the birds table
               require ('mysqli_connect.php'); // Connect to the database
               // Make the query:
               $q = "SELECT location.location, birds.bird_name, birds.rarity, birds.best_time
                       FROM location INNER JOIN birds ON location.bird_id=birds.bird_id";
               $result = mysqli_query ($dbcon, $q); // Run the query
               if ($result) { // If it ran OK, display the records
                       // Table header
                       ?>
                       <table class="table table-striped" style="background: white;color:black;">
                       <tr>
                                 <th scope="col">Location</th>
                                 <th scope="col">Bird Name</th>
                                 <th scope="col">Rarity</th>
                                 <th scope="col">Best Time</th>
                       </tr>
               <?php
               // Fetch and print all the records
               while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
                       $location = htmlspecialchars($row['location'], ENT_QUOTES);
                       $bird_name = htmlspecialchars($row['bird_name'], ENT_QUOTES);
                       $rarity = htmlspecialchars($row['rarity'], ENT_QUOTES);
                       $best_time = htmlspecialchars($row['best_time'], ENT_QUOTES);
                       echo '<tr>
                              <td scope="row">' . $location . '</td>
                              <td scope="row">' . $bird_name . '</td>
                              <td scope="row">' . $rarity . '</td>
                              <td scope="row">' . $best_time . '</td>
                       </tr>';
               }
               echo '</table>'; // Close the table
               mysqli_free_result ($result); // Free up the resources
       } else { // If it did not run OK
                // Message
                echo '<p class="text-center">The current birds or locations could not be retrieved. ';
                echo 'We apologize for any inconvenience.</p>';
                // Debugging message
       //echo '<p>' . mysqli_error($dbcon) . '<br><br />Query: ' . $q . '</p>';
       } // End of if ($result)
       mysqli_close($dbcon); // Close the database connection
}
catch(Exception $e) // We finally handle any problems here
{
       // print "An Exception occurred. Message: " . $e->getMessage();
       print "The system is busy please try later";
       //  $date = date('m.d.y h:i:s');
       //  $errormessage = $e->getMessage();
       //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
       //   error_log($eMessage,3,ERROR_LOG);
       // e-mail support person to alert there is a problem
       //  error_log("Date/Time: $date – Exception Error, Check error log for
       //details", 1, noone@helpme.com, "Subject: Exception Error \nFrom: Error Log
       // <errorlog@helpme.com>" . "\r\n");
}
catch(Error $e)
{
       // print "An Error occurred. Message: " . $e->getMessage();
       print "The system is busy please try later";
       // $date = date('m.d.y h:i:s');
       // $errormessage = $e->getMessage();
       // $eMessage = $date . " | Error | " , $errormessage . |\n";
       // error_log($eMessage,3,ERROR_LOG);
       // e-mail support person to alert there is a problem
       //  error_log("Date/Time: $date – Error, Check error log for
       //details", 1, noone@helpme.com, "Subject: Error \nFrom:
       // Error Log <errorlog@helpme.com>" . "\r\n");
   }
?>
</div><!-- End of the view birds page content. -->
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2" style="padding-top: 20px; padding-right: 0px;">
                <?php include('includes/info-col.php'); ?>
        </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
        style="padding-bottom:1px; padding-top:8px;background-color:#CCFF99;">
                <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 9-13Creating the Page for Displaying Selected Data from Two Joined Tables (join-2.php)

请注意,ON 单词后面的项目顺序可以颠倒,您将获得相同的结果。例如,以下内容:

   ON location.bird_id=birds.bird_id)" ;

可以写成如下:

   ON birds.bird_id=location.bird_id";

您可以通过单击“连接两个表”菜单按钮来查看这两个连接的表。您将能够看到该表包含来自两个表的数据,如图 9-13 所示。

SQL ON 关键字通过将相关的 bird_id 字段链接在一起来连接这两个表。让我们花点时间来讨论一下如何以及为什么将表连接在一起。

我们现在将创建一个显示三个虚拟连接表的环境。这些页面可通过主页中的菜单按钮访问。

注意

如前所述,连接表是虚拟表。只有在浏览器中显示后才能看到它们。本教程中的页面专门用于演示连接表的结果。通过单击菜单上的按钮来选择表格显示。真实世界的网站不会有标记为“连接 2 个表”和“连接 3 个表”的按钮。这些标签只是为了您的方便。

创建一个页面来显示三个连接的表

之前我们使用 myPhpAdmin 创建了数据库中三个表的连接关系,如图 9-14 所示。

img/314857_2_En_9_Fig14_HTML.jpg

图 9-14

创建并保存第二个和第三个表之间的关系

我们现在将创建一个页面来选择和显示这三个表中的数据。其原理非常符合逻辑:首先连接两个表以产生一个虚拟表,然后将它连接到第三个表。用于连接和选择三个表中的数据的查询的语法如下:

$q= "SELECT some column, some other column, another column
       FROM table1
               INNER JOIN table2 USING (the key that links table1 and table2)
               INNER JOIN table3 USING (the key that links table2 and table3) ";

让我们从三个连接的表中选择一些数据显示在 web 页面上。

$q = "SELECT bird_name, best_time, location, bird_hides, entrance_member,
        entr_non_member FROM birds
                INNER JOIN location USING (bird_id)
                INNER JOIN reserves_info USING (location_id) ";

执行该 SQL 语句的结果如图 9-15 所示。

img/314857_2_En_9_Fig15_HTML.jpg

图 9-15

浏览器中连接和显示的三个表

显示此表的页面可以通过单击索引页面中的 Join 3 Tables 菜单按钮来查看。

清单 9-15 显示了这个页面的代码。

<?php
        define('ERROR_LOG','errorlog.log');
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Three Tables Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
        href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="birds1.css">
</head>
<body>
<div class="container" style="margin-top:30px;border: 3px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
        style="margin-bottom:2px; padding:20px;background-color:#CCFF99;">
        <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Left-side Column Menu Section -->
        <nav class="col-sm-2">
               <ul class="nav nav-pills flex-column">
                       <?php include('includes/nav.php'); ?>
               </ul>
        </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8 row" style="padding-left: 30px;">
        <h2>The location and habitat of the Devon Bird Reserves</h2>
<?php
        try {
               require ('mysqli_connect.php'); // Connect to the database
               // Make the query:
               $q = "SELECT bird_name, best_time, location, bird_hides,
                       entrance_member, entr_non_member FROM birds
                       INNER JOIN location USING (bird_id)
                       INNER JOIN reserves_info USING (location_id)";
               $result = mysqli_query ($dbcon, $q); // Run the query
               if ($result) { // If it ran OK, display the records
               // Table header
                       ?>
                       <table class="table table-responsive table-striped"
                               style="background: white;color:black;">
                               <tr>
                                       <th scope="col">Birds Name</th>
                                       <th scope="col">Best Time</th>
                                       <th scope="col">Location</th>
                                       <th scope="col">Bird Hides</th>
                                       <th scope="col">Entrance Member</th>
                                       <th scope="col">Entrance Non-Member</th>
                               </tr>
                       <?php
                       // Fetch and print all the records
                       while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
                              $bird_name = htmlspecialchars($row['bird_name'], ENT_QUOTES);
                              $best_time = htmlspecialchars($row['best_time'], ENT_QUOTES);
                              $location = htmlspecialchars($row['location'], ENT_QUOTES);
                              $bird_hides = htmlspecialchars($row['bird_hides'], ENT_QUOTES);
                              $entrance_member = htmlspecialchars($row['entrance_member'], ENT_QUOTES);
                              $entrance_non_member = htmlspecialchars($row['entr_non_member'], ENT_QUOTES);
                              echo '<tr>
                              <td scope="row">' . $bird_name . '</td>
                              <td scope="row">' . $best_time . '</td>
                              <td scope="row">' . $location . '</td>
                              <td scope="row">' . $bird_hides . '</td>
                              <td scope="row">' . $entrance_member . '</td>
                              <td scope="row">' . $entrance_non_member . '</td>
                       </tr>';
                       }
               echo '</table>'; // Close the table
               mysqli_free_result ($result); // Free up the resources
       } else { // If it did not run OK
               // Message
               echo '<p class="error">The current data could not be retrieved. ';
               echo 'We apologize for any inconvenience.</p>';
               // Debugging message
               echo '<p>' . mysqli_error($dbcon) . '<br><br />Query: ' . $q . '</p>';
       } // End of if ($result)
       mysqli_close($dbcon); // Close the database connection
}
catch(Error $e)
{
       // print "An Error occurred. Message: " . $e->getMessage();
       print "The system is busy please try later";
       // $date = date('m.d.y h:i:s');
       // $errormessage = $e->getMessage();
       // $eMessage = $date . " | Error | " , $errormessage . |\n";
       // error_log($eMessage,3,ERROR_LOG);
       // e-mail support person to alert there is a problem
       //  error_log("Date/Time: $date – Error, Check error log for
       //details", 1, noone@helpme.com, "Subject: Error \nFrom: Error Log <errorlog@helpme.com>" .
       //"\r\n");
  }
?>
</div><!-- End of the view birds page content. -->
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2" style="padding-top: 20px; padding-right: 0px;">
                <?php include('includes/info-col.php'); ?>
        </aside>
  </div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
style="padding-bottom:1px; padding-top:8px;background-color:#CCFF99;">
  <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 9-15Creating the Page for Displaying Three Joined Tables (join-3.php).

代码的解释

本节解释代码。

$q = "SELECT bird_name, best_time, location, bird_hides,
        entrance_member,
        entr_non_member FROM birds
                INNER JOIN location USING (bird_id)
                INNER JOIN reserve_info USING (location_id)";

该代码类似于两个表的代码,只是多了一个 INNER JOIN 语句。

我们从三个连接的表中选择了一些数据(但不是全部)。使用 SELECT 查询从三个表中选择了六个项目。

我们现在将学习如何添加使用支票而不是 PayPal 或借记卡/信用卡支付商品或会员费的替代方法。

支票支付

仍然有顾客喜欢用支票支付,因为他们没有 PayPal 账户,或者他们不喜欢在网上透露他们的借记卡/信用卡信息。支票付款通常需要附有打印的表格,其中包含解释付款原因的信息。该表单通常包含客户的姓名、地址、电子邮件地址、电话号码和其他相关信息,如付款号码。

本教程为需要在线注册的组织使用了简化的可打印表单;该表单需要用户的完整信息,以及支付方式的选择。这对于需要会员注册的网站来说非常理想。

让我们假设德文郡鸟类保护区需要一个在线登记表,如图 9-16 所示。

img/314857_2_En_9_Fig16_HTML.jpg

图 9-16

注册页面

您可以通过单击主页标题上的注册按钮来查看注册页面。

注意

SQL 文件成员已包含在章节文件中。该文件包含为注册页面创建所需表格的代码。在尝试运行以下任何代码之前,必须使用 phpMyAdmin 导入该代码。

注册页面和相关表格与第七章中使用的基本相同。大部分代码来自第七章,嵌入到 birds 模板中,保存为 member_reg.php 。因为代码是相似的,所以这里不包括它,但是包含在章节文件中。

当用户注册成功后,注册按钮会将他们重定向到一个“谢谢”页面,该页面包含支付会员费的替代方法。

支付方式的选择

填写完会员注册表格后,点击注册按钮会将用户带到一个页面,让用户选择三种支付方式,如图 9-17 所示。

img/314857_2_En_9_Fig17_HTML.jpg

图 9-17

为用户提供支付方式的选择

PayPal 徽标的显示特意在页面上复制,以表明您可以选择垂直或水平徽标。为了避免页面混乱,您应该只选择一个。清单 9-17 给出了创建如图 9-17 所示页面的代码。

<?php
        define("ERROR_LOG","errorlog.log");
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Register Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
        href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
        integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
        <script src="verify.js"></script>
        <script src='https://www.google.com/recaptcha/api.js'></script>
        <link rel="stylesheet" type="text/css" href="birds1.css">
</head>
<body>
        <div class="container" style="margin-top:30px;border: 3px black solid;">
        <!-- Header Section -->
        <header class="jumbotron text-center row" id="includeheader"
                style="margin-bottom:2px; padding:20px;background-color:#CCFF99;">
                <?php include('includes/header.php'); ?>
        </header>
        <!-- Body Section -->
        <div class="content mx-auto" id="contents">
        <div class="row mx-auto" style="padding-left: 0px; height: auto;">
        <!-- Left-side Column Menu Section -->
                 <nav class="col-sm-2">
                        <ul class="nav nav-pills flex-column">
                                <?php include('includes/nav.php'); ?>
                        </ul>
                 </nav>
        <!-- Center Column Content Section -->
        <div class="col-sm-8">
        <h3 class="h2 text-center" >Thank you for registering</h2>
        <h6 class="text-center">
        To confirm your registration please verify membership class and
        pay the membership fee now.</h6>
        <h6 class="text-center">You can use PayPal, a check or a credit/debit card.</h6>
        <p class="text-center" >When you have completed your registration you will be
        able to login to the member's only pages.</p>
<?php
try {
        require ("mysqli_connect.php");
        $query = "SELECT * FROM prices";
        $result = mysqli_query ($dbcon, $query); // Run the query.
        if ($result) { // If it ran OK, display the records.
                $row = mysqli_fetch_array($result, MYSQLI_NUM);
                $yearsarray = array(
        "Standard one year:", "Standard five year:", "Military one year:", "Under 21 one year:",
        "Other - Give what you can. Maybe:" );
               echo '<h6 class="text-center text-danger">Membership classes:</h6>' ;
               echo '<h6 class="text-center text-danger small"> ';
               for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {
                       echo $yearsarray[$j] . " &pound; " .
                       htmlspecialchars($row[$i], ENT_QUOTES)  .
                       " GB, &dollar; " .
                       htmlspecialchars($row[$i + 1], ENT_QUOTES) .
                       " US";
               if ($j != 4) {
                       if ($j % 2 == 0) { echo "</h6><h6 class='text-center text-danger small'>"; }
                       else { echo " , "; }
               }
        }
        echo "</h6>";
}
?>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
        <input type="hidden" name="cmd" value="_s-xclick">
        <input type="hidden" name="hosted_button_id" value="XXXXXXXXXXXXX">
        <div class="form-group row">
        <label for="level" class="col-sm-4 col-form-label text-right">*Membership Class</label>
        <div class="col-sm-8">
                <select id="level" name="level" class="form-control" required>
                <option value="0" >-Select-</option>
        <?php
                $class = htmlspecialchars($_GET['class'], ENT_QUOTES);
                for ($j = 0, $i = 0; $j < 5; $j++, $i = $i + 2) {
                        echo '<option value="' .
                        htmlspecialchars($row[$i], ENT_QUOTES) . '" ';
                        if ((isset($class)) && ( $class == $row[$i]))
                        {
                               echo ' selected ';
                        }
                echo ">" . $yearsarray[$j] . " " .
                htmlspecialchars($row[$i], ENT_QUOTES) .
                " &pound; GB, " .
                htmlspecialchars($row[$i + 1], ENT_QUOTES) .
                "&dollar; US</option>";
                }
        ?>
        </select>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 col-form-label">
        <nav class="float-right navbar navbar-expand-md navbar-dark">
                <button class="navbar-toggler" type="button" data-toggle="collapse"
                        data-target="#collapsibleMenu">
                        <span class="navbar-toggler-icon"></span>
                </button>
<div class="btn-group-vertical btn-group-sm collapse navbar-collapse" id="collapsibleMenu"
        role="group" aria-label="Button Group">
                <ul class="navbar-nav flex-column" style="width: 140px;">
                       <li class="nav-item">
                               <a class="btn btn-primary" id="buttons"
                               href="pay-with-check.php" role="button">Pay By Check</a>
                       </li>
                </ul>
</div>
</nav>
</div>
<div class="col-sm-6">
<!-- Replace the code below with code provided by PayPal once you obtain a Merchant ID -->
        <input type="hidden" name="currency_code" value="GBP">
                <input style="margin:10px 0 0 40px" type="image"
                src="https://www.paypalobjects.com/en_US/GB/i/btn/btn_buynowCC_LG.gif"
                name="submit" alt="PayPal  The safer, easier way to pay online.">
        <img alt="" src=
                "https://www.paypalobjects.com/en_GB/i/scr/pixel.gif" width="1" height="1">
<!-- Replace code above with PayPal provided code -->
</div>
</div>
</form>
</div>
<!-- Right-side Column Content Section -->
        <aside class="col-sm-2">
               <?php include('includes/info-col-cards.php'); ?>
        </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
        style="padding-bottom:1px; padding-top:8px;background-color:#CCFF99;">
        <?php include('includes/footer.php'); ?>
</footer>
<?php
} // end try
catch(Exception $e) // We finally handle any problems here
{
        // print "An Exception occurred. Message: " . $e->getMessage();
        print "The system is busy please try later";
        //  $date = date('m.d.y h:i:s');
        //  $errormessage = $e->getMessage();
        //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
        //   error_log($eMessage,3,ERROR_LOG);
        // e-mail support person to alert there is a problem
        //  error_log("Date/Time: $date – Exception Error, Check error log for
        //details", 1, noone@helpme.com, "Subject: Exception Error \nFrom: Error Log
                <errorlog@helpme.com>" . "\r\n");
}
catch(Error $e)
{
        // print "An Error occurred. Message: " . $e->getMessage();
        print "The system is busy please try later";
        // $date = date('m.d.y h:i:s');
        // $errormessage = $e->getMessage();
        // $eMessage = $date . " | Error | " , $errormessage . |\n";
        // error_log($eMessage,3,ERROR_LOG);
        // e-mail support person to alert there is a problem
        //  error_log("Date/Time: $date – Error, Check error log for
        //details", 1, noone@helpme.com, "Subject: Error \nFrom: Error Log
        // <errorlog@helpme.com>" . "\r\n");
   }
?>
</body>
</html>

Listing 9-17Creating a Page for Alternative Methods of Payment (register-thanks.php)

当点击 PayPal Pay Now 按钮时,用户会被带到通常的 PayPal 页面进行支付。或者,用户现在可以单击“用支票支付”按钮,填写并打印一个表格,与他们的支票一起发送。这段代码类似于第七章中的 register_thanks 程序。

支票付款

当用户点击 Pay By Check 按钮时,他们将被带到一个包含可打印表格的页面,如图 9-18 所示。

img/314857_2_En_9_Fig18_HTML.jpg

图 9-18

可打印的表格可以在屏幕上填写,除了签名和日期(pay-with-check.php)

清单 9-18 给出了创建可打印表单的代码。

该表格设计为在屏幕上填写,然后打印。

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Register Page</title>
  <meta charset="utf-8">
  <meta name="viewport" content=
            "width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
<script src="verify.js"></script>
<script src='https://www.google.com/recaptcha/api.js'></script>

<link rel="stylesheet" type="text/css" href="birds1.css" media="screen">
<link rel="stylesheet" type="text/css" href="print.css" media="print">

<style type="text/css">
    label { margin-bottom:5px; }
    label { width:570px; float:left; text-align:right; }
    .sign { font-weight:bold;}
</style>
</head>
<body>
<div class="container" style="margin-top:30px;border: 3px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
    style="margin-bottom:2px; padding:20px;background-color:#CCFF99;">
            <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Left-side Column Menu Section -->
     <nav class="col-sm-2">
     <ul class="nav nav-pills flex-column">
                      <?php include('includes/nav.php'); ?>
     </ul>
     </nav>
<!-- Center Column Content Section -->
<div class="col-sm-8">
     <h4>Complete your Registration by Paying with a Check</h4>
     <p>Thank you for registering online, now please fill out this
     form. Asterisks indicate essential fields. When you
     have filled out the form please print two copies by clicking
     the "Print This Form" button. Sign one copy and keep one for
     reference sign a check payable to &quot;The Devon Bird
     Reserves&quot;. </p><p>Mail the signed form and check to:
     <br>The Treasurer, The Devon Bird Reserves, 99 The Street,
     The Village, EX99 99ZZ </p>
<form>
<div id="fields">
     <label class="label" for="title">Title<span
              class="large-red">*</span>
     <input id="title" name="title" size="35" type="text" required>
     </label>
     <br><br><label class="label" for="firstname">First Name
<span>*</span>
     <input id="firstname" name="firstname" size="35" type="text"
              required></label><br>
<br><label class="label" for="lastname">Last Name
<span>*</span>
<input id="lastname" name="lastname" size="35" type="text"
       required></label><br>
<br><label class="label" for="useremail">Your Email Address
<span>*</span>
<input id="useremail" name="useremail" type="email" size="35"
       required></label><br>
</div>
</form>
<br><br>
<p class="sign">
 Signed___________________________________________&nbsp;Date_________________
</p>
<br>
<div id="button">
     <input type="button" value=
     "Click to automatically print the form in black and white"
               onclick="window.print()" title="Print this Form"><br>
</div>
<!--End of content.-->
</div>
<!-- Right-side Column Content Section -->
     <aside class="col-sm-2">
     <?php include('includes/info-col-cards.php'); ?>
     </aside>
</div>
<!-- Footer Content Section -->
<footer class="jumbotron text-center row"
    style="padding-bottom:1px; padding-top:8px;
            background-color:#CCFF99;">
            <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</body>
</html>

Listing 9-18Creating the Printable Form

(pay-with-check.php)

代码的解释

本节解释代码。

<link rel="stylesheet" type="text/css" href="birds.css" media="screen">
<link rel="stylesheet" type="text/css" href="print.css" media="print">

使用 PHP 打印表单是不可能的,因为 PHP 是一个服务器端脚本。但是,表单是由浏览器显示的,我们可以从浏览器显示的屏幕打印。我们通过使用媒体属性 media="print "链接到一个单独的条件样式表来实现这一点。

第一行链接到主样式表 birds.css ,它使用媒体属性 media="screen "在屏幕上显示页面。第二行将页面链接到页面的打印版本。当显示内容发送到打印机或打印预览时,会自动调用此功能。打印的表单不需要页眉、菜单或页脚。此外,使用 CSS 样式表,我们可以使它只使用黑色墨水打印,这样用户就不必使用更昂贵的彩色墨盒。

<div id="button">
         <input type="button" value=
                  "Click to automatically print the form in black and white"
                  onclick="window.print()" title="Print this Form"><br>
</div>

这是页面上用于将浏览器显示设置为打印机的按钮的代码。

打印在线表格

图 9-19 显示打印页面。

img/314857_2_En_9_Fig19_HTML.jpg

图 9-19

用黑色墨水打印的页面的上半部分包含了所有重要的信息

为了生成图 9-19 所示的打印输出,在用户屏幕上填写表格,然后点击打印按钮。结果使用了最少的墨水并且没有彩色墨水;然而,它包含了成为德文郡鸟类保护区成员的所有必要条件。用户在表单上签名,然后将它和支票一起寄出。当用户填写注册页面时,他们的地址被输入数据库;因此,这不需要在印刷表格中重复。

样式表 print.css 是生成可打印表单的关键。清单 9-19 显示了这个样式表的代码。

/*PRINT.CSS: style amendments for printing only*/
/*SELECT ITEMS THAT YOU DO NOT WANT TO PRINT, e.g.,
header, menu, print-this-page button, and footer*/
#header, #nav, #leftcol, #button, #rightcol, #footer,
#info-col, ul { display:none; }
input { border:1px black solid; }
h2 { font-size:16pt; color:black; text-align:center; }
h3 { text-align:center; font-size:11pt;}
/*REVEAL OUTGOING URL links on printed page*/
a[href^="http://":after {content: "(" attr(href)")"; }

Listing 9-19Creating the Style Sheet for Printing Forms

(print.css)

CSS 语句{ display:none;}告诉打印机哪些项目不应该打印。为了避免在测试打印页面的外观时浪费纸张和墨水,请使用浏览器的打印预览功能。将页面加载到浏览器中,选择文件,然后选择打印预览,查看可打印页面的外观。如果可打印页面包含分页符,请单击打印预览屏幕的向右箭头查看后续页面。

按 Esc 键退出打印预览模式。

h2 { font-size:16pt; text-align:center; }
h3 { text-align:center; font-size:11pt;}

要为打印机选择正确的字体大小,请使用磅值(如 16 磅。和 12 磅。)并使用试错法来优化尺寸。你可能有文字在里面

tags, so be sure to include a style for the paragraph font size.

/*REVEAL OUTGOING URL links on the printed page*/
a[href^="http://]:after {content: "(" attr(href)")"; }

如果页面包含您的网站的 URL,您可能希望它以对用户有用的格式出现在打印的页面上(假设他们打印并保留了表单的副本)。注意三种类型的括号的使用。在表单中,HTML 看起来像这样:

<p>Click for
<a title="Click to visit the Devon Bird Reserves web site"
href="http://www.the devonbirdreserves.co.uk">The Devon Bird Reserves</a></p>

该 URL 将在浏览器中显示如下:

点击访问德文郡鸟类保护区网站。

使用前面的代码,打印的表单将如下所示:

点击访问德文郡鸟类保护区网站( www.devonbirdreserves.co.uk )。

摘要

在本章中,我们介绍了使用多个表的理论和实践。您了解了这种表是虚拟表,只存在于服务器的易失性内存中,可以在 MySQL 或 MariaDB 等 DBMS 中查看。描述了各种连接方法之间的差异。然后,我们演示了通过使用 SQL 查询和 PHP 可以使虚拟表在 web 页面中可见。一个教程向您展示了如何通过支票实现会员付费。除此之外,还演示了经济的表格打印,以便向该组织发送申请表和支票。在下一章,我们将向你介绍一个在线留言板。

十、创建一个留言板

留言板可以是一个独立的功能,也可以是论坛中的一个重要组件。一个基本论坛至少有四个表,分别用于消息、会员注册、主题和回复。然而,为了节省空间并遵从本书的副标题简化的方法,本章描述了一个简单的留言板,其中有一个消息表和一个成员表。我们希望对这个留言板的原则的理解能启发你扩展它的功能并探索更复杂的解决方案。为了让你感兴趣,在本章的最后,我们添加了一个图表,展示了如何增强留言板来创建一个论坛。

留言板的功能比论坛少;一般来说,它们缺乏以允许它们作为线索被收集的方式接受回复的能力。在论坛中,回复与原始发帖 ID 相关联,并按日期升序显示。我们的留言板旨在收集明智和滑稽的报价,然后将它们插入一个可搜索的数据库。

完成本章后,您将能够

  • 创建一个带有数据库和表格的留言板

  • 创建注册页面

  • 创建登录和注销页面

  • 创建留言板类别的网关

  • 创建页面以显示报价

  • 创建搜索工具

警告

为了防止不愉快内容的显示,留言板需要持续的监控(调节)。在你的潜在客户承诺与你签订设计合同之前,应该提醒他们这一点。2018 年,美国实施了将公共留言板和其他公共论坛的内容责任置于论坛提供商的法律。这与以前的强制措施相反,以前的强制措施解除了论坛提供商对公共内容的责任。

计划

为了简化本章中的留言板,用户只有在注册并登录后才能查看消息。然而,一些信息会显示在主页上,以吸引用户注册。在我们的例子中,消息是报价,目的是建立一个有用的报价数据库。主页上的登录按钮会将注册用户重定向到一个页面,在那里他们可以选择查看哪种类型的报价,滑稽报价或明智报价。

当访问留言板时,注册会员将能够提供报价。因为该成员提供了报价,所以我们在教程中将线程称为报价。为了进一步简化,表中的列数被减少到实际的最小值。

用于登录的用户名将是用户选择的唯一的假名,因为留言板和论坛应该保护用户的个人信息。当会员发布新报价时,他们的假名是留言板上显示的唯一名称。但是,在注册时,他们可能还会被要求提供其他信息,例如他们的电子邮件地址,以便站点管理员在必要时可以与他们联系。他们的电子邮件地址从未在留言板上公开过。

我们现在将为留言板创建数据库和表格。

创建数据库

从本书的页面下载本章的文件。并将它们放在一个名为 msgboard 的新文件夹中,该文件夹位于 htdocseds-www 文件夹中。

启动 XAMPP 或 EasyPHP,在 phpMyAdmin 中创建名为 msgboarddb 的数据库。将编码设置为 utf8_general_ci。向下滚动并单击添加新用户。然后选择 Databases 选项卡,选择 msgboarddb 旁边的框,并单击 privileges。添加具有以下详细信息的新用户:

  • 用户名:布鲁内尔

  • 主机:本地主机

  • 密码:介于 1lblac 3r 之间

向下滚动到全局权限并选中全部选中框。单击保存(或在某些版本中单击继续)。

数据库连接文件 mysqli_connect.php 包含在可下载文件中。一定要把它添加到你的 htdocseds-www 文件夹中。如果要手动创建文件,请使用以下代码:

<?php
// Create a connection to the msgboarddb database and to MySQL
// Set the encoding to utf-8
// Set the database access details as constants
define ('DB_USER', 'brunel');
define ('DB_PASSWORD', 'tra1lblaz3r');
define ('DB_HOST', 'localhost');
define ('DB_NAME', 'msgboarddb');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');

创建表

使用可下载的导入表格。sql 文件或手动创建它们。

如果您想从头开始创建表,在 phpMyAdmin 的左侧面板中,单击单词 msgboarddb 。然后创建第一个包含六列的表,并将表成员命名为。

使用表 10-1 中的属性,并为将接受排序规则的列选择排序规则 utf8 _ general _ ci(reg _ date 和 member_level 没有排序规则)。创建列时,将出现 member_id 和 user_name 索引的弹出对话框。对于 member_id 弹出窗口,这两个字段都将是主字段。在用户名索引弹出窗口中,在第一个字段中输入用户名,在第二个字段中输入唯一用户名

表 10-1

成员表

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|   |

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 成员 id | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 |   | ·······················。 |
| 用户名 | 可变长字符串 | Twelve | 没有人 |   | -是吗 | 独一无二的 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Sixty | 没有人 |   | -是吗 |   |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   |   | -是吗 |
| reg_date | DATETIME |   | 没有人 |   | -是吗 |   |   | -是吗 |
| 成员级别 | 蒂尼因特 | Two | 没有人 |   | -是吗 |   |   | -是吗 |

member_id 列是主键,user_name 列有一个唯一的索引以防止重复条目。向下滚动并单击保存。

创建第二个表

您可以从可下载的 SQL 文件中导入论坛表,或者手动创建包含五列的论坛表。它包含一个名为 post_id 的列,该列被配置为主键。我们的项目将使用户能够从引文中搜索短语或单词;因此,提供了全文搜索。给消息列一个全文索引。

表 10-2 显示了表的细节。

表 10-2

论坛表的属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|   |

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| post_id | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 |   | ·······················。 |
| 用户名 | 可变长字符串 | Twelve | 没有人 |   | -是吗 |   |   | -是吗 |
| 科目 | 可变长字符串 | Sixty | 没有人 |   | -是吗 |   |   | -是吗 |
| 消息 | 文本 |   | 没有人 |   | -是吗 | 全面测试 |   | -是吗 |
| 发布日期 | DATETIME |   | 没有人 |   | -是吗 |   |   | -是吗 |

当您在论坛表中创建 post_id 列时,您将看到一个弹出对话框。对于 post_id 列,两个字段都应该包含单词 PRIMARY 。单击开始。当你在论坛表中创建消息栏时,你会看到如图 10-1 所示的弹出对话框。

img/314857_2_En_10_Fig1_HTML.jpg

图 10-1

添加索引对话框

接下来,我们将检查网站的一些页面。

为留言板创建主页

主页上显示了一些例子来鼓励人们注册。注册并登录后,他们将能够查看更多报价,还可以向收藏中添加报价。如果您喜欢从头开始创建表格,则在填充消息表格之前,主页上不会显示报价(消息)。

图 10-2 显示了填充表格后的主页。

img/314857_2_En_10_Fig2_HTML.jpg

图 10-2

表格被填充,因此主页显示四个引号

清单 10-2a 显示了生成主页的代码。

<?php // Start the session.
session_start() ;
     if ( isset( $_SESSION[ 'member_id' ] ) )
                 { $menu = 1;}
     else { $menu = 5; }
?>
<!DOCTYPE html>
<html lang="en">
<head>
     <title>Message Board Home Page</title>
     <meta charset="utf-8">
     <meta name="viewport" content=
                 "width=device-width, initial-scale=1, shrink-to-fit=no">
     <!-- Bootstrap CSS File -->
     <link rel="stylesheet"
                 href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                 integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                 crossorigin="anonymous">
     <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
     <div class="container"
              style="margin-top:30px;border: 2px black solid;">
     <!-- Header Section -->
     <header class="jumbotron text-center row" id="includeheader"
         style="margin-bottom:2px;
         background:linear-gradient(#0073e6, white); padding:10px;">
             <?php include('includes/header.php'); ?>
     </header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12">
     <h4 class="text-center">
         This home page shows a selection from our large collection
             of quotations.</h4>
         <h4 class="text-center">To view the whole collection,
             please register. </h4>
         <h5 class="text-center">You will then be able to contribute to
             this message board by adding quotations.</h5>
     <?php
     // Connect to the database
     require ( 'mysqli_connect.php' ) ;
     // Make the query
     $query =
     "SELECT user_name,post_date, subject, message FROM forum LIMIT 4" ;
     $result = mysqli_query( $dbcon, $query ) ;
     if ( mysqli_num_rows( $result ) > 0 )
     {
             ?>
             <table class="table table-responsive table-striped col-sm-12"
             style="background: white;color:black; padding-left: 80px;">
                    <tr>
                                 <th scope="col">Posted By</th>
                                 <th scope="col">Subject</th>
                                 <th scope="col">Message</th>
                    </tr>
             <?php
             while ( $row = mysqli_fetch_array( $result, MYSQLI_ASSOC ))
             {
                    $user_name =
                    htmlspecialchars($row['user_name'], ENT_QUOTES);
                    $post_date =
                    htmlspecialchars($row['post_date'], ENT_QUOTES);
                    $message =
                    htmlspecialchars($row['message'], ENT_QUOTES);
                    echo '<tr>
                                 <td scope="row">' . $user_name . '</td>
                                 <td scope="row">' . $post_date . '</td>
                                 <td scope="row">' . $message . '</td>
                    </tr>';
             }
             echo '</table>' ;
     }
     else { echo '<p class="text-center">
               There are currently no messages.</p>' ; }
     mysqli_close( $dbcon ) ;
?>
</div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
     style="padding-bottom:1px; margin: 0px; padding-top:8px;
             background-color:white;">
     <div class="col-sm-12 text-center">
             <?php include('includes/footer.php'); ?>
  </div>
</footer>
</div>
</div>
</body>
</html>

Listing 10-2aCreating the Home Page for the Forum’s Website (index.php)

标题使用了一个 case 语句,其中包含了本章所要求的每个水平菜单按钮,但是不同的按钮将被禁用以适应每个页面。

清单 10-2b 给出了索引页面的主页标题代码段的代码。

<meta name="viewport" content="width=device-width, initial-scale=1">
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js">
    </script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js">
    </script>
<div class="col-sm-8">
    <h1 class="mb-4 font-bold float-left" style="color:white">
             <?php
             if (!empty($_GET['name'])) {
                        $name =
                        filter_var( $_GET['name'], FILTER_SANITIZE_STRING);
                        echo $name;
             }
             else { echo "Quick Quotes"; }
             ?>
    </h1>
</div>
    <nav class="float-left navbar navbar-expand-xl navbar-trans navbar-light">
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleMenu">
    <span class="navbar-toggler-icon"></span>
  </button>
      <div class="btn-group btn-group-md collapse navbar-collapse navbar" id="collapsibleMenu"  role="group" aria-label="Button Group">   <?php
              switch ($menu) {
                      case 1: //index.php
                      ?>
                               <button type="button" style="width: 110px;"
                               class="btn btn-secondary bg-primary disabled"
                               onclick="location.href = "">Home Page
                               </button>

                               <button type="button" style="width: 110px;"
                               class="btn btn-secondary bg-primary disabled"
                               onclick="location.href = "" >Login
                               </button>

                               <button type="button" style="width: 110px;"
                               class="btn btn-secondary bg-primary"
                               onclick="location.href =
                               'logout.php?name=Logout'">Logout
                               </button>

                               <button type="button" style="width: 110px;"
                               class="btn btn-secondary bg-primary disabled"
                               onclick="location.href = "" >Register
                               </button>

                               <button type="button" style="width: 120px;"
                               class="btn btn-secondary bg-primary"
                               onclick="location.href =
                               'view_posts.php?name=Your Quotes'" >
                               Your Quotes
                               </button>

                               <button type="button" style="width: 120px;"
                               class="btn btn-secondary bg-primary"
                               onclick="location.href =
                               'post.php?name=Add A Quote'">Add A Quote
                               </button>

                               <button type="button" style="width: 120px;"
                               class="btn btn-secondary bg-primary"
                               onclick="location.href =
                               'forum_c.php?name=Comic Quotes'" >
                               Comic Quotes
                               </button>

                               <button type="button" style="width: 120px;"
                               class="btn btn-secondary bg-primary"
                               onclick="location.href =
                               'forum_w.php?name=Wise Quotes'">
                               Wise Quotes
                               </button>

                               <button type="button" style="width: 120px;" class=
                               "btn btn-secondary bg-primary"
                               onclick="location.href =
                               'search.php?name=Search Quotes'" >
                               Search Quotes
                               </button>
              <?php
              break;

Listing 10-2bCreating the Header for the Home Page (includes/header.php)

标题使用一个水平菜单,在页面上为成员的发帖和回复提供最大的空间。清单 10-2b 中显示的代码段包括引导代码,用于在较小的设备(手机)中将菜单更改为“煎饼菜单”。case 语句的第一段(case 1)演示了用户登录时索引页面菜单的状态。除了“登录”和“注册”按钮之外,所有按钮都已启用。如果用户未登录,不同的按钮块(情况 5,未显示)将仅显示登录和注册按钮为活动状态(如图 10-2 所示)。

<?php // Start the session.
session_start() ;
     if ( isset( $_SESSION[ 'member_id' ] ) )
                 { $menu = 1;}
     else { $menu = 5; }
?>

索引页面代码的顶部(清单 10-2a )包含了前一段。该段通过搜索 member_id 值来确定显示哪个菜单。如果存在,则用户已经登录。变量\(menu 设置为 1,这将激活除登录和注册之外的所有按钮。如果 member_id 值不存在,则用户尚未登录。变量\)menu 设置为 5。案例 5 的菜单块(在 header.php 文件的中)仅激活登录和注册按钮。

注意

目前,很少有按钮工作,因为我们还没有创建其他页面。这将在我们创建一些数据后得到纠正。

下一步将是创建注册页面,以便我们可以注册一些成员数据。

创建注册表单

登记表是第七章中表格的简化版。形式如图 10-3 所示。

img/314857_2_En_10_Fig3_HTML.jpg

图 10-3

注册页面

如前所述,用户名不是成员的真实姓名,而是匿名登录网站的假名。

清单 10-3a 显示了注册表单的代码,并将一条记录插入到成员表中,清单 10-3b 给出了 PHP 代码。

<?php
$menu = 2;
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//require("cap.php");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Register Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content=
"width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
   <script src="verify.js"></script>
   <script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:30px;border: 2px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
style="margin-bottom:2px;
background:linear-gradient(#0073e6, white); padding:10px;
padding-right: 5px;">
                <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12">
<h4 class="text-center">Registration</h4>
<h4 class="text-center">Items marked with an asterisk *
are required</h4>
<h6 class="text-center"><strong>IMPORTANT:</strong> Do NOT
use your real name for the username.</h6>
<h6 class="text-center"><strong>Terms and conditions:</strong>
Your registration and all your messages
will be immediately deleted </h6>
<h6 class="text-center">if you post unpleasant, obscene or
defamatory messages to the message board.</h6>
<?php
try {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
                require('process-register-page.php');
} // End of the main Submit conditional.
?>
<div class="col-sm-10">
<form action="safer-register-page.php" method="post"
onsubmit="return checked();" name="regform" id="regform">
<div class="form-group row">
   <label for="user_name" class="col-sm-4 col-form-label text-right">
User Name*:</label>
    <div class="col-sm-8">
<input type="text" class="form-control" id="user_name"
        name="user_name"
                        pattern="[a-zA-Z][a-zA-Z0-9\s]*" title=
"Alphabetic, numeric and space only max of 30 characters"
                        placeholder="User Name" maxlength="30" required
                        value=
                                "<?php if (isset($_POST['user_name']))
                        echo htmlspecialchars($_POST['user_name'], ENT_QUOTES);
                        ?>" >
    </div>
</div>
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-right">
E-mail*:</label>
<div class="col-sm-8">
        <input type="email" class="form-control" id="email"
name="email" placeholder="E-mail" maxlength="60"
required
                        value=
                                "<?php if (isset($_POST['email']))
                        echo htmlspecialchars($_POST['email'], ENT_QUOTES);
?>" >
    </div>
</div>
<div class="form-group row">
<label for="password1" class="col-sm-4 col-form-label
text-right">Password*:</label>
<div class="col-sm-8">
      <input type="password" class="form-control" id="password1"
                        name="password1"
                        pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,12}"
                        title=
"One number, one upper, one lower, one special, with 8 to 12 characters"
                placeholder="Password" minlength="8" maxlength="12" required
                        value=
                            "<?php if (isset($_POST['password1']))
                echo htmlspecialchars($_POST['password1'], ENT_QUOTES); ?>" >
                        <span id="message">Between 8 and 12 characters.</span>
    </div>
</div>
<div class="form-group row">
<label for="password2" class="col-sm-4 col-form-label text-right">
Confirm Password*:</label>
<div class="col-sm-8">
      <input type="password" class="form-control" id="password2"
        name="password2"
              pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,12}"
                        title=
"One number, one uppercase, one lowercase letter, with 8 to 12 characters"
                        placeholder=
"Confirm Password" minlength="8" maxlength="12" required
                        value=
                                "<?php if (isset($_POST['password2']))
                echo htmlspecialchars($_POST['password2'], ENT_QUOTES); ?>" >
    </div>
</div>
<div class="form-group row">
<label for="question" class="col-sm-4 col-form-label text-right">
          Secret Question*:</label>
<div class="col-sm-8">
          <select id="question" class="form-control">
                        <option selected value="">- Select -</option>
<option value="Maiden">Mother's Maiden Name</option>
                        <option value="Pet">Pet's Name</option>
                        <option value="School">High School</option>
                        <option value="Vacation">Favorite Vacation Spot</option>
                </select>
        </div>
</div>
<div class="form-group row">
<label for="secret" class="col-sm-4 col-form-label
text-right">Answer*:</label>
<div class="col-sm-8">
      <input type="text" class="form-control" id="secret" name="secret"
                pattern="[a-zA-Z][a-zA-Z\s\.\,\-]*"
                title="Alphabetic, period, comma, dash and space only max of 30
                        characters"
                placeholder="Secret Answer" maxlength="30" required
                value=
                        "<?php if (isset($_POST['secret']))
                        echo htmlspecialchars($_POST['secret'], ENT_QUOTES); ?>" >
  </div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8" style="padding-left: 80px;">
<div class="float-left g-recaptcha"
data-sitekey=
"6LcrQ1wUAAAAAPxlrAkLuPdpY5qwS9rXF1j46fhq"></div>
</div>
</div>
<div class="form-group row">
<label for="" class="col-sm-3 col-form-label"></label>
<div class="col-sm-8 text-center">
        <input id="submit" class="btn btn-primary" type="submit" name="submit"
        value="Register">
</div>
</div>
</form>
</div>
</div>
</div>
<!-- Footer Section -->
<footer class="jumbotron row mx-auto" id="includefooter"
style="padding-bottom:1px; margin: 0px; padding-top:8px;
padding-left: 0px; background-color:white;">
<div class="col-sm-12 text-center">
                <?php include('includes/footer.php'); ?>
  </div>
</footer>
</div>
</div>
<?php
}
catch(Exception $e) // We finally handle any problems here
 {
// print "An Exception occurred. Message: " . $e->getMessage();
        print "The system is busy please try later";
        //  $date = date('m.d.y h:i:s');
        //  $errormessage = $e->getMessage();
        //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
        //   error_log($eMessage,3,ERROR_LOG);
// e-mail support person to alert there is a problem
        //  error_log("Date/Time: $date – Exception Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Exception Error \nFrom:
// Error Log <errorlog@helpme.com>" . "\r\n");
 }
catch(Error $e)
 {
   // print "An Error occurred. Message: " . $e->getMessage();
   print "The system is busy please try later";
   // $date = date('m.d.y h:i:s');
   // $errormessage = $e->getMessage();
   // $eMessage = $date . " | Error | " , $errormessage . |\n";
   // error_log($eMessage,3,ERROR_LOG);
   // e-mail support person to alert there is a problem
        //  error_log("Date/Time: $date – Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Error \nFrom: Error Log
// <errorlog@helpme.com>" . "\r\n");
 }
 ?>
</body>
</html>

Listing 10-3aCreating the Registration Page (safer-register-page.php)

<?php
define("ERROR_LOG","errors.log");
// Has the form been submitted?
try {
require ('mysqli_connect.php'); // Connect to the database
$errors = array(); // Initialize an error array.
// --------------------check the entries-------------
// Trim the first name
        $user_name = filter_var( $_POST['user_name'], FILTER_SANITIZE_STRING);
if ((!empty($user_name)) && (preg_match('/[a-z0-9\s]/i',$user_name)) &&
                (strlen($user_name) <= 30)) {
                //Save the trimmed first name
        $user_nametrim = $user_name;
        }else{
                $errors[] =
'First name missing or not alphabetic, numeric and space characters.
        Max 30';
        }
// Check that an email address has been entered
        $emailtrim = filter_var( $_POST['email'], FILTER_SANITIZE_EMAIL);
        if  ((empty($emailtrim)) ||
(!filter_var($emailtrim, FILTER_VALIDATE_EMAIL))
                        || (strlen($emailtrim > 60))) {
                $errors[] = 'You forgot to enter your email address';
                $errors[] = ' or the e-mail format is incorrect.';
        }
// Check for a password and match against the confirmed password:
$password1trim = filter_var( $_POST['password1'], FILTER_SANITIZE_STRING);
if (empty($password1trim)){   //
$errors[] ='Please enter a valid password';
}
else {
if(!preg_match(
'/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$@!%&*?])
[A-Za-z\d#$@!%&*?]{8,12}$/',
$password1trim)) {  //
$errors[] =
'Invalid password, 8 to 12 chars, one upper, one lower,
one number, one special.';
} else
{
$password2trim =
filter_var( $_POST['password2'], FILTER_SANITIZE_STRING);
if($password1trim === $password2trim) { //
$password = $password1trim;
}else{
$errors[] = 'Your two passwords do not match.';
$errors[] = 'Please try again';
}
}
}
//Is the secret present? If it is, sanitize it
$secret = filter_var( $_POST['secret'], FILTER_SANITIZE_STRING);
if ((!empty($secret)) && (preg_match('/[a-z\.\s\,\-]/i', $secret)) &&
        (strlen($secret) <= 30)) {
        //Sanitize the trimmed city
        $secrettrim = $secret;
}else{
        $errors[] =
'Missing city. Only alphabetic, period, comma, dash and space. Max 30.';
}
if (empty($errors)) { // If everything's OK.
// If no problems encountered, register user in the database
//Determine whether the email address has already been registered
$query = "SELECT user_name FROM members WHERE email = ? ";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
mysqli_stmt_bind_param($q,'s', $emailtrim);
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);

if (mysqli_num_rows($result) == 0){
//The email address has not been registered
//already therefore register the user in the users table
                        //-------------Valid Entries - Save to database -----
                //Start of the SUCCESSFUL SECTION. i.e.
//all the required fields were filled out
                $hashed_password = password_hash($password, PASSWORD_DEFAULT);
                // Register the user in the database...
                // Register the user in the database...
                $query =
"INSERT INTO members (member_id, user_name, email,
        passcode, secret, reg_date) ";
        $query .= "VALUES(' ', ?, ?, ?, ?, NOW() )";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// use prepared statement to ensure that only text is inserted
// bind fields to SQL Statement
mysqli_stmt_bind_param($q, 'ssss', $user_nametrim, $emailtrim,
        $hashed_password, $secrettrim);
// execute query
mysqli_stmt_execute($q);
if (mysqli_stmt_affected_rows($q) == 1) {
                        header ("location: register-thanks.php");
                } else {
                        // echo 'Invalid query:' . $dbcon->error;
                $errorstring = "System is busy, please try later";
echo "<p class=' text-center col-sm-2' style='color:red'>$errorstring</p>";
                }
        }else{//The email address is already registered
        $errorstring = 'The email address is already registered.';
echo "<p class=' text-center col-sm-2'
style='color:red'>$errorstring</p>";
}
} else {//End of SUCCESSFUL SECTION
// ---------------Process User Errors---------------
// Display the users entry errors
$errorstring = 'Error! The following error(s) occurred: ';
foreach ($errors as $msg) { // Print each error.
$errorstring .= " - $msg<br>\n";
    }
$errorstring .= 'Please try again.';
echo "<p class=' text-center col-sm-2'
        style='color:red'>$errorstring</p>";
}// End of if (empty($errors)) IF.
}
catch(Exception $e) // We finally handle any problems here
{
// print "An Exception occurred. Message: " . $e->getMessage();
print "The system is busy please try later";
//  $date = date('m.d.y h:i:s');
//  $errormessage = $e->getMessage();
//  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
//   error_log($eMessage,3,ERROR_LOG);
// e-mail support person to alert there is a problem
//  error_log("Date/Time: $date – Exception Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Exception Error \nFrom:
//Error Log <errorlog@helpme.com>" . "\r\n");
}
catch(Error $e)
{
// print "An Error occurred. Message: " . $e->getMessage();
print "The system is busy please try later";
// $date = date('m.d.y h:i:s');
// $errormessage = $e->getMessage();
// $eMessage = $date . " | Error | " , $errormessage . |\n";
// error_log($eMessage,3,ERROR_LOG);
// e-mail support person to alert there is a problem
//  error_log("Date/Time: $date – Error, Check error log for
//details", 1, noone@helpme.com, "Subject: Error \nFrom:
//Error Log <errorlog@helpme.com>" . "\r\n");
}
?>

Listing 10-3bChecking the Registration Page (process-register-page.php)

不需要对代码进行解释,因为它类似于第七章中的登记表。

注意

大多数菜单项都被禁用,只有主页按钮处于活动状态。主页标题上的注册按钮现在可以工作了,因为它可以链接到我们新创建的注册页面。

“谢谢”页面

如果注册成功,显示“谢谢”,如图 10-4 所示。

img/314857_2_En_10_Fig4_HTML.jpg

图 10-4

“谢谢”页面

注意

safer-register-page.php中使用的按钮块(情况 2)在“谢谢”文件中使用。只有主页是活动的。

清单 10-4 显示了“谢谢”页面的代码。

<?php
        $menu = 2;
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Thank You Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
                href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity=
        "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
<div class="container" style="margin-top:30px;border: 2px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
        style="margin-bottom:2px; background:linear-gradient(#0073e6, white); padding:10px;">
        <?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<div class="col-sm-12">
        <h4 class="text-center">Thank you for registering</h4>
        <h5 class="text-center">On the Home Page, you will now be able to login</h5>
        <h5 class="text-center">and add new quotes to the message board.</h5>
</div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
        style="padding-bottom:1px; margin: 0px; padding-top:8px; background-color:white;">
        <div class="col-sm-12 text-center">
                <?php include('includes/footer.php'); ?>
        </div>
</footer>
</div>
</div>
</body>

Listing 10-4Creating the “Thank You” Page (register-thanks.php)

推广成员表

现在您已经有了一个注册表单,您可以使用标题中的 Register 按钮来注册表 10-3 中给出的成员,或者您可以导入可下载的 SQL 文件。

表 10-3

成员表的属性

|

用户名

|

电子邮件地址

|

密码

|
| --- | --- | --- |
| 粉红色的百合 | jsmith@myisp.co.uk | 砰砰砰!3b33 |
| 巨人台阶 12 | ndean@myisp.co.uk | 第三季第 1 集 |
| 机械 7 | jdoe@myisp.co.uk | -= ytet-伊甸园字幕组=-翻译:粒粒粒尘紫月猫姐 scenery 校对 |
| 内衣 | jsmith@outcook.com | D0gs0dy!2 |
| 神话之王 | 亚瑟@myisp.net | Cam@10t4 |

成员将自动拥有默认的 member_level 值零。为了节省空间,在本教程中,我们没有包括管理设施。如果要添加此功能,请将成员注册为管理员,member_level 值为 1。然后为他们提供管理页面,如前几章所述。如果没有这个特性,网站管理员将需要监控最新的帖子,并使用 phpMyAdmin 删除任何攻击性的帖子。

现在我们有了一些注册会员,他们可以登录了。

登录页面

图 10-5 显示登录页面。

img/314857_2_En_10_Fig5_HTML.jpg

图 10-5

登录页面

清单 10-5a 给出了登录页面的代码。

标题块(情况 3)禁用除注册和主页之外的所有按钮。这段代码处理来自登录表单的提交。

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
      //require("cap.php");
}
$menu = 3;
?>
<!DOCTYPE html>
<html lang="en">
<head>
      <title>Login Page</title>
      <meta charset="utf-8">
      <meta name="viewport"
              content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <!-- Bootstrap CSS File -->
      <link rel="stylesheet"
              href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
              integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
              crossorigin="anonymous">
      <script src='https://www.google.com/recaptcha/api.js'></script>
      <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
      <div class="container"
              style="margin-top:30px;border: 2px black solid;">
      <!-- Header Section -->
      <header class="jumbotron text-center row" id="includeheader"
              style="margin-bottom:2px;
              background:linear-gradient(#0073e6,whit1); padding:20px;">
              <?php include('includes/header.php'); ?>
      </header>
      <!-- Body Section -->
      <div class="content mx-auto" id="contents">
      <div class="row mx-auto" style="padding-left: 0px; height: auto;">
      <div class="col-sm-12">
      <?php
      if ($_SERVER['REQUEST_METHOD'] == 'POST') {                                       //#1
               require('process-login.php');
      }
      ?>
      <!-- Display the login form fields -->
      <div class="col-sm-10">
                <h3 class="h3 text-center">
                         <?php if(empty($errorstring)) { echo "Login"; }
                                  else { echo $errorstring; }
                         ?>
                </h3>
      <form action="login.php" method="post" name="loginform" id="loginform">
                <div class="form-group row">
      <label for="user_name" class="col-sm-4 col-form-label text-right">
                User ID:</label>
      <div class="col-sm-6">
        <input type="user_name" class="form-control" id="user_name"
                         name="user_name"
                         placeholder="User ID" maxlength="30" size="30" required
                         value=
                         "<?php if (isset($_POST['user_name']))
              echo htmlspecialchars($_POST['user_name'], ENT_QUOTES); ?>" >
    </div>
</div>
<div class="form-group row">
    <label for="passcode" class="col-sm-4 col-form-label
                text-right">Password:</label>
    <div class="col-sm-6">
                <input type="password" class="form-control" id="passcode"
                         name="passcode"
                         pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,12}"
                         title="One number, one upper, one lower, one special,
                                 with 8 to 12 characters"
                         placeholder="Password" minlength="8" maxlength="12"
                         required
                         value=
                         "<?php if (isset($_POST['passcode']))
                echo htmlspecialchars($_POST['passcode'], ENT_QUOTES); ?>" >
                         <span id="message">Between 8 and 12 characters.</span>
    </div>
</div>
<div class="form-group row">
     <label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8" style="padding-left: 80px;">
<div class="float-left g-recaptcha" data-sitekey=
              "6LcrQ1wUAAAAAPxlrAkLuPdpY5qwS9rXF1j46fhq"></div>
</div>
</div>
<div class="form-group row">
     <label for="" class="col-sm-3 col-form-label"></label>
<div class="col-sm-8 text-center">
     <input id="submit" class="btn btn-primary" type="submit" name="submit"
             value="Login">
     </div>
   </div>
</form>
</div>
</div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
     style="padding-bottom:1px; margin: 0px; padding-left: 0px;
     padding-top:8px; background-color:white;">
     <div class="col-sm-12 text-center">
          <?php include('includes/footer.php'); ?>
     </div>
</footer>
</div>
</div>
</body>
</html>

Listing 10-5aCreating the Login Page (login.php)

代码的解释

您之前已经看到了所有的代码,但是编号为#1 的行引用了一个需要解释的文件。

if ($_SERVER['REQUEST_METHOD'] == 'POST') {                                             //#1
         require('process-login.php');
}

名为process-login.php的文件启动登录过程。清单 10-5b 中给出了代码。

<?php
        define('ERROR_LOG',"errors.log");
        // This section processes submissions from the login form
        // Check if the form has been submitted:
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        //connect to database
        try {
                require ('mysqli_connect.php');
                // Check that user name has been entered
                $user_name = filter_var( $_POST['user_name'], FILTER_SANITIZE_STRING);
                if  ((empty($user_name))
                        || (strlen($user_name > 30))) {
                        $errors[] = 'You forgot to enter your User ID';
                        $errors[] = ' or the User ID format is incorrect.';
                }
        // Check for a password and match against the confirmed password:
        $password = filter_var( $_POST['passcode'], FILTER_SANITIZE_STRING);
        //$string_length = strlen($password);
        if (empty($password)){
                $errors[] ='Please enter a valid password';
        }
        else {
                if(!preg_match(
                '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$@!%&*?])[A-Za-z\d#$@!%&*?]{8,12}$/',
                        $password)) {
                $errors[] = 'Invalid password, 8 to 12 chars, one upper, one lower, one number, one special.';
        }
}
  if (empty($errors)) { // If everything's OK.
        // Retrieve the user_id, psword, first_name and user_level for that
        // email/password combination
        $query = "SELECT member_id, passcode, user_name FROM members ";
        $query .= "WHERE user_name=?";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
                // bind $user_name to SQL Statement
        mysqli_stmt_bind_param($q, "s", $user_name);
        // execute query
        mysqli_stmt_execute($q);
        $result = mysqli_stmt_get_result($q);
        $row = mysqli_fetch_array($result, MYSQLI_NUM);
        if (mysqli_num_rows($result) == 1) {
                //if one database row (record) matches the input:-
                // Start the session, fetch the record and insert the
                // values in an array
                if (password_verify($password, $row[1])) {
                        session_start();
                        $_SESSION[ 'member_id' ] = $row[0];
                        $_SESSION[ 'user_name' ] = $row[2] ;
                        header ( 'Location: forum.php' ) ;
                } else { // No password match was made.
                        $errors[] = 'User ID/Password entered does not match our records. ';
                        $errors[] = 'Perhaps you need to register, just click the Register ';
                        $errors[] = 'button on the header menu';
                }
        } else { // No e-mail match was made.
                $errors[] = 'User ID/Password entered does not match our records. ';
                $errors[] = 'Perhaps you need to register, just click the Register ';
                $errors[] = 'button on the header menu';
        }
}
if (!empty($errors)) {
                $errorstring = "Error! <br /> The following error(s) occurred:<br>";
                foreach ($errors as $msg) { // Print each error.
                        $errorstring .= " $msg<br>\n";
                }
                $errorstring .= "Please try again.<br>";
                }// End of if (!empty($errors)) IF.

}
catch(Exception $e) // We finally handle any problems here
   {
        // print "An Exception occurred. Message: " . $e->getMessage();
        print "The system is busy please try later";
        //  $date = date('m.d.y h:i:s');
        //  $errormessage = $e->getMessage();
        //  $eMessage = $date . " | Exception Error | " , $errormessage . |\n";
        //   error_log($eMessage,3,ERROR_LOG);
        // e-mail support person to alert there is a problem
        //  error_log("Date/Time: $date – Exception Error, Check error log for
        //details", 1, noone@helpme.com, "Subject: Exception Error \nFrom:
        // Error Log <errorlog@helpme.com>" . "\r\n");
   }
catch(Error $e)
   {
        // print "An Error occurred. Message: " . $e->getMessage();
        print "The system is busy please try later";
        // $date = date('m.d.y h:i:s');
        // $errormessage = $e->getMessage();
        // $eMessage = $date . " | Error | " , $errormessage . |\n";
        // error_log($eMessage,3,ERROR_LOG);
        // e-mail support person to alert there is a problem
        //  error_log("Date/Time: $date – Error, Check error log for
        //details", 1, noone@helpme.com, "Subject: Error \nFrom:
        // Error Log <errorlog@helpme.com>" . "\r\n");
   }
}
?>

Listing 10-5bCreating the Code for Processing the Login (process_login.php)

验证与前几章相同。如果登录失败,它会显示错误消息。

注销

注销是一项重要的安全功能。除非用户注销(或关闭浏览器),否则会话仍然是活动的,并且有人可以访问和更改信息。清单 10-5c 显示了注销页面的代码。

<?php
session_start();//access the current session.
// if no session variable exists then redirect the user
if (!isset($_SESSION['member_id'])) {
header("location:index.php");
exit();
//cancel the session and redirect the user:
}else{ //cancel the session
    $_SESSION = array(); // Destroy the variables
        $params = session_get_cookie_params();
        // Destroy the cookie
        setcookie(session_name(), ", time()-42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]);
if (session_status() == PHP_SESSION_ACTIVE) {
session_destroy(); } // Destroy the session itself
header("location:index.php");
        }

Listing 10-5cCreating the Logout File (logout.php)

用户注销后,只能访问登录或注册页面以及主页。前面描述的登录页面将注册用户重定向到论坛页面,在那里他们可以选择查看哪个论坛。接下来描述论坛页面。

创建选择报价的途径

门户页面允许成员选择要查看的类别,如图 10-6 所示。

img/314857_2_En_10_Fig6_HTML.jpg

图 10-6

forum.php 页面允许用户选择要查看的报价类别

清单 10-6 给出了网关页面的代码。

标题块(header.php的情况 4)激活除登录和注册之外的所有按钮,因为用户现在已经登录。

<?php
// Start the session.
session_start() ;
//Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
       { header("Location: login.php");
                exit(); }
$menu = 4;
?>
<!DOCTYPE html>
<html lang="en">
<head>
       <title>Thank You Page</title>
       <meta charset="utf-8">
       <meta name="viewport" content="width=device-width,
                initial-scale=1, shrink-to-fit=no">
       <!-- Bootstrap CSS File -->
       <link rel="stylesheet"
                 href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                 integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                 crossorigin="anonymous">
       <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
       <div class="container" style="margin-top:30px;border:
                2px black solid;">
       <!-- Header Section -->
       <header class="jumbotron text-center row" id="includeheader"
               style="margin-bottom:2px;
               background:linear-gradient(#0073e6,white); padding:10px;">
               <?php include('includes/header.php'); ?>
       </header>
       <!-- Body Section -->
       <div class="content mx-auto" id="contents">
       <div class="row mx-auto" style="padding-left: 0px; height: auto;">
       <div class="col-sm-12">
            <h4 class="text-center">Thanks for logging in.
               Choose a forum from the menu above.</h4>
       </div>
       </div>
       <footer class="jumbotron row mx-auto" id="includefooter"
            style="padding-bottom:1px; margin: 0px; padding-top:8px;
               background-color:white;">
               <div class="col-sm-12 text-center">
                         <?php include('includes/footer.php'); ?>
               </div>
       </footer>
    </div>
    </div>
</body>
</html>

Listing 10-6Creating a Gateway to the Two Categories (forum.php)

在我们创建两个论坛页面之前,我们需要在论坛页面中显示一些报价。我们现在将创建一个表单,以便我们可以输入一些报价。

用于发布报价的表单

图 10-7 显示了过账表单。

img/314857_2_En_10_Fig7_HTML.jpg

图 10-7

用于发布报价的表单

你可以用这个词主语或者,如果你愿意,你可以用类别来代替。在本教程中,它们是同义词。主题使用下拉菜单有两个原因。

  • 我们假设留言板所有者想要限制主题(类别)的数量,因为所有者不希望成员创建新的主题。成员们将会在“滑稽语录”主题或“智慧语录”主题中添加新的语录。

  • 拼写需要一致。下拉菜单保证了这一点。当然,所有者可以在下拉菜单中添加新的主题(类别)。

清单 10-7a 给出了发布页面的代码。

包含的标题块(header.php的案例 8)激活除登录、注销和添加报价之外的所有按钮。

<?php
// Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
    { header("Location: login.php");
exit(); }
$menu = 8;
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    //require("cap.php");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Post A Quote Page</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS File -->
    <link rel="stylesheet"
            href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
            integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
    crossorigin="anonymous">
    <script src='https://www.google.com/recaptcha/api.js'></script>
    <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
    <div class="container" style="margin-top:30px;border: 2px black solid;">
    <!-- Header Section -->
    <header class="jumbotron text-center row" id="includeheader"
            style="margin-bottom:2px; background:linear-gradient(#0073e6,white); padding:10px;">
            <?php include('includes/header.php'); ?>
    </header>
    <!-- Body Section -->
    <div class="content mx-auto" id="contents">
    <div class="row mx-auto" style="padding-left: 0px; height: auto;">
    <div class="col-sm-10" style="padding-top: 20px;">
            <h4 class="text-center">Post a Quotation</h4>
    <!-- Display the form fields-->
    <form id="post_form" action="process_post.php" method="post" accept-charset="utf-8"  >
            <div class="form-group row">
                    <label for="question" class="col-sm-4 col-form-label text-right">
            Choose the Subject*:</label>
            <div class="col-sm-8">
                    <select id="subject" name="subject" class="form-control">
                    <option selected value="">- Select -</option>
                    <option value="Comic Quotes">Comic Quotes</option>
                    <option value="Wise Quotes">Wise Quotes</option>
                    </select>
            </div>
            </div>
    <div class="form-group row">
            <label for="" class="col-sm-4 col-form-label text-right"></label>
    <div class="col-sm-8 text-center">
                    <label for="message">Please enter your quote below</label>
                    <textarea class="form-control" id="message" name="message" rows="5" cols="50"
                    value=
                            "<?php if (isset($_POST['message']))
                    echo htmlspecialchars($_POST['message'], ENT_QUOTES); ?>" >
            </textarea>
    </div>
    <div>
    <div class="form-group row">
    <label class="col-sm-4 col-form-label"></label>
    <div class="col-sm-8">
    <div class="g-recaptcha" style="margin-left: 90px;"
            data-sitekey="6LcrQ1wUAAAAAPxlrAkLuPdpY5qwS9rXF1j46fhq"></div>
    </div>
    </div>
    <div class="form-group row">
            <label for="" class="col-sm-3 col-form-label"></label>
    <div class="col-sm-8 text-center" style="padding-left: 40px;">
            <input id="submit" class="btn btn-primary" type="submit" name="submit" value="Submit">
    </div>
    </div>
</form>
</div>
<!--posting an entry into the database table automatically sends a message to the forum moderator                                                                                 #1
// Assign the subject-->
<!--<?php
    $subject = "Posting added to message board";
    $member = isset($_SESSION['user_name']) ? $_SESSION['user_name'] : "";
    $body = "Posting added by " . $member;
    mail("admin@myisp.co.uk", $subject, $body, "From:admin@myisp.co.uk\r\n");
    ?>-->
</div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
    style="padding-bottom:1px; margin: 0px; padding-top:8px; background-color:white;">
    <div class="col-sm-12 text-center">
        <?php include('includes/footer.php'); ?>
    </div>
</footer>
</div>
</body>
</html>

Listing 10-7aCreating the Form for Posting New Quotations (post.php)

代码的解释

本节解释代码。

<!--posting an entry into the database table automaticlally sends a message to the forum moderator                                                                                 #1
// Assign the subject-->
<!--<?php
    $subject = "Posting added to message board";
    $member = isset($_SESSION['user_name']) ? $_SESSION['user_name'] : "";
    $body = "Posting added by " . $member;
    mail("admin@myisp.co.uk", $subject, $body, "From:admin@myisp.co.uk\r\n");
?>-->

每当论坛中添加新报价时,都会向管理员发送一封电子邮件。然后,管理员应该检查公告,以确保它是适当的。如前所述,论坛提供商对论坛的内容负责。在这个例子中,这个电子邮件部分被注释掉了,因为只有当论坛被加载到提供 PHP 电子邮件服务的 web 服务器上时,这个代码才起作用。

本例中的电子邮件保持可用。PHP 函数 mail()的格式如下:mail (to,subject,body,from)。

收件人和发件人必须是电子邮件地址。变量可以是基本的,比如本例中的$subject,也可以是复杂的。在这个清单中,发布消息的人的用户名从会话中提取,然后与一些文本连接在一起,形成电子邮件的正文。“主题”、“收件人”和“发件人”项创建了电子邮件的标题。标题是电子邮件的顶部;正文是页眉下面的窗口。生成的电子邮件将类似于表 10-4 。

表 10-4

电子邮件的外观

| 来自: | admin@myisp.co.uk | | 日期: | 2018 年 08 月 02 日 17 时 26 分 | | 致: | admin@myisp.co.uk | | 主题: | 张贴添加到快速报价留言板 | | 粉红百合添加的帖子 |

当然,您必须用自己的电子邮件地址替换这些假电子邮件地址。请注意,给自己发送电子邮件时,两个电子邮件地址是相同的。

当然,数据库可以由熟悉 phpMyAdmin 的管理员管理;然而,为不熟悉 phpMyAdmin 的人创建一个用户友好的管理工具是很好的。(查看前面的章节,尤其是第三章,了解创建管理页面的说明。)用户友好的管理页面允许管理员查看最新帖子的表格。该表将有删除和编辑链接,如第三章所述。或者,可以在论坛表中添加一个额外的字段,以指示批准了哪些帖子。则只能显示批准的报价。这将不允许显示任何未批准的帖子。

注意

除非提供 PHP 电子邮件服务,否则不会使用您的计算机发送和接收电子邮件。购买主机环境时,确保包含 PHP 电子邮件。

处理过帐

清单 10-7b 给出了 process_post.php 的代码。

<?php
// Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
        { header("Location: login.php");
        exit(); }
//Connect to the database
require ( 'mysqli_connect.php' ) ;
// Has the form been submitted?
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
        // Check that the user has entered a subject and a message                        #1
        $subject = filter_var( $_POST['subject'], FILTER_SANITIZE_STRING);
         if ( empty($subject ) ) { echo 'You forgot to select a subject.'; }

        $comment = filter_var( $_POST['message'], FILTER_SANITIZE_STRING);
        if ((!empty($comment)) && (strlen($comment) <= 480)) {
                // remove ability to create link in email
                $patterns = array("/http/", "/https/", "/\:/","/\/\//","/www./");
                $commenttrim = preg_replace($patterns," ", $comment);
        }else{ // if comment not valid display error page
                echo "You forgot to enter a message";
        }
        // If successful insert the post into the database table
        if( !empty($commenttrim) && !empty($subject) )
        {
                //Make the insert query                                                   #2
                $query = "INSERT INTO forum (post_id, user_name, subject, message, post_date) ";
                $query .= "VALUES( ' ', ?, ?, ?, NOW() )";
                $q = mysqli_stmt_init($dbcon);
                mysqli_stmt_prepare($q, $query);
                // use prepared statement to ensure that only text is inserted
                // bind fields to SQL Statement
                $user_name = filter_var( $_SESSION['user_name'], FILTER_SANITIZE_STRING);
                mysqli_stmt_bind_param($q, 'sss', $user_name, $subject, $commenttrim);
                // execute query
                mysqli_stmt_execute($q);
                if (mysqli_stmt_affected_rows($q) == 1) {
                        header ("Location: post_thanks.php");
        }
        else
        {
                echo "An Error has occurred in loading your posting";
        }
// Close the database connection
mysqli_close( $dbcon ) ;
}
 }
?>

Listing 10-7bCreating the File for Processing the Postings (process_post.php)

代码的解释

本节解释代码。

// Check that the user has entered a subject and a message                           #1
$subject = filter_var( $_POST['subject'], FILTER_SANITIZE_STRING);
if ( empty($subject ) ) { echo 'You forgot to select a subject.'; }

$comment = filter_var( $_POST['message'], FILTER_SANITIZE_STRING);
if ((!empty($comment)) && (strlen($comment) <= 480)) {
        // remove ability to create link in email
        $patterns = array("/http/", "/https/", "/\:/","/\/\//","/www./");
        $commenttrim = preg_replace($patterns," ", $comment);

消息的文本区域(以及来自用户的所有信息)吸引了那些想要插入危险脚本的恶意用户。因此,必须在代码中内置特殊的安全过滤器。使用带有 FILTER_SANITIZE_STRING 参数的 filter_var 函数清理 textarea(和 subject)中的消息。这使得条目无害。使用预准备语句还可以确保传递的字符串作为文本传递,并且不会造成伤害。

之前已经多次遇到 filter_var 和 FILTER_SANITIZE_STRING。它将删除任何不需要的字符,包括撇号。

//Make the insert query                                                             #2
$query = "INSERT INTO forum (post_id, user_name, subject, message, post_date) ";
$query .= "VALUES( ' ', ?, ?, ?, NOW() )";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// use prepared statement to ensure that only text is inserted
// bind fields to SQL Statement
$user_name = filter_var( $_SESSION['user_name'], FILTER_SANITIZE_STRING);
mysqli_stmt_bind_param($q, 'sss', $user_name, $subject, $commenttrim);
// execute query
mysqli_stmt_execute($q);
if (mysqli_stmt_affected_rows($q) == 1) {
        header ("Location: post_thanks.php");

准备好的 insert 语句使用筛选过的会话用户名、先前筛选过的主题和先前筛选过的引号($commenttrim)。如果由于某种原因,变量仍然包含有害数据,则准备好的语句会将它们用作文本,并且不会执行任何代码。尽管从安全角度来看,这些条目现在是无害的,但它们可能仍然包含不适合留言板的字符或信息。管理员可以更正数据或删除公告。

张贴一些报价

现在我们有了一个将帖子插入论坛表的表单,我们将发布表 10-5 中显示的引用。您还可以使用 phpMyAdmin 从下载文件一章中导入 SQL 文件。当每个报价发布后,您将被重定向到论坛页面。但是,您还不能查看报价,因为我们还没有创建显示报价的两个页面。

表 10-5

贴一些语录

|

以…身份登录

|

主题(又名论坛)

|

消息

|
| --- | --- | --- |
| 粉红色的百合 | 明智的报价 | "逆境使一些人崩溃,另一些人打破记录."威廉·阿瑟·沃德 |
| 机械 7 | 滑稽的引语 | “我喜欢最后期限。我喜欢它们飞过时发出的嗖嗖声。”道格拉斯·亚当斯 |
| 粉红色的百合 | 滑稽的引语 | "打高尔夫球是一种很好的散步方式."[人名]马克·吐温(美国幽默大师、小说家、作家、演说家) |
| 粉红色的百合 | 滑稽的引语 | "生活是一件接一件糟糕的事情。"[人名]马克·吐温(美国幽默大师、小说家、作家、演说家) |
| 巨人台阶 12 | 滑稽的引语 | “给我高尔夫球杆,新鲜空气和一个漂亮的伴侣,你可以保留高尔夫球杆和新鲜空气”杰克·本尼 |
| 神话之王 | 明智的报价 | "没有巨大的热情,就不会有伟大的成就。"拉尔夫·瓦尔多·爱默生 |
| 神话之王 | 明智的报价 | "至理名言往往落在贫瘠的土地上,但一句友善的话绝不会被丢弃."亚瑟帮忙 |
| 神话之王 | 滑稽的引语 | "许多小事情因为正确的广告而变得很大."[人名]马克·吐温(美国幽默大师、小说家、作家、演说家) |
| 神话之王 | 明智的报价 | "同时做两件事等于什么都不做。"Publilius Syrus |
| 巨人台阶 12 | 明智的报价 | "从未犯过错误的人从未尝试过新事物."[人名]阿尔伯特·爱因斯坦(犹太裔理论物理学家) |
| 巨人台阶 12 | 滑稽的引语 | "经验只是我们给自己的错误起的名字."奥斯卡·王尔德 |
| 巨人台阶 12 | 滑稽的引语 | "如果你想重获青春,就砍掉他的零花钱."阿尔·贝恩斯坦 |
| 机械 7 | 滑稽的引语 | "技术进步只是为我们提供了一种更有效的倒退手段."阿尔多斯·赫胥黎 |
| 粉红色的百合 | 明智的报价 | "真正的知识是知道自己无知的程度."孔子 |
| 机械 7 | 明智的报价 | "如果你不在乎谁得到了荣誉,你会取得惊人的成就."哈里·S·杜鲁门 |

纪念

将自动添加发布的日期和时间。

当你有一些报价要显示时,它们会出现在主页上(如图 10-2 所示)。

滑稽的报价页面

图 10-8 显示了滑稽的报价页面。

img/314857_2_En_10_Fig8_HTML.jpg

图 10-8

滑稽的报价页面

清单 10-8 中给出的代码只从数据库论坛表中选择滑稽的引文。

<?php // Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
        { header("Location: login.php");
        exit(); }
$menu = 9;
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Message Board Home Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
                href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity=
        "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
        <div class="container" style="margin-top:30px;border: 2px black solid;">
        <!-- Header Section -->
        <header class="jumbotron text-center row" id="includeheader"
                style="margin-bottom:2px; background:linear-gradient(#0073e6,white); padding:10px;">
                <?php include('includes/header.php'); ?>
        </header>
        <!-- Body Section -->
        <div class="content mx-auto" id="contents">
        <div class="row mx-auto" style="padding-left: 0px; height: auto;">
        <div class="col-sm-12">
                <h4 class="text-center">Comic Quotes</h4>
                <?php
                // Connect to the database
                require ( 'mysqli_connect.php' ) ;
                // Make the query                                                         #1
                $query = "SELECT user_name,post_date,subject,message FROM forum WHERE  ";
                $query .=  "subject = 'Comical Quotes' ORDER BY 'post_date' ASC";
                $result = mysqli_query( $dbcon, $query ) ;
                if ( mysqli_num_rows( $result ) > 0 )
                {
                        ?>
                        <table class="table table-responsive table-striped col-sm-12"
                                style="background: white;color:black; padding-left: 20px;">
                                <tr>
                                        <th scope="col">Posted By</th>
                                        <th scope="col">Forum</th>
                                        <th scope="col">Quotation</th>
                                </tr>
                        <?php
                        while ( $row = mysqli_fetch_array( $result, MYSQLI_ASSOC ))
                        {
                                $user_name = htmlspecialchars($row['user_name'], ENT_QUOTES);
                                $post_date = htmlspecialchars($row['post_date'], ENT_QUOTES);
                                $message = htmlspecialchars($row['message'], ENT_QUOTES);
                                echo '<tr>
                                        <td scope="row">' . $user_name . '</td>
                                        <td scope="row">' . $post_date . '</td>
                                        <td scope="row">' . $message . '</td>
                                </tr>';
                        }
                        echo '</table>' ;
                }
                else { echo 'There are currently no messages.' ; }
        mysqli_close( $dbcon ) ;
?>
</div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
        style="padding-bottom:1px; margin: 0px; padding-top:8px; background-color:white;">
        <div class="col-sm-12 text-center">
                <?php include('includes/footer.php'); ?>
        </div>
</footer>
</div>
</div>
</body>
</html>

Listing 10-8Creating the Comical Quotes Page (forum_c.php)

代码的解释

清单 10-8 中的大部分代码大家都会很熟悉,清单中还有注释提醒你重要的代码行。

我们现在将检查第 1 行:

// Make the query                                                                      #1
$query = "SELECT user_name,post_date,subject,message FROM forum WHERE  ";
$query .=  "subject = 'Comical Quotes' ORDER BY 'post_date' ASC";

该查询只从论坛表中选择滑稽的引文。报价将按发布的升序排序,即最早的先排序。

滑稽引用页面的标题

在查看滑稽报价页面后,用户可能还想查看明智报价页面或插入他们自己的报价。为了让用户能够做到这一点,下面显示的标题块(header.php的第 9 种情况)激活了除登录、注册和漫画引用之外的所有按钮。目前,这些按钮中有几个是无效的,因为我们还没有设计相关的页面。

要显示的按钮在来自header.php的代码片段中显示如下:

case 9: //forum_c.php
        ?>
             <button type="button" style="width: 110px;"
                     class="btn btn-secondary bg-primary"
                     onclick="location.href =
                     'index.php?name=Quick Quotes'">
                     Home Page
             </button>

             <button type="button" style="width: 110px;" class=
                     btn btn-secondary bg-primary disabled"
                     onclick="location.href = "" >Login
             </button>

             <button type="button" style="width: 110px;"
                     class="btn btn-secondary bg-primary"
                     onclick="location.href =
                     'logout.php?name=Logout'">Logout
             </button>

             <button type="button" style="width: 110px;" class=
                     "btn btn-secondary bg-primary disabled"
                     onclick="location.href = "" >Register
             </button>

             <button type="button" style="width: 120px;"
                     class="btn btn-secondary bg-primary"
                     onclick="location.href =
                     'view_posts.php?name=Your Quotes'" >Your Quotes
             </button>

             <button type="button" style="width: 120px;"
                     class="btn btn-secondary bg-primary"
                     onclick="location.href = 'post.php?name=Add A Quote'">
                     Add A Quote
             </button>

             <button type="button" style="width: 120px;" class=
                     "btn btn-secondary bg-primary disabled"
                     onclick="location.href = "" >Comic Quotes
             </button>

             <button type="button" style="width: 120px;"
                     class="btn btn-secondary bg-primary"
                     onclick="location.href =
                     'forum_w.php?name=Wise Quotes'">Wise Quotes
             </button>

             <button type="button" style="width: 120px;"
                     class="btn btn-secondary bg-primary"
                     onclick="location.href =
                     'search.php?name=Search Quotes'" >Search Quotes
             </button>
      <?php
    break;

明智的报价页面

除了 SQL 查询之外,这个页面几乎与滑稽的报价页面相同。此外,明智的报价按钮被禁用,而滑稽的报价按钮被启用。图 10-9 显示了 Wise 报价页面。

img/314857_2_En_10_Fig9_HTML.jpg

图 10-9

明智引语的展示

该页面仅显示明智的报价。清单 10-9 给出了 Wise 报价页面的代码。

<?php // Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
        { header("Location: login.php");
        exit(); }
$menu = 10;
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Message Board Home Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
                href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
        <div class="container" style="margin-top:30px;border: 2px black solid;">
        <!-- Header Section -->
        <header class="jumbotron text-center row" id="includeheader"
                style="margin-bottom:2px; background:linear-gradient(#0073e6,white); padding:10px;">
                <?php include('includes/header.php'); ?>
        </header>
        <!-- Body Section -->
        <div class="content mx-auto" id="contents">
        <div class="row mx-auto" style="padding-left: 0px; height: auto;">
        <div class="col-sm-12">
                <h4 class="text-center">Wise Quotes</h4>
                <?php
                // Connect to the database
                require ( 'mysqli_connect.php' ) ;
                // Make the query                                                       #1
                $query = "SELECT user_name,post_date,subject,message FROM forum ";
                $query .= "WHERE subject = 'Wise Quotes' ORDER BY 'post_date' ASC";
                $result = mysqli_query( $dbcon, $query ) ;
                if ( mysqli_num_rows( $result ) > 0 )
                {
                        ?>
                        <table class="table table-responsive table-striped col-sm-12"
                                style="background: white;color:black; padding-left: 50px;">
                                <tr>
                                        <th scope="col">Posted By</th>
                                        <th scope="col">Forum</th>
                                        <th scope="col">Quotation</th>
                                </tr>
                        <?php
                        while ( $row = mysqli_fetch_array( $result, MYSQLI_ASSOC ))
                        {
                                $user_name = htmlspecialchars($row['user_name'], ENT_QUOTES);
                                $post_date = htmlspecialchars($row['post_date'], ENT_QUOTES);
                                $message = htmlspecialchars($row['message'], ENT_QUOTES);
                                echo '<tr>
                                        <td scope="row">' . $user_name . '</td>
                                        <td scope="row">' . $post_date . '</td>
                                        <td scope="row">' . $message . '</td>
                                </tr>';
                        }
                echo '</table>' ;
                }
                else { echo 'There are currently no messages.' ; }
                mysqli_close( $dbcon ) ;
        ?>
        </div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
        style="padding-bottom:1px; margin: 0px; padding-top:8px; background-color:white;">
        <div class="col-sm-12 text-center">
                <?php include('includes/footer.php'); ?>
        </div>
</footer>
</div>
</div>
</body>
</html>

Listing 10-9Creating the Wise Quotes Page (forum_w.php)

代码的解释

代码几乎与 Comical Quotes 论坛页面的列表相同,除了此处显示的项目:

// Make the query                                                                         #1
$query = "SELECT user_name,post_date,subject,message FROM forum ";
$query .= "WHERE subject = 'Wise Quotes' ORDER BY 'post_date' ASC";

该查询只选择主题为“Wise Quotes”的记录报价将按发布的升序排序,即最早的先排序。

与滑稽报价页面一样,一个新的标题块(header.php的案例 10)将允许用户重定向到滑稽报价页面。

明智报价页面的标题

标题块与“滑稽引用”页面的标题块相同,只是“明智引用”按钮被禁用,而“滑稽引用”按钮被激活。请注意,“查看您的帖子”和“搜索”按钮将不起作用,因为相关页面尚未创建。

   case 10: //forum_w.php
            ?>
                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'index.php?name=Quick Quotes'">
                        Home Page
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "" >Login
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'logout.php?name=Logout'">Logout
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "" >Register
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'view_posts.php?name=Your Quotes'" >
                        Your Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'post.php?name=Add A Quotes'">
                        Add A Quote
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'forum_c.php?name=Comic Quotes'" >
                        Comic Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "">Wise Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'search.php?name=Search Quotes'" >
                        Search Quotes
                </button>
        <?php
        break;

添加搜索工具

最可能的搜索原因如下:

  • 成员可能想要查看他们自己的帖子或其他贡献者的帖子的列表。

  • 成员可能希望在邮件中搜索特定作者的特定单词或短语或引文。

在实现这些搜索之前,我们必须创建显示搜索结果的页面。

图 10-10 显示了查看单个成员帖子的显示。

img/314857_2_En_10_Fig10_HTML.jpg

图 10-10

显示一个成员的帖子

清单 10-10 给出了显示成员帖子的页面代码。

<?php
// Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
{ header("Location: login.php");
exit(); }
$menu = 7;
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>View Postings Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content=
"width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
                href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="birds.css">
</head>
<body>
<div class="container" style=
"margin-top:30px;border: 2px black solid;">
<!-- Header Section -->
<header class="jumbotron text-center row" id="includeheader"
style="margin-bottom:2px;
background:linear-gradient(#0073e6,white); padding:10px;">
                        <?php include('includes/header.php'); ?>
</header>
<div id="content"><!--Start of the quotes found page content-->
<?php
// Connect to the database
require ( 'mysqli_connect.php' ) ;                                                      //#1
                $user_name =
filter_var( $_SESSION['user_name'], FILTER_SANITIZE_STRING);
                // Make the full text query
                $query = "SELECT user_name,post_date,subject,message FROM ";
$query .= "forum WHERE user_name = ? ";
                $query .= "ORDER BY post_date ASC";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // bind $id to SQL Statement
                mysqli_stmt_bind_param($q, "s", $user_name);
        // execute query
        mysqli_stmt_execute($q);
        $result = mysqli_stmt_get_result($q);
                if (mysqli_num_rows($result) > 0) {
                        echo '<h4 class="text-center">Your Postings</h4>';
                        ?>
                        <table class=
"table table-responsive table-striped col-sm-12"
                                style="background: white;color:black;
padding-left: 40px;">
                                <tr>
                                        <th scope="col">Posted By</th>
                                        <th scope="col">Forum</th>
                                        <th scope="col">Quotation</th>
                                </tr>
                        <?php
                        while ( $row =
mysqli_fetch_array( $result, MYSQLI_ASSOC ))
                        {
                                $user_name =
htmlspecialchars($row['user_name'], ENT_QUOTES);
                                $post_date =
htmlspecialchars($row['post_date'], ENT_QUOTES);
                                $subject =
htmlspecialchars($row['subject'], ENT_QUOTES);
                                $message =
htmlspecialchars($row['message'], ENT_QUOTES);
                                echo '<tr>
                                        <td scope="row">' . $user_name . " " .
 $post_date . '</td>
                                        <td scope="row">' . $subject . '</td>
                                        <td scope="row">' . $message . '</td>
                                </tr>';
                        }
                        echo '</table>' ;
                }
else { echo '<p class="text-center">
There are currently no messages.</p>' ; }
mysqli_close( $dbcon ) ;
?>
</div><!--End of the quotes found page content.-->
<footer class="jumbotron row mx-auto" id="includefooter"
style="padding-bottom:1px; margin: 0px; padding-top:8px;
background-color:white;">
<div class="col-sm-12 text-center">
                        <?php include('includes/footer.php'); ?>
                </div>
</footer>
</div>
</body>
</html>

Listing 10-10Creating a Page to Display an Individual Member’s Postings (view_posts.php)

代码的解释

您之前已经看到了大部分代码,但是查询需要一些解释。

require ( 'mysqli_connect.php' ) ;                                                      //#1
                $user_name =
filter_var( $_SESSION['user_name'], FILTER_SANITIZE_STRING);
                // Make the full text query
                $query = "SELECT user_name,post_date,subject,message FROM ";
$query .= "forum WHERE user_name = ? ";
                $query .= "ORDER BY post_date ASC";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // bind $id to SQL Statement
                mysqli_stmt_bind_param($q, "s", $user_name);
        // execute query
        mysqli_stmt_execute($q);

该查询选择要显示的项目,并指定两个条件:只显示在会话中指定的用户名($user_name)的过帐,并按过帐日期的升序对表行显示进行排序。

ViewPosts.php 的头球

标题块将激活除登录、注册和报价之外的所有按钮。下面的代码片段显示了来自header.php的代码:

case 8: //post.php
            ?>
                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'index.php?name=Quick Quotes'">
                        Home Page
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "" >Login
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'logout.php?name=Logout'">
                        Logout
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "" >Register
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'view_posts.php?name=Your Quotes'" >
                        Your Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "">Add A Quote
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'forum_c.php?name=Comic Quotes'" >
                        Comic Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'forum_w.php?name=Wise Quotes'">
                        Wise Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'search.php?name=Search Quotes'" >
                        Search Quotes
                </button>
        <?php
        break;

我们现在将启用成员进行全文搜索的能力。

搜索特定的单词或短语

成员们可能希望看到马克·吐温的语录表或关于高尔夫的语录列表;因此,成员将搜索特定的单词或短语。这将需要在标题中的搜索按钮。作为一种替代方案,可以在论坛页面中加入一个搜索字段;然而,为了简单起见,我们将使用更简单的方法:使用一个链接到搜索表单的按钮。

全文搜索将在每封邮件中查找单词。名为 messages 的列必须作为全文索引,如本章开头所示。

全文搜索可用于 VARCHAR 和 text 列。全文搜索不区分大小写,将忽略以下内容:

  • 偏旁的话。如果你想搜索被宠坏的,你得搜索完整的单词。只搜索不会返回你要找的东西。

  • 包含少于四个字符的单词。

  • 别说了。这些都是极为常见的词语,如中的中的中的中的中的中的中的中的中的等。

  • 被搜索列中超过 50%的行中包含的单词或短语。在这种情况下,该单词或短语被视为停用词。这通常可以通过在一个表中有四个或更多记录来避免。

全文搜索表单

我们现在将创建一个在消息(引用)中搜索特定单词或短语的表单。

图 10-11 显示了搜索表单。

img/314857_2_En_10_Fig11_HTML.jpg

图 10-11

搜索表单

清单 10-11 给出了搜索表单的代码。

<?php
// Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
        { header("Location: login.php");
        exit(); }
$menu = 6;
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Search Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
        href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
        integrity=
        "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
        crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
        <div class="container" style="margin-top:30px;border: 2px black solid;">
        <!-- Header Section -->
        <header class="jumbotron text-center row" id="includeheader"
                style="margin-bottom:2px; background:linear-gradient(#0073e6,white); padding:10px;">
                <?php include('includes/header.php'); ?>
        </header>
        <!-- Body Section -->
        <div class="content mx-auto" id="contents">
        <div class="row mx-auto" style="padding-left: 0px; height: auto;">
        <div class="col-sm-12">
                <h3 class="text-center">Search for a word or phrase in the quotes</h3>
                <form id="search" action="quotes_found.php" method="post">
                <div class="form-group row">
                <label for="target" class="col-sm-4 col-form-label text-right">Enter a word or phrase:</label>
                <div class="col-sm-4">
                        <input type="text" class="form-control" id="target" name="target"
                                placeholder="Word or Phrase" maxlength="60" size="40" required
                                value=
                                        "<?php if (isset($_POST['target']))
                                echo htmlspecialchars($_POST['target'], ENT_QUOTES); ?>" >
                </div>
                 </div>
                <div class="form-group row">
                        <label for="" class="col-sm-2 col-form-label"></label>
                <div class="col-sm-8 text-center">
                        <input id="submit" class="btn btn-primary" type="submit"
                                name="submit" value="Search">
                </div>
        </div>
</form>
</div>
</div>
<footer class="jumbotron row mx-auto" id="includefooter"
        style="padding-bottom:1px; margin: 0px; padding-top:8px; background-color:white;">
        <div class="col-sm-12 text-center">
                <?php include('includes/footer.php'); ?>
  </div>
</footer>
</div>
</div>
</body>
</html>

Listing 10-11Creating the Search Form (search.php)

搜索表单将搜索词传递给显示搜索结果的页面。

显示搜索结果

图 10-12 显示了全文搜索如何显示结果。

img/314857_2_En_10_Fig12_HTML.jpg

图 10-12

显示对单词 Mark Twain 进行全文搜索的结果

请注意,马克·吐温的引用次数不到我们论坛表中所有引用次数的 50%。如果引用马克·吐温的次数超过引用次数的 50%,就不会显示任何结果。在我们的示例数据库表中,有 23 个报价。马克·吐温被引用了三次;因此,他占总数的 13%。因为这不到总数的 50 %,他将被找到并展示。如果表中只有六条引文,马克·吐温将被引用 50%,因此他将找不到。换句话说,组词马克·吐温将被视为停用词,如他的她的以及你的。如果您知道某个词或短语存在于数据库表中,但全文搜索未能找到它,则可能是表中没有足够的引号,或者至少 50%的引号包含要搜索的词或短语。清单 10-12a 给出了 quotes_found 页面的代码。

<?php
// Start the session.
session_start() ;
// Redirect if not logged in.
if ( !isset( $_SESSION[ 'member_id' ] ) )
        { header("Location: login.php");
        exit(); }
$menu = 4;
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <title>Search Page</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS File -->
        <link rel="stylesheet"
                href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
                integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
                crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="msgboard.css">
</head>
<body>
<div class="container" style="margin-top:30px;border: 2px black solid;">
<!-- Header Section -->
        <header class="jumbotron text-center row" id="includeheader"
                style="margin-bottom:2px; background:linear-gradient(#0073e6,white); padding:10px;">
                        <?php include('includes/header.php'); ?>
        </header>
<div id="content"><!--Start of the quotes found page content-->
<?php
        // Connect to the database
        require ( 'mysqli_connect.php' ) ;
        //if POST is set                                                                 #1
        if($_SERVER['REQUEST_METHOD'] == 'POST' ) {
                $target = filter_var( $_POST['target'], FILTER_SANITIZE_STRING);
                // Make the full text query                                              #2
                $query = "SELECT user_name,post_date,subject,message FROM forum WHERE ";
                $query .= "MATCH (message) AGAINST ( ? ) ORDER BY post_date ASC";
                $q = mysqli_stmt_init($dbcon);
                mysqli_stmt_prepare($q, $query);
                // bind $id to SQL Statement
                mysqli_stmt_bind_param($q, "s", $target);
                // execute query
                mysqli_stmt_execute($q);
                $result = mysqli_stmt_get_result($q);
                if (mysqli_num_rows($result) > 0) {
                        echo '<h4 class="text-center">Full Text Search Results</h2>';
                        ?>
                        <table class="table table-responsive table-striped col-sm-12"
                                style="background: white;color:black; padding-left: 50px;">
                                <tr>
                                        <th scope="col">Posted By</th>
                                        <th scope="col">Forum</th>
                                        <th scope="col">Quotation</th>
                                </tr>
                        <?php
                        while ( $row = mysqli_fetch_array( $result, MYSQLI_ASSOC ))
                        {
                                $user_name = htmlspecialchars($row['user_name'], ENT_QUOTES);
                                $subject = htmlspecialchars($row['subject'], ENT_QUOTES);
                                $message = htmlspecialchars($row['message'], ENT_QUOTES);
                                echo '<tr>
                                        <td scope="row">' . $user_name . '</td>
                                        <td scope="row">' . $subject . '</td>
                                        <td scope="row">' . $message . '</td>
                                </tr>';
                        }
                        echo '</table>' ;
                }
                else { echo '<p class="text-center">There are currently no messages.</p>' ; }
        mysqli_close( $dbcon ) ;
        }
?>
</div><!--End of the quotes found page content.-->
<footer class="jumbotron row mx-auto" id="includefooter"
        style="padding-bottom:1px; margin: 0px; padding-top:8px; background-color:white;">
        <div class="col-sm-12 text-center">
                <?php include('includes/footer.php'); ?>
        </div>
</footer>
</div>
</body>
</html>

Listing 10-12aCreating the Search Results Page (quotes_found.php)

注意

如果您运行该文件并看到错误消息“警告:mysqli_num_rows()期望参数 1 是 mysqli_result,布尔值在…中给定”,您可能忘记选择全文作为表中消息列的索引。

代码的解释

本节解释代码。

//if POST is set                                                                          #1
if($_SERVER['REQUEST_METHOD'] == 'POST' ) {
        $target = filter_var( $_POST['target'], FILTER_SANITIZE_STRING);

搜索表单将目标单词或短语(例如马克·吐温)发送到这个页面,在这里它被赋给变量$target。

// Make the full text query                                                               #2
$query = "SELECT user_name,post_date,subject,message FROM forum WHERE ";
$query .= "MATCH (message) AGAINST ( ? ) ORDER BY post_date ASC";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind $id to SQL Statement
mysqli_stmt_bind_param($q, "s", $target);

全文查询在 forum 表中搜索包含存储在变量$target 中的单词 Mark Twain 的消息(引文),该变量绑定到 prepare 语句。注意括号、逗号、双引号和单引号;它们很重要。全文搜索查询的格式如下:

SELECT list of items FROM some table WHERE MATCH (the column) AGAINST(the search words) ;

关键字 MATCH 和 AGAINST 是标准选择查询和全文搜索查询之间的主要区别。

quotes_found 页面的标题

清单 10-12b 给出了头部的代码块。这也是 forum.php 使用的相同标题。

case 4: //forum.php //quotes_found.php
            ?>
                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'index.php?name=Quick Quotes'">
                        Home Page
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "" >Login
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'logout.php?name=Logout'">
Logout
                </button>

                <button type="button" style="width: 110px;" class="btn btn-secondary bg-primary disabled"
                        onclick="location.href = "" >Register
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'view_posts.php?name=Your Quotes'" >
                        Your Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'post.php?name=Add A Quote'">
                        Add A Quote
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'forum_c.php?name=Comic Quotes'" >
                        Comic Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'forum_w.php?name=Wise Quotes'">
                        Wise Quotes
                </button>

                <button type="button" style="width: 120px;" class="btn btn-secondary bg-primary"
                        onclick="location.href = 'search.php?name=Search Quotes'" >Search Quotes
                </button>
        <?php
        break;

Listing 10-12bCreating the Header Block for the Quotes Found Page (header.php)

增强留言板

为了不熟悉数据库的读者的利益,本章中的留言板被大大简化了。通过使用您从前面章节中获得的知识,可以添加如下增强功能:

  • 用于显示两类报价的页面可以显示分页的结果。(查看第章第五章和第章第八章,了解如何完成此操作。)

  • 成员可能想要更改他们的密码。(查看第三章了解详情。)

  • 成员可能忘记了他们的密码;可以向他们发送新密码。这包括在第十一章中。

将留言板转换为论坛

在这一章的开始,我们说过我们将简要描述一个将留言板转换成论坛的结构。一个论坛需要最大的规范化和原子性。(参见第九章,提醒您这些术语的定义。)需要更多的表,其中几个将通过包含关键字 JOIN 的查询来链接。

论坛需要额外的表格来放置主题和回复。留言板由管理员审核,结果是自动发送一封包含发帖人姓名和日期的电子邮件。当报价被提交时,它被立即插入到数据库表中;更好的解决方案是创建一个额外的列(approved ),该列必须由管理员设置,以允许向成员显示报价。

如果管理员能够在不使用 phpMyAdmin 的情况下删除或编辑报价,那将会很有帮助。管理页面可以显示包含编辑和删除链接的最近帖子的表格,如第三章所述。

一个论坛最少需要四张桌子,如图 10-13 所示。

img/314857_2_En_10_Fig13_HTML.jpg

图 10-13

一个非常基本的论坛的表格

摘要

在这一章中,我们学习了基本留言板的计划和结构。我们创建了一个注册页面和登录表单。我们开发了一个两页报价的网关,然后创建了这些页面。我们学习了如何创建发布消息的表单。我们创建了两个搜索表单:一个允许成员搜索他们自己的帖子列表,另一个用于进行全文搜索。建议对基本留言板进行一些改进,最后提供了一个基本论坛的简要概述。下一章描述一个基本的电子商务网站。

十一、电子商务简介

电子商务网站接受支付以换取商品或服务。用户订购网站目录页面中显示的一系列商品或服务。用户订单的详细信息存储在网站的数据库中。金钱从用户手中转移到网站所有者手中。这是通过网站内置的支付系统实现的。最后,订购的商品被交付给用户。用户可以跟踪他们的订单进度,更新他们的帐户详细信息,并在必要时联系客户支持部门。

完成本章后,您将能够

  • 设计一个简单的电子商务网站

  • 创建一个与贝宝接口的网站

  • 创建一个包含简单购物车的网站

  • 为用户提供恢复密码的能力

两种类型的电子商务网站将从使用数据库中受益:一种是拥有广泛商品或服务的网上商店,如亚马逊,另一种是拥有有限商品范围但打算扩大范围的网上商店。本章对电子商务网站的简要概述就是基于后者。

注意

这一章简要演示了电子商务网站的基本格式。为了使基本原则尽可能清楚并节省空间,不包括替代货币。当根据你的领域调整网站时,你自然会改变内容和货币来适应。正如我们之前提到的,向多个国家销售商品和服务时,您必须确保遵守每个国家的在线销售相关法律。

本章描述了两种类型的购物车,贝宝购物车和自定义购物车。每个示例都是单独处理的,并且包括自己的数据库、表和文件。以下是本章的两个主要部分:

  • 创建一个贝宝购物车网站

    • PayPal 购物车主页

    • 管理 PayPal 购物车网站

  • 创建自定义购物车网站

    • 自定义购物车主页

    • 管理自定义购物车网站

    • 结账页面

我们将在本章中讨论几个重要的过程。然而,我们不会涵盖电子商务网站所需的每个流程。这个大纲将为你进一步学习提供一个起点。本章包含的代码很少,因为所描述的网站结合了前面章节中已经展示和解释的大部分代码。

例如:

  • 注册页面类似于第六章中描述的注册页面。

  • 第三章和第八章描述了大多数管理设施。

  • 第九章描述了多个表格的使用。

  • 使用 Bootstrap 创建可以在任何大小的设备上显示的网站贯穿全书。

登录和注销页面在前面的几章中已经详细介绍过了。但是,本章中的登录页面包含一个链接,使用户能够找回忘记的密码;代码就是为这个特性提供的。

注意

本章是对一个电子商务网站的简单介绍。完整地处理一个电子商务网站的实例,包括支付网关、库存控制系统、订单跟踪、安全措施、发票和客户服务,需要一整本书。你可以在附录 b 中找到设计和开发电子商务网站的资源列表。

我们想花时间感谢罗杰·圣巴尔贝提供的彩色蚀刻图像。他制作蚀刻画的方法令人着迷;在他的网站上可以找到这一过程的简要记录:

www.dolphin-gallery.co.uk

尽管我们声明每件产品都是独一无二的,但罗杰确实为每件蚀刻作品制作了数量有限的签名副本。为了简单起见,在我们的例子中,Dove Gallery 将只存储一个副本。阿德里安·韦斯特拥有本章中使用的其他画作。艺术家詹姆斯·凯塞尔(现已去世)是皇家艺术学院双料艺术家(伦敦和英国伯明翰)。

注意

PayPal 购物车和自定义购物车部分的可下载文件包含在单独的子文件夹中。 paypaldb.sqlcustomdb.sql 文件用于安装 PayPal 和自定义数据库和表格。主文件夹可以从地址下载。com 网站。

我们开始吧。对于这两种购物车来说,最重要的两个主题是安全性和仔细规划。

安全警告

数据库驱动的电子商务网站非常容易受到攻击;因此,安全性是首要考虑的问题。通过注意以下警告,电子商务网站可以相当安全。

  • 开发人员不应该试图推出一个电子商务网站,除非他们在 PHP、SQL 和 Maria/MySQL 数据库方面达到了很高的专业水平。

  • 对 PHP 存储过程事务的全面了解对于电子商务来说是必不可少的。

  • 开发人员必须彻底了解可能发生的固有安全问题和风险。

  • 开发人员必须了解并完全遵守所在地区或国家的数据保护法,尤其是管理在线交易的法律。

  • 网站不应该存储客户的银行和信用卡信息,除非有一个昂贵而有效的安全方案。

  • 使用 PayPal、Stripe 或 Authorize 等安全支付系统更安全。网。总是选择他们的购物车按钮的编码版本。使用这些支付系统之一来收集信用卡和其他敏感信息。不要在您的电子商务数据库中收集或存储信息。

  • 使用受安全套接字层(SSL)保护的 HTTPS 页面。

现在让我们来看看创建电子商务网站的计划。

计划

通常,设计数据库的第一步意味着讨论网站所有者的需求,然后制定一个计划来满足这些需求。与客户会面后,我们确定了以下要求:

  • 这些网站将出售原画和彩色蚀刻画。

  • 管理员将能够通过用户友好的界面从数据库表中添加或删除绘画和艺术家。

  • 一个基本的电子商务数据库会使用许多表,通常是 12 个或更多,但在第一阶段(我们将在本章中介绍),我们的网站将使用下列表:

    • 用户:该表包含管理员和注册用户。该表将包括用户的地址,但为了简洁起见,它将不包括替代的递送地址。

    • 艺术:这个表格包含了对绘画的描述,并且将被用来显示绘画库存的目录。

    • 艺术家:此表包含艺术家的姓名。然而,为了节省空间,它不会包含艺术家的传统简要描述。

    • 订单和订单处理(仅限定制购物车):这些表格将记录并跟踪订单详情,如定制购物车部分所示。

注意

我们假设在 PayPal 网站的规划阶段,所有者决定不要求用户注册,除非他们希望收到电子邮件更新,这将为美术馆提供特别优惠或新的收购。关于定制购物车网站,我们已经决定纳入预先注册。另一种方法是在结帐阶段进行注册。

创建 PayPal 购物车网站

在本节中,我们将创建一个使用 PayPal 处理所有交易的电子商务网站。让我们从建立数据库、表和文件开始。

创建 PayPal 购物车数据库和表格

请遵循以下步骤:

  1. 在 XAMPP 的 htdocs 文件夹或 EasyPHP 文件夹 eds_www 中,新建一个名为 paypalcart 的文件夹。

  2. 出版社的页面下载第 11 章的 paypalcart 文件。并将它们解压到你新的 paypalcart 文件夹中。

  3. 启动 XAMPP 或 EasyPHP,在浏览器的地址栏中输入 localhost/phpmyadmin/以访问 phpmyadmin。

  4. 单击 Databases 选项卡,创建一个名为 paypaldb 的数据库。从下拉排序规则列表中,选择 utf8_general_ci,然后单击创建。

  5. 单击权限选项卡,然后向下滚动并单击添加新用户。

  6. 输入这些详细信息:

    • 用户名:巨像

    • 密码:fstab 0 computer 3r

    • 主机:本地主机

    • 数据库名称 : paypaldb

  7. 单击开始。

查看连接文件

连接文件的代码如下:

<?php
// Create a connection to the migrate database and to MySQL
// Set the encoding to utf-8
// Set the database access details as constants
Define ('DB_USER', 'colossus');
Define ('DB_PASSWORD', 'Fstc0mput3r');
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'paypaldb');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');

要测试您的连接文件,请在浏览器的地址栏中输入以下 URL:

       http://localhost/paypalcart/mysqli_connect.php

显示的页面应该完全是空的。

推广 paypal 购物车表

使用 phpMyAdmin 导入表并填充它们。

  1. 在 phpMyAdmin 的左侧面板中,单击数据库 paypaldb 旁边的框。

  2. 不要在下一个屏幕上输入任何内容,而是单击 Import 选项卡。

  3. 点击浏览按钮并导航至 paypalcart 文件夹。

  4. 点击打开按钮,导入 paypaldb.sql 转储文件;该字段将填充文件的位置。

  5. 确保下拉菜单中的字符集是 utf-8,并且格式显示为 SQL。

  6. 单击开始。

出于您的兴趣,表 11-1 给出了 PayPal 购物车艺术表的列细节。所有的表都使用 InnoDB 存储引擎。

表 11-1

艺术表格包括一个贝宝支付代码栏

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

阿奇

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 艺术标识 | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 拇指 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 类型 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 价格 | 小数 | 6,2 | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |
| 媒介 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 艺术家 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 迷你描述 | 可变长字符串 | One hundred and fifty | 没有人 |   | -是吗 |   | -是吗 |
| ppcode | 文本 | Five hundred | 没有人 |   | -是吗 |   | -是吗 |

注意:除 PayPal 之外的支付系统可能要求在最后一栏输入不同的值。

名字和中间名被设置为空条目,因为一些艺术家可能很少使用这些名字,如毕加索。艺术家表如表 11-2 所示。

表 11-4

最小用户表的类型和属性如表 11-5 所示

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 用户 id | 中位 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 标题 | 可变长字符串 | Twelve | 没有人 |   | ·······················。 |   | -是吗 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 注册日期 | DATETIME |   |   |   | -是吗 |   | -是吗 |
| 用户级别 | 蒂尼因特 | one | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 地址 1 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 地址 2 | 可变长字符串 | Fifty | 没有人 |   | ·······················。 |   | -是吗 |
| 城市 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 州 _ 国家 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| zcode _ pcode 函数 | 可变长字符串 | Ten | 没有人 |   | -是吗 |   | -是吗 |
| 电话 | 可变长字符串 | Fifteen | 没有人 |   | ·······················。 |   | -是吗 |
| 秘密 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |

表 11-3

表 11-4 显示了如何填充艺术家表的详细信息

|

西方人名的第一个字

|

中名

|

|
| --- | --- | --- |
| 艾德里安(男子名) | W | 西 |
| 罗杰 | 街道 | 巴尔贝 |
| 詹姆斯 |   | 凯塞尔 |

表 11-2

艺术家桌

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

阿奇

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 艺术标识 | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | ·······················。 |   | -是吗 |
| 中间名 | 可变长字符串 | Thirty | 没有人 |   | ·······················。 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |

包含 user_level 列,使登录页面能够区分注册用户和管理员。用户的默认 user_level 为 0,管理员将获得一个更大的数字,该数字会将他们定向到管理页面。当任何其他用户访问 PayPal 购物车网站时,他们无需登录即可查看网站页面。包含了一个秘密栏,用于在用户忘记密码时提供附加信息。

注意

以下是用户的登录详细信息;您将需要 Mike Rosoft 先生的详细信息来登录管理页面并添加新的绘画。

迈克·罗软特先生,电子邮件:miker@myisp.com,密码:W111g@t3s,用户级别:1

罗斯·布什夫人,电子邮件:rbush@myisp.co.uk,密码:R@db100ms,用户级别:0

为了节省注释的空间,我们没有包括包含在 users.sql 文件中的所有信息。其他细节用于本示例贝宝购物车网站;因此,它们并不重要。

接下来讨论主页。

PayPal 购物车主页

PayPal 购物车网站的主页及其代码在 PayPal 购物车可下载文件中提供。他们是index.phptransparent.css

图 11-1 显示了 PayPal 购物车主页。

img/314857_2_En_11_Fig1_HTML.jpg

图 11-1

PayPal 购物车主页,也是一个搜索页面

用户无需登录或注册即可使用主页内置的搜索功能。买家运送画作的详细信息将由 PayPal 通过电子邮件提供给网站所有者。唯一必须注册和登录的人是管理员,以添加或删除绘画。但是,用户可以注册,如果他们想收到每月简讯。清单 11-1 给出了主页的代码。

<?php
$menu=7;
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>PayPal Cart Index Page</title>
  <meta charset="utf-8">
  <meta name="viewport"
  content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="transparent.css">
</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header class="jumbotron text-center row mx-auto" id="includeheader">
<?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto" style="padding-left: 20px; height: auto;">
<!-- Center Column Content Section -->
<div class="col-sm-6 text-center" style="padding:0px; margin-top: 5px;">
      <!--Start of found paintings content-->
      <form  action="found_paintings.php" method="post">
<div class="form-group row">
      <div class="col-sm-10 text-left"
             style="padding: 20px; padding-left: 30px;">
             <h3>Welcome to the Dove Gallery</h3>
             <h6 style="color: black;">
<b>All prices include frames, sales tax, delivery and insurance</b></h6>
             <h2 class="text-center">Search for a painting</h2>
      </div>
      <div class="col-sm-6">
</div>
</div>
      <div class="form-group row">
          <label for="type" class="col-sm-3 col-form-label text-right">
             Type:</label>
<div class="col-sm-6">
      <select id="type" name="type" class="form-control">
             <option selected value="">- Select -</option>
             <option value="still-life">Still Life</option>
             <option value="nature">Nature</option>
             <option value="abstract">Abstract</option>
</select>
</div>
</div>
       <div class="form-group row">
      <label for="price"
class="col-sm-3 col-form-label text-right">
Maximum Price:</label>
<div class="col-sm-6">
      <select id="price" name="price" class="form-control">
             <option selected value="">- Select -</option>
             <option value="40">&pound;40</option>
             <option value="80">&pound;80</option>
             <option value="800">&pound;800</option>
</select>
</div>
</div>
      <div class="form-group row">
             <label class="col-sm-3 col-form-label"></label>
      <div class="col-sm-6">
<input id="submit" class="btn btn-primary"
             type="submit" name="submit" value="Submit">
</div>
</div>
</form><!--End of the search content-->
</div>
<div class="col-sm-3 text-center"
style="padding:0px; margin-top: 5px;">
<img alt="Copper Kettle by James Kessell"
class="img-fluid float-left"
style="margin-top: 20px;"
src="img/k-copper-kettle-300.jpg">
</div>
<aside class="col-sm-3" id="includemenu">
             <?php include('includes/menu.php'); ?>
</aside>
</div>
<div class="form-group row">
<label class="col-sm-4"></label>
<div class="col-sm-8">
<footer class="jumbotron row" id="includefooter">
             <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</div>
</div>
</body>
</html>

Listing 11-1The PayPal Cart Home Page (index.php)

本页和本章中演示的其他页面的 HTML 和引导格式与您在其他章节中看到的类似。因此,我们不会重复每个页面的 HTML 代码。还要注意,我们使用前面章节中的一个头文件来处理所有页面。

使用 PayPal 购物车主页搜索画作

图 11-2 显示了搜索最高花费 40 的自然绘画的结果。

img/314857_2_En_11_Fig2_HTML.jpg

图 11-2

搜索成本不超过 40 英镑的自然绘画的结果

注意

除非您连接到互联网,否则 PayPal 按钮不会显示在搜索结果页面上。

可以从 Apress 网站下载并查看found _ paints . PHP文件的 HTML 代码。它类似于您之前看到的代码。清单 11-2 显示了显示找到的画作的 PHP 代码。

<?php
//Connect to the database
try {
require ( 'mysqli_connect.php' ) ;
// Select the first three items from the art table                                        #1
$type=$_SESSION['type'];
$price=$_SESSION['price'];
$query =
"SELECT art_id, thumb, type, price, medium, artist, mini_descr, ppcode ";
$query .=
"FROM art WHERE type= ? AND price <= ? ORDER BY price DESC LIMIT 3";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind $id to SQL Statement
mysqli_stmt_bind_param($q, "si", $type, $price);
// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
if (mysqli_num_rows($result) > 0) {
?>
<table class="table table-responsive table-striped"
style="background: white;">
<tr><th scope="col">Thumb</th>
<th scope="col"><b>Type</b></th>
<th scope="col"><b>Medium</b></th>
<th scope="col"><b>Artist</b></th>
<th scope="col"><b>Details</b></th>
<th scope="col"><b>Price &pound;</b></th>
<th scope="col"><b>Add to Cart</b></th>
</tr>
<?php
// Fetch the matching records and populate the table display                              #2
while
($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
             echo '<tr>
             <td><img src='.$row['thumb'] . '></td>
             <td>' . $row['type'] . '</td>
             <td>' . $row['medium'] . '</td>
             <td>' . $row['artist'] . '</td>
             <td>' . $row['mini_descr'] . '</td>
             <td>' . $row['price'] . '</td>
             <td>' . $row['ppcode'] . '</td>
             </tr>';
        }
?>
</table>
<?php
// Close the database connection.
       mysqli_close( $dbcon ) ;
}
// Or notify the user that no matching paintings were found
else {
echo '<p>There are currently no items matching your search
criteria.</p>' ; }
}
catch(Exception $e)
{
       print "The system is busy, please try later";
       $error_string = date('mdYhis') . " | Found Pics | " .
$e->getMessage() . "\n";
       error_log($error_string,3,"/logs/exception_log.log");
       //error_log("Exception in Found Pics Program.
// Check log for details", 1, "noone@nowhere.com",
       //        "Subject: Found Pics Exception" . "\r\n");
       // You can turn off display of errors in php.ini
// display_errors = Off
       //print "An Exception occurred. Message: " .
// $e->getMessage();
}catch(Error $e)
{
       print "The system is busy, please come back later";
       $error_string = date('mdYhis') . " | Found Pics | " .
$e->getMessage() . "\n";
       error_log($error_string,3,"/logs/error_log.log");
       //error_log("Error in Found Pics Program.
//Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Found Pics Error" . "\r\n");
       // You can turn off display of errors in php.ini
// display_errors = Off
      //print "An Error occurred. Message: " .
// $e->getMessage();
}
?>

Listing 11-2Code to Show the Results of a Search (process_found_pics.php)

代码的解释

本节解释代码。

// Select the first three items from the art table                                        #1
$type=$_SESSION['type'];
$price=$_SESSION['price'];
$query =
"SELECT art_id, thumb, type, price, medium, artist, mini_descr, ppcode ";
$query .=
"FROM art WHERE type= ? AND price <= ? ORDER BY price DESC LIMIT 3";

在本章的演示中,我们将艺术作品的显示限制为三个与用户选择的类型和价格相匹配的作品。我们这样做是为了减少本章的代码。在实际环境中,如本书前面所示,需要对代码进行调整以包含分页。

// Fetch the matching records and populate the table display                              #2
while
($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
             echo '<tr>
             <td><img src='.$row['thumb'] . '></td>
             <td>' . $row['type'] . '</td>
             <td>' . $row['medium'] . '</td>
             <td>' . $row['artist'] . '</td>
             <td>' . $row['mini_descr'] . '</td>
             <td>' . $row['price'] . '</td>
             <td>' . $row['ppcode'] . '</td>
             </tr>';

在创建的表格中,会显示 PayPal 代码(ppcode)。该代码将为用户提供向 PayPal 购物车添加商品的能力,如下所述。

与 PayPal 购物车按钮集成

PayPal 是安全的,为全球数百万人所熟悉。贝宝支付按钮是经过编码的,这样价格就不会被恶意顾客操纵。当产品付款后,PayPal 会向管理员发送一封电子邮件,其中包含以下内容:交易日期和 id、付款金额、买家的电子邮件地址、买家的送货地址以及所购商品的描述。

贝宝支付系统将不会详细描述,因为这将需要自己的一章(或两章)。完整的描述可能会过时,因为贝宝经常更新流程。我们没有给出详细的说明,而是提供了这个概述,以便您知道在 PayPal 网站上应该寻找什么。PayPal 和其他信用卡验证资源在其网站上提供了大量说明。附录 b 列出了两个有用的论坛。它们将帮助您了解如何配置 PayPal,并解决您在将购物车按钮与您的网站集成时可能遇到的问题。

要创建企业账户,请访问您所在地区或国家的 PayPal 网站(例如,美国的 www.paypal.com ,英国的 www.paypal.co.uk )。注册是免费的,并不难成立。然后,通过单击“业务”选项卡并使用搜索字段找到按钮和徽标所需的代码来访问您的业务帐户。

电子商务目录中的每个产品都需要其唯一的代码用于“添加到购物车”按钮;这些都可以使用您在 PayPal 网站上的帐户进行完全配置。然后可以将每个按钮的代码复制并粘贴到数据库的 art 表中。在我们的例子中,这可以通过将代码粘贴到管理员页面的 PayPal 文本区域来实现,如图 11-4 所示。

PayPal 提供多种信用卡/借记卡标志。这些标志告诉用户,即使他们没有 PayPal 账户,也可以进行支付。图 11-3 显示了两个标准的 PayPal 编码按钮和一个信用卡/借记卡标志的横版。

图 11-3 中的按钮不包含在可下载文件中,因为它们是用户在线时由 PayPal 代码生成的。

img/314857_2_En_11_Fig3_HTML.jpg

图 11-3

按钮和信用卡/借记卡标志的外观

注意

本章的可下载文件中使用了 PayPal 按钮的虚拟代码。你自己的贝宝按钮将是独一无二的,只适用于你自己的电子商务网站。PayPal 将生成代码,供您复制并粘贴到 admin_add_painting.php 文件中。

伪代码也放在可下载文件中,用于 Recaptcha 验证。您需要将此代码替换为您的 Recaptcha 详细信息。

添加到购物车按钮的典型代码将类似于清单 11-3a 中的示例。PayPal 可能会更改此代码,所以请确保从 PayPal 网站上复制正确的代码。为了安全起见,每个产品的 ID 值在这里被给定为 XXXXXXX,但是真实世界的按钮将有数字而不是 X s

<form target="paypal" action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input name="cmd" value="_s-xclick" type="hidden">
<input name="hosted_button_id" value="XXXXXXX" type="hidden">
<p><input src=
"https://www.paypal.com/en_GB/i/btn/btn_cart_LG.gif" name="submit"
alt="PayPal - The safer, easier way to pay online."
Style="float: left;" border="0" type="image"><img alt="" src="https://www.paypal.com/en_GB/i/scr/pixel.gif"
border="0" height="1" width="1"></p>
</form>

Listing 11-3aSample Code for a PayPal Add to Cart Button

我们还在商店的 found _ paintings 页面的标题中放置了一个 PayPal 查看购物车按钮(位于header.php)。查看购物车按钮的代码将类似于清单 11-3b 。如前所述,此代码的格式可以由贝宝更改。经常去 PayPal 网站下载最新的格式。

粗体显示的电子邮件地址将被网站所有者(或管理员)的电子邮件地址替换。订单确认后,PayPal 会通过电子邮件将详细信息发送到该地址。

<form name="_xclick" target="paypal"
action="https://www.paypal.com/uk/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_cart">
<input type="hidden" name="business" value="me@mybusiness.co.uk">
<input type="image" src="https://www.paypal.com/en_GB/i/btn/view_cart.gif" border="0" name="submit"
alt="Make payments with PayPal - it's fast, free and secure!">
<input type="hidden" name="display" value="1">
</form>

Listing 11-3bThe Code for a PayPal View Cart Button

警告

为您商店中的每件产品配置唯一的 PayPal 按钮时,配置必须包括价格、货币、所购商品的名称和产品识别号。最重要的是,请确保您配置了 PayPal 按钮,以便提示买家输入送货地址。贝宝按钮将自动编码。

现在让我们看看网站的其他页面。管理员将使用图 11-4 所示的添加绘画页面添加带有 PayPal 代码的绘画。

img/314857_2_En_11_Fig4_HTML.jpg

图 11-4

添加绘画的管理员界面和 PayPal 添加到购物车按钮

admin _ add _ paints . PHP站点的 HTML 代码类似于本书中显示的其他表单代码。因此,此处不包含。但是,您可以从 Apress 网站下载并在您的文本编辑器中查看它。表单被提交到 PHP 文件admin _ add _ paints . PHP。清单 11-4 提供了这个代码。

<?php
      $errors = array();
// Start an array to contain the error messages
// Check if a thumbnail url has been entered                                              #1
       $thumbtrim = trim($_POST['thumb']);
             if ((!empty($thumbtrim)) &&
(filter_var($thumbtrim, FILTER_VALIDATE_URL))
                          && (strlen($thumbtrim) <= 50)) {
                    // no changes
             }
             else
             {
             $errors[] =
'Missing thumbnail url or wrong format. Max 50.';
             }
// Check for a type                                                                       #2
       $typetrim = trim($_POST['type']);
       if ((!empty($typetrim)) &&
(preg_match('/[a-z\-\s\.]/i',$typetrim)) &&
                   (strlen($typetrim) <= 50)) {
       //Sanitize the trimmed type
       $typetrim = (filter_var($typetrim, FILTER_SANITIZE_STRING));
       }else{
$errors[] =
'Type missing or not alphabetic, -, period or space. Max 50.';
             }
// Has a price been entered?
$pricetrim = trim($_POST['price']);
if ((!empty($pricetrim)) && (strlen($pricetrim) <= 10)) {
       //Sanitize the trimmed price
              $pricetrim =
(filter_var($pricetrim, FILTER_SANITIZE_NUMBER_INT));
             $pricetrim = preg_replace('/\D+/', “, ($pricetrim));
       }else{
$errors[] =
'Price missing. Must be Numeric. Max ######.##.';
       }
// Has the medium been entered?
       $mediumtrim = trim($_POST['medium']);
       if ((!empty($mediumtrim)) &&
(preg_match('/[a-z\-\s\.]/i',$mediumtrim)) &&
                   (strlen($mediumtrim) <= 50)) {
      //Sanitize the trimmed medium
$mediumtrim = (filter_var($mediumtrim, FILTER_SANITIZE_STRING));
      }else{
$errors[] =
'Medium missing only alphabetic, -, period or space. Max 50.';
       }
       // Has the artist been entered?
       $artisttrim = trim($_POST['artist']);
       if ((!empty($artisttrim)) &&
(preg_match('/[a-z\-\s\.]/i',$artisttrim)) &&
                    (strlen($artisttrim) <= 50)) {
             //Sanitize the trimmed artist
$artisttrim = (filter_var($artisttrim, FILTER_SANITIZE_STRING));
      }else{
$errors[] =
'Artist missing or not alphabetic, -, period or space. Max 50.';
       }
// Has a brief description been entered?
       $minitrim = trim($_POST['mini_descr']);
       if ((!empty($minitrim)) &&
(preg_match('/[a-z\-\s\.]/i',$minitrim)) &&
                   (strlen($minitrim) <= 150)) {
       //Sanitize the trimmed artist
$minitrim = (filter_var($minitrim, FILTER_SANITIZE_STRING));
       }else{
       $errors[] =
'Description missing or not alphabetic, -, period or space. Max 50.';
       }
        // Has the PPcode been entered?
       $ppcodetrim = trim($_POST['ppcode']);
       if ((!empty($ppcodetrim)) &&
(strlen($ppcodetrim) <= 45)) {
       //Sanitize the trimmed ppcode
       $ppcodetrim =
(filter_var($minitrim, FILTER_SANITIZE_STRING));
}else{
             $errors[] =
'PayPal Code missing or not alphabetic, -, period or space. Max 50.';
      }
if (empty($errors)) { // If no errors were encountered
      // Register the painting in the database
try {
             require ('mysqli_connect.php');
// Connect to the database
             // Make the query:'
       $query = "INSERT INTO art” .
" (art_id, thumb, type, medium, artist, mini_descr, price, ppcode)";
       $query .= "VALUES ";
       $query .= "(' ', ?,?,?,?,?,?,?)";
       $q = mysqli_stmt_init($dbcon);
       mysqli_stmt_prepare($q, $query);
       // use prepared statement to ensure that only text is inserted
       // bind fields to SQL Statement
mysqli_stmt_bind_param($q, 'sssssss', $thumbtrim, $typetrim, $mediumtrim, $artisttrim, $minitrim, $pricetrim, $ppcodetrim );
       // execute query
       mysqli_stmt_execute($q);
       if (mysqli_stmt_affected_rows($q) == 1) {
              echo '<h2 style="margin-left: 60px;">
The painting was successfully registered</h2><br>';
       } else { // If it was not registered
              // Error message:
              echo '<h2>System Error</h2>
              <p class="error">
The painting could not be added due to a system
 error. We apologize for any inconvenience.</p>';
              // Debugging message:
              //echo '<p>' . mysqli_error($dbcon) .
// '<br><br>Query: ' . $q . '</p>';
              } // End of if ($result)
              mysqli_close($dbcon); // Close the database connection.
}
catch(Exception $e)
{
             print "The system is busy, please try later";
             $error_string = date('mdYhis') .
" | Add Painting | " . $e-getMessage() . "\n";
             error_log($error_string,3,"/logs/exception_log.log");
             //error_log("Exception in Add Painting Program. " .
//" Check log for details", 1, "noone@nowhere.com",
             //     "Subject: Add Painting Exception" . "\r\n");
             // You can turn off display of errors in php.ini
// display_errors = Off
             //print "An Exception occurred. Message: " .
//     $e->getMessage();
}
catch(Error $e)
{
             print "The system is busy, please come back later";
             $error_string = date('mdYhis') . " | Add Painting | " .
$e-getMessage() . "\n";
             error_log($error_string,3,"/logs/error_log.log");
             //error_log("Error in Add Painting Program. " .
//"Check log for details", 1, "noone@nowhere.com",
             //     "Subject: Add Painting Error" . "\r\n");
             // You can turn off display of errors in php.ini
// display_errors = Off
             //print "An Error occurred. Message: " .
// $e->getMessage();
}
} else { // Display the errors.
       echo '<h2>Error!</h2>
       <p class="error">The following error(s) occurred:<br>';
             foreach ($errors as $msg) { // Print each error.
                    echo " - $msg<br>\n";
             }
       echo '</p><h3>Please try again.</h3><p><br></p>';
}// End of if (empty($errors))
?>

Listing 11-4Code for Adding Paintings (admin_add_painting.php)

代码的解释

本节解释代码。

// Check if a thumbnail url has been entered                                              #1
       $thumbtrim = trim($_POST['thumb']);
             if ((!empty($thumbtrim)) &&
(filter_var($thumbtrim, FILTER_VALIDATE_URL))
                          && (strlen($thumbtrim) <= 50)) {
                    // no changes
             }
             else
             {
             $errors[] =
'Missing thumbnail url or wrong format. Max 50.';
             }
The validation for thumb uses the filter_var property FILTER_VALIDATE_URL to ensure that the location of the artwork picture is formatted correctly.
// Check for a type                                                                       #2
       $typetrim = trim($_POST['type']);
       if ((!empty($typetrim)) &&
(preg_match('/[a-z\-\s\.]/i',$typetrim)) &&
                    (strlen($typetrim) <= 50)) {
       //Sanitize the trimmed type
       $typetrim = (filter_var($typetrim, FILTER_SANITIZE_STRING));
             }else{
$errors[] =
'Type missing or not alphabetic, -, period or space. Max 50.';
             }

表单传递的其他值的验证包括空格的修整、使用正则表达式检查格式是否正确,以及使用 filter_var 进行清理。在本例中,正则表达式允许字母字符(a-z)、破折号(-)、空格(s)和句点(。).我们之前讨论过正则表达式。更多信息见附录 b。

创建 PayPal 购物车网站的大部分代码与我们在其他章节中使用的代码相似。因此,我们没有提供很多解释。提供的一些管理屏幕与定制购物车示例中包含的相同。我们将在下一部分查看这些屏幕。现在让我们创建自己的定制购物车。

创建自定义购物车

一些电子商务网站使用他们自己的定制购物车,这里我们指的是由网站开发者设计的购物车。定制购物车很复杂,需要几个交互式页面来取代在线 PayPal 系统。该过程最好用流程图来描述,如图 11-5 所示。

img/314857_2_En_11_Fig5_HTML.jpg

图 11-5

定制购物车的典型流程图

数字圆圈表示用户采取的步骤。登录(步骤 2)后,用户的 ID 保存在一个会话中,所有后续页面都可以访问该会话。检测用户级别,并将用户重定向到搜索页面(步骤 3);我们的例子有包含搜索标准的下拉菜单。用户选择标准并点击搜索按钮。然后,他们会被重定向到符合搜索条件的产品目录。如果用户决定购买一幅画,他们将点击添加到购物车按钮(步骤 4)。这将产品 ID 和用户 ID 发送到购物车;显示确认操作的消息(步骤 5)。显示屏上有一个继续购物链接和一个到结账页面的链接。单击继续购物链接,用户将返回到购物页面(步骤 4),在这里用户可以添加更多产品。如果用户单击标题菜单上的 View cart 按钮,他们将看到一个表格,显示他们选择的产品和价格。该表允许用户更改数量或从购物车中删除商品。用户走到收银台确认订单,然后订单被处理。让我们来看看如何创建这些功能。

自定义购物车主页

自定义购物车主页不包括搜索功能,因为自定义购物车有一个单独的搜索页面。我们要求用户登录购买任何绘画。为了鼓励观众注册,主页上展示了诱人的产品样品。图 11-6 显示主页。

img/314857_2_En_11_Fig6_HTML.jpg

图 11-6

customcart 主页

主页的代码及其 CSS 文件包含在可下载文件中,分别为index.phptransparent.css 。注意标题中的注册按钮和登录按钮。这向用户提供了一个提示,即他们必须登录才能购买产品。通过展示一些产品,鼓励用户注册。

创建自定义购物车数据库和表

自定义购物车示例使用自己的数据库、表和代码。要查看演示,请按照下列步骤操作:

  1. 在 XAMPP 的 htdocs 文件夹或者 EasyPHP 的 eds_www 文件夹中,新建一个名为 customcart 的文件夹。

  2. 出版社的页面下载第 11 章的定制购物车文件。并将它们解压缩到新的 customcart 文件夹中。

  3. 在 XAMPP 或 EasyPHP 中启动 Apache 和 MySQL,在浏览器的地址栏中输入 localhost/phpmyadmin/以访问 phpmyadmin。

  4. 单击 Databases 选项卡并创建一个名为 customdb 的数据库。从下拉排序规则列表中,选择 utf8_general_ci,然后单击创建。

  5. 单击权限选项卡,然后向下滚动并单击添加新用户。

  6. 输入这些详细信息:

    • 用户名:图灵

    • 密码:在 1gm3 中

    • 主机:本地主机

    • 数据库名称 : customdb

  7. 单击开始。

在查看连接文件的新标题下,代码应如下所示:

<?php
// Create a connection to the customdb database and to MySQL
// Set the encoding to utf-8
// Set the database access details as constants
DEFINE ('DB_USER', 'turing');
DEFINE ('DB_PASSWORD', 'En1gm3');
DEFINE ('DB_HOST', 'localhost');
DEFINE ('DB_NAME', 'customdb');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');

要测试您的连接文件,请在浏览器的地址栏中输入以下 URL:

       http://localhost/customcart/mysqli_connect.php

显示的页面应该完全是空的。

使用 phpMyAdmin 导入表并按如下方式填充它们:

  1. 在左侧面板中,单击数据库 customdb 旁边的框。

  2. 不要在下一个屏幕上输入任何内容,而是单击 Import 选项卡。

  3. 点击浏览按钮并导航至 customcart 文件夹。

  4. 单击 SQL 转储文件,然后单击“打开”按钮,逐个导入这些文件;该字段将填充转储文件的 URL。

  5. 确保下拉菜单中的字符集是 utf-8,并且格式显示为 SQL。

  6. 单击开始。对每个 SQL 转储文件重复步骤 4 到 6。

探索定制购物车

当 Apache 和 MariaDB/MySQL 在 XAMPP 或 EasyPHP 中运行时,在浏览器的地址栏中键入 localhost/customcart/ ,然后按照以下步骤操作:

  1. 使用电子邮件地址 rbush@myisp.co.uk 和密码 R @ dbl00ms 以 Rose Bush 的身份登录。

  2. 选择项目和价格范围,然后单击“提交”按钮。

  3. 当搜索结果页面出现时,单击其中一个添加到购物车链接。

  4. 单击查看购物车菜单按钮,然后尝试将数量从 1 更改为 2;然后点击更新购物车按钮。

  5. 在购物车视图中,单击继续购物链接,将另一幅画添加到购物车中,然后单击标题菜单上的查看购物车按钮。将显示购物车的全部内容。

  6. 要从购物车视图中删除一幅画,请将其数量更改为零,然后单击更新购物车链接。

下面显示了由 SQL 转储文件和文件清单创建的五个 customcart 表,供您参考。

最小艺术家表有四列,如表 11-5 所示。

表 11-5

艺术家桌

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 艺术的 | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 中间名 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |

绘画的细节存储在一个名为 art 的七列表中,如表 11-6 所示。

表 11-6

艺术桌

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 艺术标识 | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 拇指 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 类型 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 媒介 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 艺术家 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 迷你 _ 描述 | 可变长字符串 | One hundred and fifty | 没有人 |   | -是吗 |   | -是吗 |
| 价格 | 小数 | 6,2 | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |

名为 thumb 的列用于包含绘画缩略图的 URL。description 列包含绘画的名称、大小和一些关于图片的信息,例如油画是在画布上还是在板上画的。一个真实世界的艺术表格也可能包括一个放大版本的绘画的 URL 的列。请注意,可下载的表格没有 PayPal 购物车按钮代码栏。

orders 表是最小的,只有四列:订单 ID、用户 ID、价格和订单日期。如表 11-7 所示。

表 11-7

订单表

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 订单 id | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 用户 id | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |
| 总价 | 小数 | 7,2 | 没有人 |   | -是吗 |   | -是吗 |
| 订单日期 | DATETIME |   | 没有人 |   | -是吗 | 指数 | -是吗 |
| 价格 | 小数 | 6,2 | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |

订单表需要能够连接到其他表。

一个名为 order_contents 的五列表格包含各种 id、价格、订购数量和发货日期,如表 11-8 所示。

表 11-8

最小订单内容表

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 内容标识 | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 订单 id | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |
| 艺术标识 | (同 Internationalorganizations)国际组织 | eight | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |
| 价格 | 小数 | 5,2 | 没有人 | 无符号的 | -是吗 | 指数 | -是吗 |
| 量 | (同 Internationalorganizations)国际组织 | four | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 派遣日期 | DATETIME | Sixty | 没有人 |   | -是吗 |   | -是吗 |

该表与其他表相连接。通常情况下,会有一个数量栏,为了完整起见,在表 11-8 中显示了这一点,但是如果这些画是原作(如我们的示例网站中所示),则每幅画只存在一个,在这种情况下,数量栏是不需要的。如果商店出售印刷品或墨盒,数量栏将是必不可少的。还将包括发送地址和特殊递送指示栏。

简单的 order 和 order_contents 表只是为了说明类似的表也是电子商务网站的一部分。在现实世界中,它们将被连接到其他表。

最后,定制购物车网站的用户表有 15 列,如表 11-9 所示。

表 11-9

最小用户表的类型和属性

|

列名

|

类型

|

长度/值

|

默认

|

属性

|

|

索引

|

A_I

|
| --- | --- | --- | --- | --- | --- | --- | --- |
| 用户 id | 中位 | eight | 没有人 | 无符号的 | -是吗 | 主要的 | ·······················。 |
| 标题 | 可变长字符串 | Twelve | 没有人 |   | ·······················。 |   | -是吗 |
| 名字 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| 姓氏 | 可变长字符串 | Forty | 没有人 |   | -是吗 |   | -是吗 |
| 电子邮件 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 密码 | 茶 | Sixty | 没有人 |   | -是吗 |   | -是吗 |
| 注册日期 | DATETIME |   | 没有人 |   | -是吗 |   | -是吗 |
| 用户级别 | 蒂尼因特 | Two | 没有人 | 无符号的 | -是吗 |   | -是吗 |
| 地址 1 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 地址 2 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 城市 | 可变长字符串 | Fifty | 没有人 |   | -是吗 |   | -是吗 |
| 州 _ 国家 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |
| zcode _ pcode 函数 | 可变长字符串 | Ten | 没有人 |   | -是吗 |   | -是吗 |
| 电话 | 可变长字符串 | Fifteen | 没有人 |   | ·······················。 |   | -是吗 |
| 秘密 | 可变长字符串 | Thirty | 没有人 |   | -是吗 |   | -是吗 |

包含 user_level 列,使登录页面能够区分注册用户和管理员。用户的 user_level 为 0,但管理员的编号为 1。我们将很快看到秘密栏将被用来帮助用户恢复他们的密码。

可下载文件 users.sql 包含表 11-9 中所示的两个注册。这些用户的详细信息将在您在本章后面探索定制购物车的工作方式时用到。

注意

以下是用户的登录详细信息:

迈克·罗索夫先生,电子邮件:miker@myisp.com,密码:W111g@t3s,用户级别:51

罗斯·布什夫人,电子邮件:rbush@myisp.co.uk,密码:R@db100ms,用户级别:0

为了在上面的注释中节省空间,我们没有显示包含在表的 user.sql 文件中的附加信息。我们给了布什夫人和 Rosoft 先生相同的邮政地址,因为他们是同一个人。在这个自定义购物车网站的示例中,不会再次访问其他详细信息;因此,它们并不重要。

自定义购物车登录页面

图 11-7 显示了 login.php 的页面。

img/314857_2_En_11_Fig7_HTML.jpg

图 11-7

customcart 和 PayPal 登录页面

注意页面有忘记密码?链接。让我们看看 HTML 代码。PHP 代码( process_login.php )与我们在前面章节中看到的相同。你可以从网站下载的文件中查看。

清单 11-7 显示了 login.php 页面的代码。

<?php
$menu = 7;
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//require("cap.php");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Template for an interactive web page</title>
  <meta charset="utf-8">
  <meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS File -->
  <link rel="stylesheet"
  href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
  integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
  crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="transparent.css">
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header class="jumbotron text-center row mx-auto"
id="includeheader">
<?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto"
style="padding-left: 0px; height: auto;">
<!-- Center Column Content Section -->
<div class="col-sm-8 text-center"
style="padding:0px; margin-top: 5px;">
<!--Start of login content-->
<?php
// Display any error messages if present.
if ( isset( $errors ) && !empty( $errors ) )
{
       echo '<p id="err_msg">A problem occurred:<br>' ;
       foreach ( $errors as $msg ) { echo " - $msg<br>" ; }
             echo 'Please try again or ' .
'<a href="register-page.php">Register</a></p>' ;
}
?>
<!-- Display the login form fields -->
<form  action="process_login.php" method="post">
<div class="form-group row">
    <label class="col-sm-4 col-form-label"></label>
    <div class="col-sm-8">
<h2 style="margin-top: 10px;">Login</h2>
</div>
</div>
<div class="form-group row">
    <label for="email"
class="col-sm-4 col-form-label text-right">
E-mail:</label>
    <div class="col-sm-8">
      <input type="text" class="form-control" id="email"
name="email"
             placeholder="E-mail" maxlength="30" required
value=
             "<?php if (isset($_POST['email']))
             echo htmlspecialchars($_POST['email'], ENT_QUOTES); ?>" >
required >
    </div>
       </div>
<div class="form-group row">
    <label for="passcode"
class="col-sm-4 col-form-label text-right">
Password:</label>
<div class="col-sm-8">
      <input type="password" class="form-control"
id="passcode" name="passcode"
             placeholder="Password" minlength="8"
maxlength="12" required
value=
             "<?php if (isset($_POST['passcode']))
             echo htmlspecialchars($_POST['passcode'], ENT_QUOTES); ?>"
required >
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<div class="g-recaptcha" style="padding-left: 50px;"
data-sitekey="placeyourrecaptchasitekeyhere"></div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<input id="submit" class="btn btn-primary"
type="submit" name="submit" value="Submit">
</div>
</div>
<div class="form-group row">                                                       <!--#1-->
       <label class="col-sm-4 col-form-label"></label>
       <div class="col-sm-8">
       <a href="forgot.php">Forgot Password?</a>
</div>
</div>
<div class="form-group row">
      <label class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<footer class="jumbotron row" id="includefooter">
             <?php include('includes/footer.php'); ?>
</footer>
</div>
</div>
</form><!--End of the add a login content-->
</div>
<!-- Right-side Column Content Section -->
<aside class="col-sm-4" id="includemenu">
       <?php include('includes/menu.php'); ?>
       </aside>
</div>
</div>
</div>
</body>
</html>

Listing 11-7Creating the Login Page (login.php)

代码的解释

本章的下载文件中提供了登录和注销文件。登录页面的代码与本书之前的登录文件相同,只是包含了以下语句:

<div class="form-group row">                                                       <!--#1-->
       <label class="col-sm-4 col-form-label"></label>
       <div class="col-sm-8">
       <a href="forgot.php">Forgot Password?</a>
</div>
</div>

这只是一个指向名为forgot.php 的页面的链接。清单 11-8 显示的是forget . php 的代码。

找回忘记的密码

你可能想知道为什么我们这么晚才引入找回忘记的密码的方法。我们的推理如下:

  • 我们认为更复杂的 PHP 代码最好在以后的章节中介绍,当你对 PHP 的使用更加熟练的时候。

  • 在早期教程中,密码检索不是必需的,因为您没有与可能忘记密码的真实用户进行交互。

  • 我们假设您至少在到达第十章之前不会尝试将数据库驱动的网站迁移到外部主机。

  • 密码检索包括向用户发送电子邮件;除非您将网站上传到主机或在您的计算机上安装电子邮件服务器,否则无法测试这一点。

如果用户点击忘记密码?链接,他们将看到如图 11-8 所示的屏幕。

img/314857_2_En_11_Fig8_HTML.jpg

图 11-8

找回忘记的密码的显示

forgot.php文件的 HTML 代码类似于前面显示的其他代码。因此,此处不包含。你可以从 Apress 网站下载。然而,让我们看看process _ forget . PHP页面的 PHP 代码,如清单 11-8 所示。

<?php
try {
       require ('mysqli_connect.php');
       // Assign the value FALSE to the variable $buyid
       $buyid = FALSE;
// Validate the email address...
if (!empty($_POST['email'])) {
// Does that email address exist in the database?                                         #1
       $query =
'SELECT user_id, user_level, secret FROM users WHERE email=?';
       $q = mysqli_stmt_init($dbcon);
       mysqli_stmt_prepare($q, $query);
       // bind $id to SQL Statement
       $email = htmlspecialchars($_POST['email'], ENT_QUOTES);
       mysqli_stmt_bind_param($q, "s", $email);
       // execute query
       mysqli_stmt_execute($q);
       $result = mysqli_stmt_get_result($q);
       $row = mysqli_fetch_array($result, MYSQLI_NUM);
$secret = htmlspecialchars($_POST['secret'], ENT_QUOTES);
       if ((mysqli_num_rows($result) == 1) && ($row[1] == 0) &&                         //#2
              ($row[2] == $secret))
       {
             $buyid = $row[0];
       } else {
// If the buyid for the email address was not retrieved
      echo '<h6 style="padding-left:80px; padding-top: 20px;">
If your e-mail and secret are correct, you will receive an
e-mail</h6>';
      }
}
if ($buyid) {
// If buyid for the email address was retrieved,
// create a random password                                                               #3
      $password = substr ( md5(uniqid(random_int(), true)), 5, 10);
// Update the database table
        $hashed_password =
password_hash($password, PASSWORD_DEFAULT);
        $query = "UPDATE users SET password=? WHERE user_id=?";
        $q = mysqli_stmt_init($dbcon);
        mysqli_stmt_prepare($q, $query);
        // bind $id to SQL Statement
       mysqli_stmt_bind_param($q, "si", $hashed_password, $buyid);
       // execute query
        mysqli_stmt_execute($q);
        if (mysqli_stmt_affected_rows($q) == 1) {
// Send an email to the buyer                                                             #4
       $body = "Your password has been changed to '" . $password;
$body .= "'. Please login as soon as possible using the new password. ";
$body .= "Then change it immediately. otherwise, ";
$body .= "if a hacker has intercepted ";
$body .= "this email they will know your login details.";
      mail ($email, 'Your new password.', $body,
'From: admin@thedovegallery.co.uk');
// Echo a message and exit the code
echo '<h6 style="padding-left:80px; padding-top: 20px;">
Your password has been changed. ';
echo 'You will shortly receive the new temporary password ';
echo 'by email.</h6>';
       mysqli_close($dbcon);
       include ('includes/footer.php');
       exit(); // Stop the script.
} else { // If the query failed to run
       echo '<p class="error">Due to a system error, your password ';
  echo 'could not be changed. We apologize for any inconvenience. </p>';
}
}
mysqli_close($dbcon);
}
catch(Exception $e)
{
       print "The system is busy, please try later";
       $error_string = date('mdYhis') . " | Forgot Password | " .
$e-getMessage() . "\n";
       error_log($error_string,3,"/logs/exception_log.log");
       //error_log("Exception in Forgot Password Program.
// Check log for details", 1, "noone@nowhere.com",
       // "Subject: Forgot Password Exception" . "\r\n");
       // You can turn off display of errors in php.ini
// display_errors = Off
      //print "An Exception occurred. Message: " .
$e->getMessage();
}
catch(Error $e)
{
       print "The system is busy, please come back later";
       $error_string = date('mdYhis') . "| Forgot Password |".
$e-getMessage() . "\n";
       error_log($error_string,3,"/logs/error_log.log");
       //error_log("Error in Forgot Password Program.
// Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Forgot Password Error" . "\r\n");
       // You can turn off display of errors in php.ini
// display_errors = Off
       //print "An Error occurred. Message:".
// $e->getMessage();
}
?>

Listing 11-8Code for the Forgotten Password Page (process_forgot.php)

代码的解释

本节解释代码。

// Does that email address exist in the database?                                         #1
      $query =
'SELECT user_id, user_level, secret FROM users WHERE email=?';

请验证该电子邮件是否在数据库中。否则,错误消息将显示以下消息:

如果您的电子邮件和密码正确,您将会收到一封电子邮件

此消息不会告诉用户该电子邮件不存在,因为未经授权的用户会知道他们需要继续尝试其他电子邮件,直到他们发现一个有效的电子邮件。

if ((mysqli_num_rows($result) == 1) && ($row[1] == 0) &&                                //#2
             ($row[2] == $secret))
       {
             $buyid = $row[0];

如果电子邮件存在(返回了一行)并且 user_level 为 0(不是管理员,以便为管理 ID 提供额外的保护),并且返回的机密消息是正确的,那么将$buyid 设置为 user_id。

// create a random password                                                               #3
      $password = substr ( md5(uniqid(random_int(), true)), 5, 10);
// Update the database table
        $hashed_password =
password_hash($password, PASSWORD_DEFAULT);
        $query = "UPDATE users SET password=? WHERE user_id=?";

如果信息得到验证,则创建一个随机密码,对其进行哈希运算,并将其保存到表中的记录中。

// Send an email to the buyer                                                             #4
       $body = "Your password has been changed to '" . $password;
$body .= "'. Please login as soon as possible using the new password. ";
$body .= "Then change it immediately. otherwise, ";
$body .= "if a hacker has intercepted ";
$body .= "this email they will know your login details.";
       mail ($email, 'Your new password.', $body,
'From: admin@thedovegallery.co.uk');

创建一封包含登录信息(包括密码)的电子邮件。请记住,系统上必须有电子邮件服务器,才能发送电子邮件。

注意

注销的代码将与前几章相同。

自定义购物车搜索页面

搜索页面类似于首页贝宝购物车主页;它被重新命名为 search.php。图 11-9 显示搜索页面。

img/314857_2_En_11_Fig9_HTML.jpg

图 11-9

自定义购物车搜索页面(用户 _ 搜索 _ 页面)

当管理员登录时,他们将被定向到一个管理页面,这将在下面描述。

将绘画添加到自定义购物车的表格中

要将绘画添加到用于定制购物车的数据库表中,管理界面几乎与用于 PayPal 购物车的界面相同,但是省略了 PayPal 代码的文本区域。图 11-10 显示了新的界面。

img/314857_2_En_11_Fig10_HTML.jpg

图 11-10

自定义购物车管理页面(admin_page.php)

图 11-11 显示了搜索结果和添加到购物车链接。

img/314857_2_En_11_Fig11_HTML.jpg

图 11-11

显示绘画及其添加到购物车链接

因为购物车的 HTML 代码类似于 PayPal 购物车中显示的购物车,所以这里没有显示。不过你可以从你下载的文件中查看 found_pics_cart.php 文件了解详情。PHP 代码如清单 11-11 所示。

<?php
//Connect to the database
try {
require ( 'mysqli_connect.php' ) ;
// Select the first three items items from the art table
$type=$_SESSION['type'];
$price=$_SESSION['price'];
$query =
"SELECT art_id, thumb, type, price, medium, artist, mini_descr, ppcode";
$query .=
"FROM art WHERE type= ? AND price <= ? ORDER BY price DESC LIMIT 3";
$q = mysqli_stmt_init($dbcon);
mysqli_stmt_prepare($q, $query);
// bind $id to SQL Statement
$type = htmlspecialchars($type, ENT_QUOTES);
$price = htmlspecialchars($_POST['price'], ENT_QUOTES);
mysqli_stmt_bind_param($q, "si", $type, $price);
// execute query
mysqli_stmt_execute($q);
$result = mysqli_stmt_get_result($q);
if (mysqli_num_rows($result) > 0) {
// Table header
?>
<table class="table table-responsive table-striped"
style="background: white;">
<tr><th scope="col">Thumb</th><th scope="col">Type</th>
<th scope="col">Medium</th><th scope="col">Artist</th>
<th scope="col">Details</th><th scope="col">
Price &pound;</th>
</tr>
<?php
// Fetch the matching records and populate the table display                              #1
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
       echo '<tr>
       <td><img src='.$row['thumb'] . '></td>
       <td>' . $row['type'] . '</td>
       <td>' . $row['medium'] . '</td>
       <td>' . $row['artist'] . '</td>
       <td>' . $row['mini_descr'] . '</td>
       <td>' . $row['price'] .
       '<br><a href="added.php?id=' . $row['art_id'] .
'">Add to Cart</a></td>
      </tr>';
      }
?>
</table>
<?php
// Close the database connection.
  mysqli_close( $dbcon ) ;
}
// Or notify the user that no matching paintings were found
else {
echo '<p>There are currently no items matching your search '
echo 'criteria.</p>' ; }
}
catch(Exception $e)
{
       print "The system is busy, please try later";
       $error_string = date('mdYhis') . " | Found Pics | " .
$e->getMessage() . "\n";
       error_log($error_string,3,"/logs/exception_log.log");
       //error_log("Exception in Found Pics Program.
//Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Found Pics Exception" . "\r\n");
       // You can turn off display of errors in php.ini
//display_errors = Off
      //print "An Exception occurred. Message:".
//$e->getMessage();
}catch(Error $e)
{
       print "The system is busy, please come back later";
$error_string = date('mdYhis') . " | Found Pics | " .
$e->getMessage() . "\n";
       error_log($error_string,3,"/logs/error_log.log");
       //error_log("Error in Found Pics Program.
//Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Found Pics Error" . "\r\n");
       // You can turn off display of errors in php.ini
//display_errors = Off
       //print "An Error occurred. Message:".
//$e->getMessage();
}
?>

Listing 11-11Code for process_found_pics.php

代码的解释

本节解释代码。

// Fetch the matching records and populate the table display                              #1
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
       echo '<tr>
       <td><img src='.$row['thumb'] . '></td>
       <td>' . $row['type'] . '</td>
       <td>' . $row['medium'] . '</td>
       <td>' . $row['artist'] . '</td>
       <td>' . $row['mini_descr'] . '</td>
       <td>' . $row['price'] .
       '<br><a href="added.php?id=' . $row['art_id'] .
'">Add to Cart</a></td>
       </tr>';

自定义购物车代码和 PayPal 代码之间的唯一区别是购物车提供的链接。在自定义购物车代码中,链接被附加到added.php程序。在贝宝程序中,它会转到贝宝网站。这个定制的购物车链接还会将的 art_id 传递给 added.php

当一幅画被添加到购物车时,用户会得到通知,如图 11-12 所示。

img/314857_2_En_11_Fig12_HTML.jpg

图 11-12

确认一幅画已被添加到购物车

请注意确认消息下方的两个链接;用户可以继续购物以向购物车添加更多商品。他们可以选择通过单击标题菜单上的查看购物车链接来查看购物车内容。他们也可以使用第二个链接直接进入收银台。清单 11-12 给出了确认页面的代码。

<?php
session_start();
if (!isset($_SESSION['user_id'])){
header('location:login.php');
exit();
}
$menu = 2;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Added to Cart</title>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS File -->
<link rel="stylesheet"
      href=
"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
       integrity=
"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
       crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="transparent.css">
</head>
<body>
<div class="container" style="margin-top:10px">
<!-- Header Section -->
<header class="jumbotron text-center row mx-auto"
id="includeheader">
<?php include('includes/header.php'); ?>
</header>
<!-- Body Section -->
<div class="content mx-auto" id="contents">
<div class="row mx-auto"
style="padding-left: 0px;margin-top: -17px; width: 90%; height: auto;">
<!-- Center Column Content Section -->
<div class="col-sm-12 text-center"
style="padding:0px; margin-top: 5px;">
<div id="content"><!--Start of added page-->
<p>
<?php
if ( isset( $_GET['id'] ) ) { $id = $_GET['id'] ; }
// Connect to the database
try {
require ( 'mysqli_connect.php' ) ;
// Get selected painting data from the  'art' table
             $query = "SELECT * FROM art ";
             $query .= "WHERE art_id = ? ";
             $q = mysqli_stmt_init($dbcon);
             mysqli_stmt_prepare($q, $query);
             // bind $id to SQL Statement
             mysqli_stmt_bind_param($q, "i", $id);
             // execute query
             mysqli_stmt_execute($q);
             $result = mysqli_stmt_get_result($q);
             $row = mysqli_fetch_array($result, MYSQLI_ASSOC);
             if (mysqli_num_rows($result) == 1) {
 // If the cart already contains one of those products                                    #1
             if ( isset( $_SESSION['cart'][$id] ) )
             {
       // Add another one of those paintings
       $_SESSION['cart'][$id]['quantity']++;
echo '<h3>Another one of those paintings has been added';
echo 'to your cart</h3>';
             }
             else
             {
    // Add a different painting                                                           #2
             $_SESSION['cart'][$id]=
array ( 'quantity' => 1, 'price' => $row['price'] ) ;
      echo '<h3>A painting has been added to your cart</h3>';
             }
}
}
catch(Exception $e)
{
       print "The system is busy, please try later";
       $error_string = date('mdYhis') . " | Added  | " .
$e->getMessage() . "\n";
       error_log($error_string,3,"/logs/exception_log.log");
       //error_log("Exception in Added Program.
// Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Added Exception" . "\r\n");
       // You can turn off display of errors in php.ini
// display_errors = Off
       //print "An Exception occurred. Message: " .
//$e->getMessage();
}catch(Error $e)
{
       print "The system is busy, please come back later";
       $error_string = date('mdYhis') . " | Added | " .
$e->getMessage() . "\n";
       error_log($error_string,3,"/logs/error_log.log");
       //error_log("Error in Added Program.
//Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Added Error" . "\r\n");
       // You can turn off display of errors in php.ini
//display_errors = Off
       //print "An Error occurred. Message: " . $e->getMessage();
}
// Close the database connection
mysqli_close($dbcon);
// Insert three links
echo '<p><a href="users_search_page.php">
Continue Shopping</a> |
<a href="checkout.php">Checkout</a></p>' ;
?>
<footer>
<?php include("includes/footer.php"); ?>
</footer>
</div>
</div><!--End of page content-->
</div>
</div>
</div>
</body>
</html>

Listing 11-12Creating the Confirmation Page (added.php)

代码的解释

本节解释代码。

// If the cart already contains one of those products                                     #1
             if ( isset( $_SESSION['cart'][$id] ) )
             {
       // Add another one of those paintings
       $_SESSION['cart'][$id]['quantity']++;
echo '<h3>Another one of those paintings has been added';
echo 'to your cart</h3>';

检查所选绘画的细节以确定显示两个消息中的哪一个,或者“一幅绘画已被添加”或者“那些绘画中的另一幅已被添加”会话变量 cart 包含一个带有 id、数量和价格索引的三维数组。通俗地说,购物车包括由 ID(购买了什么)、数量(多少)和价格(每个)定义的每个订单。如果用户已经选择购买该画,则数量值增加({ ' 数量')++)1(这由 if 语句确定)。

// Add a different painting                                                               #2
             $_SESSION['cart'][$id]=
array ( 'quantity' => 1, 'price' => $row['price'] ) ;
       echo '<h3>A painting has been added to your cart</h3>' ;

如果以前没有选择过这幅画,那么将创建一个新的数组,数量设置为 1,价格来自艺术表中包含的值。

如果用户选择查看购物车,他们将看到显示的内容,如图 11-13 所示。

img/314857_2_En_11_Fig13_HTML.jpg

图 11-13

购物车里有一幅价值 750 英镑的静物画

用户可以编辑所显示表格第三列中的数量(白色部分)。为了从购物车中移除一幅画,用户将把当前数字变为零。在编辑数量之后,用户必须单击 Update My Cart 按钮来显示新的小计(顶行的最后一列)和修改后的总价。

用户可以点击继续购物链接选择另一幅画。让我们假设他们选择了另一幅价值 750 英镑的油画;当查看购物车时,他们将看到添加的绘画和新的总成本,如图 11-13 所示。

cart.php 的 HTML 代码可以从新闻网站下载。该代码类似于前面看到的 HTML 代码。让我们看看创建交互式购物车的 PHP 代码(清单 11-13 )。

<?php
// If the user changes the quantity then Update the cart
       if(isset($_POST['qty'])) {
       foreach ( $_POST['qty'] as $art_id => $item_qty )                                 //1
       {
       // Ensure that the id and the quantity are integers
       // PHP would convert for us. But other languages would not
       $id = (int) $art_id;
       $qty = (int) $item_qty;
       // If the quantity is set to zero clear the session or
       // else store the changed quantity
       if ( $qty == 0 ) { unset ($_SESSION['cart'][$id]); }
       elseif ( $qty > 0 ) {
             $_SESSION['cart'][$id]['quantity'] = $qty; }
       }
       }
       // Set an initial variable for the total cost
       $total = 0;
       // Display the cart content
       if (!empty($_SESSION['cart']))
       {
?>
<div class="row mx-auto" style="padding-left: 0px; height: auto;">
<!-- Center Column Content Section -->
<div class="col-sm-4 text-center mx-auto"
      style="padding-left: 40px;"></div>
<div class="col-sm-10 text-center mx-auto"
      style="color: black; padding:20px; margin-top: 5px;">
<?php
       try {
       // Connect to the database.
       require ('mysqli_connect.php');
       // Get the items from the art table and
       //insert them into the cart
       $q = "SELECT * FROM art WHERE art_id IN (";
       foreach (                                                                         //2
             $_SESSION['cart'] as $id => $value) { $q .= $id . ','; }
             $q = substr( $q, 0, -1 ) . ') ORDER BY art_id ASC';
             $result = mysqli_query ($dbcon, $q);
             // Create a form and a table
             echo '<form action="cart.php" method="post">';
             echo '<table class="table table-responsive table-striped"
             style=" color:black;">';
             echo '<tr><th scope="col">Medium</th><th scope="col">Type</th>';
             echo '<th scope="col">Quantity</th><th scope="col"></th>';
             echo '<th scope="col">Price</th></tr>';
             while ($row = mysqli_fetch_array ($result, MYSQLI_ASSOC))
             {
                    // Calculate the subtotals and the grand total                        #3
                    $subtotal = $_SESSION['cart'][$row['art_id']]['quantity'] *
                    $_SESSION['cart'][$row['art_id']]['price'];
                    $total += $subtotal;
                    // Display the table                                                  #4
                    echo "<tr> <td>{$row['type']}</td><td>Painting(s)</td>
                    <td><input type=\"text\" size=\"3\"
             name=\"qty[{$row['art_id']}]\"
             value=\"{$_SESSION['cart'][$row['art_id']]['quantity']}\"></td>
                    <td>at {$row['price']} each </td>
                    <td style=\"text-align:right\">".
                    number_format ($subtotal, 2)."</td></tr>";
             }
       // Close the database connection
       mysqli_close($dbcon);
       // Display the total
       echo ' <tr><td colspan="5"
       style="text-align:right">Total = '.
       number_format($total,2).'</td></tr></table>';
       echo '<input id="submit" class="btn btn-primary"
       type="submit" name="submit" value="Update Order"></form>';
}
catch(Exception $e)
{
       print "The system is busy, please try later";
       $error_string = date('mdYhis') . " | Cart | " .
              $e-getMessage() . "\n";
       error_log($error_string,3,"/logs/exception_log.log");
       //error_log("Exception in Cart Program.
       //Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Cart Exception" . "\r\n");
       // You can turn off display of errors in php.ini
       // display_errors = Off
       //print "An Exception occurred. Message: " .
       //$e->getMessage();
}
catch(Error $e)
{
       print "The system is busy, please come back later";
       $error_string = date('mdYhis') . " | Cart | " .
              $e-getMessage() . "\n";
       error_log($error_string,3,"/logs/error_log.log");
       //error_log("Error in Cart Program.
       //Check log for details", 1, "noone@nowhere.com",
       //    "Subject: Cart Error" . "\r\n");
       // You can turn off display of errors in php.ini
       //display_errors = Off
       //print "An Error occurred. Message:" .
       //$e->getMessage();
}
echo "</div>";
echo "</div>";
}
else
// Or display a message
{ echo
'<p style="padding: 60px;">Your cart is currently empty.</p>' ;
}
// Create some links
echo
'<p><a href="users_search_page.php">Continue Shopping</a>';
echo ' | <a href="checkout.php">Checkout</a>' ;
?>

Listing 11-13Code for the View Cart Page (process_cart.php)

代码的解释

清单中的注释解释了用于填充购物车的步骤。但是,有些项目需要更多的解释,如下所示:

   foreach ( $_POST['qty'] as $art_id => $item_qty )                                    //#1
The view cart process relies on sessions and some complex arrays. The function foreach is a special loop that works with arrays. In line #1 the item $_POST['qty'] is an array containing the quantity of a product. The symbol => does not mean equal to or greater than, it is an array operator that associates the item qty with the $art_id of the product.
$q = "SELECT * FROM art WHERE art_id IN (";
foreach (                                                                               //#2
$_SESSION['cart'] as $id => $value) { $q .= $id . ','; }
$q = substr( $q, 0, -1 ) . ') ORDER BY art_id ASC';

值被添加到这里显示的名为 cart 的会话数组中。这些值包括\(id,即\)art_id 和价格。

// Calculate the subtotals and the grand total                                            #3
       $subtotal = $_SESSION['cart'][$row['art_id']]['quantity'] *
             $_SESSION['cart'][$row['art_id']]['price'];
       $total += $subtotal;   }

购物车中每一行的数量和价格用于计算小计。然后,将每一行的小计加到总计中,得出到期总计。

// Display the table                                                                      #4
echo "<tr> <td>{$row['type']}</td><td>Painting(s)</td>
<td><input type=\"text\" size=\"3\"
name=\"qty[{$row['art_id']}]\" value=\"{$_SESSION['cart'][$row['art_id']]['quantity']}\"></td>
<td>at {$row['price']} each </td>
<td style=\"text-align:right\">".
number_format ($subtotal, 2)."</td></tr>";

会话数组用于将值插入到视图购物车表的单元格中。请注意,在格式化表格的代码中,双引号通过使用反斜杠进行转义。

注意

在现实世界的网站中,要达到必要的安全标准,需要使用两种技术:准备好的语句和事务。事务超出了本书的范围,但是您可以使用附录 b 中提供的资源来了解它们。

我们将简要提及结帐页面。

结账页面

当点击 checkout 链接时,会发生四件事。

  • 出现一个“谢谢”页面,其中也注明了订单号。

  • 订单详细信息会发布到订单内容表中。

  • 订单被输入到订单表中。

  • 购物车被清空,准备下次购物。

图 11-14 显示了“感谢”页面。

img/314857_2_En_11_Fig14_HTML.jpg

图 11-14

结帐“谢谢”页面

结帐页面根据当前日期和时间以及用户的 ID 号生成订单号。如果出现问题,这些信息足以帮助服务台查找正确的订单。结帐页面的代码包含在可下载的文件中。

额外的管理任务

本章前面介绍了添加艺术家和绘画的过程,但是管理员必须执行其他几项任务。编辑和删除艺术家和画作可以通过第三章描述的技术来实现。其他任务通常由管理团队承担;这些职责包括订单处理、运输、库存控制、财务控制和客户支持。所有这些活动都将记录在适当的数据库表格中。

摘要

在这一章中,你学习了电子商务网站所需的许多要素中的一些。然而,简洁的需要意味着我们必须限制元素的数量。因为一个实际的电子商务网站需要大量的文件,这一章的空间被用来描述用户看到的主要显示。这一章不是教程,而是对一个电子商务网站的简要描述,随后是 PayPal 和定制购物车的演示。

下载代码包括接受用户信息的安全方法。这些网站还被设计用于任何尺寸的设备,包括智能手机。

我们希望您能够理解电子商务网站的复杂性,以及加强安全性的必要性。我们也希望这一章能启发你探索附录 b 中给出的一些电子商务资源。

十二、简单看一下 Oracle MySQL 8

到目前为止,您可能会奇怪为什么我们没有推出 Oracle 的 MySQL 8 数据库服务器。我们首先想教你在 MariaDB 和 MySQL 的任何当前级别上都能工作的技术。在很大程度上,我们已经完成了这项任务。第二,如果你对 MariaDB 或 MySQL 5.7 满意,你可以不安装 MySQL 8。你可以跳过这一章,我们向你展示的一切都将会起作用。

完成本章后,您将能够

  • 了解 Oracle MySQL 8 的特性和工具

  • 安装 MySQL 8

  • 使用 MySQL Workbench 中包含的工具

  • 将数据库迁移到 MySQL 8

  • 在 MySQL 8 中创建和修改数据库

  • 在 MySQL 8 中创建用户 id

MySQL 8 有很多很强的特性,我们来看看升级的优势。您将看到版本 8 有一些以前版本中没有的新特性。

升级的优势

免费社区版和企业版中有以下改进:

  • MySQL 文档库:可以使用比 SQL 8 更老的基于 SQL 和非基于 SQL 的数据库。

  • SQL 用户角色:您可以基于传统的 IT 工作角色创建管理用户 id。

  • 改进的 SQL Workbench :目前没有兼容 MySQL 8 的 phpMyAdmin 版本。我们将在本章中介绍 MySQL 工作台的特性。

  • 改进的安全特性:两个版本都使用了改进的 openSSL 协议,即开源的安全套接字层(SSL)和传输层安全(TLS)协议,具有默认认证。我们将在本章的例子中介绍默认认证。

  • 密码历史:您可以维护密码历史信息。现在可以限制重复使用以前的密码了。

  • 密码哈希:新的 caching_sha2_password 认证可用。它实现了 SHA-256 密码哈希,并使用缓存来解决任何延迟问题。您会发现 PHP 7 目前不支持这种新的散列级别。

  • 可靠性提高:数据定义语言(DDL)语句变得安全可靠。元数据现在存储在事务数据字典中。

  • 改进的 InnoDB 数据库引擎:数据库引擎得到了改进。

  • 性能提升:性能最高比 MySQL 5.7 快两倍。

  • 新的 SQL 命令:锁定记录和表时,有 NOWAIT 和 SKIP 锁定选项。SQL 支持索引定义中带有 DESC 的降序索引。它不再被忽视。分组函数可以在包含 WITH ROLLUP 修饰符的 GROUP BY 查询中使用。ALTER TABLE 支持更好的列重命名(将列 old_name 重命名为 new_name)。

  • 更新的 SQL 命令 : SELECT 和 UNION 使用相同的语法。自然连接允许使用可选的 INNER 关键字(自然内部连接)。右深度连接不再需要括号(连接...加入...安大略...开)。您可以在查询两边使用括号(选择...联合选择...).顶级语句中现在允许联合的左手嵌套。这些更改遵循 SQL 标准。

  • 默认支持更大的字符集:两个版本都包含了更多的国家字符集,比如俄国的。默认字符集为 utf8mb4 ( https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-0.html#mysqld-8-0-0-charset )。

  • GIS 地理支持:两个版本都包括空间参考系统(SRS)。

  • 新增错误日志:两个版本都使用 MySQL 组件架构。

  • JSON 改进:有新命令,新功能,改进排序,部分更新。您可以使用 SQL 命令来访问 JSON 数据。数据被返回,就好像它是一个关系表。

以下改进仅在企业版中可用。它不是免费的,但是有一个 30 天的超时版本可以下载测试。

  • 备份和恢复:完整、增量、部分备份。这包括时间点恢复。备份压缩可用。

  • 高可用性:针对 InnoDB 集群的集成、本机和高可用性。

  • 加密:密钥生成、数字签名和其他功能。

  • 认证:与其他安全系统,包括微软 Active Directory 和 PAM。

  • 防火墙:防御数据库攻击,包括 SQL 注入。

  • 审计:基于策略的审计。

  • 监控:用于管理数据库基础设施。

  • 云服务:由甲骨文云提供支持。它支持 SaaS、PaaS 和 DBaaS 应用。

更多详情,请访问 https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html

截至本书出版时,MySQL 8 还没有集成到 XAMPP 或 easyPHP 中。这些工具的创造者选择支持 MySQL 和 MariaDB 的一些原始开发者。但是,您现在会发现,您可以在您的平台上运行两个版本的 MySQL (5.7 和 8.11)。让我们从安装社区服务器版本开始。

安装 MySQL 8 社区服务器

完成以下步骤来安装 Oracle 的 MySQL 8 社区服务器:

  1. 前往 https://dev.mysql.com/downloads/mysql/

  2. 向下滚动到正式发布(GA)区域(参见图 12-1 )。

img/314857_2_En_12_Fig1_HTML.jpg

图 12-1

MySQL 安装程序

MySQL 有一个适用于当今许多操作系统的版本。如果您没有使用 Windows,您可以使用图 12-1 顶部所示的下拉框选择正确的版本。本演示将安装 Windows 版本,但其他版本的安装过程类似。

  1. 单击转到下载页面按钮。我们选择的版本包括所有适用于 Windows 的工具。

  2. 向下滚动到正式发布(GA)区域(参见图 12-2 )。

img/314857_2_En_12_Fig2_HTML.jpg

图 12-2

选择安装程序

第一个选项(web 安装程序)会将大部分软件保留在互联网上,直到您选择要安装的项目。然后它会在安装时下载每一个。第二个选项一次下载所有项目。如果您在整个安装过程中都连接到互联网,请使用第一个选项。

  1. 点按您想要安装的项目旁边的安装按钮(下载)。

  2. 下一个屏幕建议您登录或创建一个免费的用户 ID 来使用 Oracle 的最新更新和服务。您可以向下滚动到页面底部并单击“不,谢谢,开始下载”链接来跳过这一步(参见图 12-3 )。

    img/314857_2_En_12_Fig3_HTML.jpg

    图 12-3

    启动安装程序

  3. 单击保存文件。

    将下载安装程序。找到它的位置并单击它开始安装。见图 12-4 。

    img/314857_2_En_12_Fig4_HTML.jpg

    图 12-4

    找到并启动安装程序

  4. 您可能会收到一条警告消息,提示您已经下载了可执行文件;如果是,请单击确定。Windows 还会询问您是否允许应用对您的系统进行更改。单击是。将显示安装程序的许可协议。选中“我接受”框,然后单击“下一步”。将出现图 12-5 所示的画面。

    img/314857_2_En_12_Fig5_HTML.jpg

    图 12-5

    选择安装类型

  5. 有五种安装类型可供选择。给出的描述都挺详细的,这里就不描述了。但是,在本演示中,我们不会安装所有产品。我们将只安装我们需要的那些。因此,选择 Custom 并单击 Next 按钮。将出现图 12-6 中的屏幕。

    img/314857_2_En_12_Fig6_HTML.jpg

    图 12-6

    选择要安装的产品

  6. 对于本演示,我们希望安装 MySQL 服务器、MySQL 工作台、文档和示例。您需要单击每个区域旁边的+号(在某些情况下,多个+号)来查找要安装的产品。找到产品后,选择它并单击绿色右箭头。产品应该移动到屏幕的右侧。一旦在右侧看到所有需要的产品(见图 12-6 ),点击下一步。

  7. 所选产品将在下一个屏幕上列出。验证它们是否正确,然后单击“执行”按钮。

  8. 安装完所有产品后,该按钮将变为“下一步”按钮。点击它。

  9. 下一个屏幕将指示我们需要为服务器和示例进行一些产品配置。单击下一步。

  10. 下一个屏幕将为您提供创建复制数据库的机会。这适用于数据可能分散在许多位置的大型配置。我们只测试一个小系统,所以我们需要默认(独立)选择。单击下一步。将出现图 12-7 所示的画面。

![img/314857_2_En_12_Fig7_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/prac-php7/img/314857_2_En_12_Fig7_HTML.jpg)

图 12-7

更改端口号
  1. 为了我们的测试和开发需要,配置类型选项应该设置为开发计算机。但是,将端口号从默认端口(3306)更改为另一个端口(尝试 3307;警告图标将告诉您是否存在冲突)。如果使用不同的端口,我们可以同时运行两个版本的 MySQL。我们目前有一个冲突,因为 MySQL 5.7 正在使用端口 3306。更改端口后,您可以单击下一步。将出现图 12-8 中的屏幕。
![img/314857_2_En_12_Fig8_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/prac-php7/img/314857_2_En_12_Fig8_HTML.jpg)

图 12-8

更改身份验证方法
  1. 由于 PHP 7 目前不能与新的 Oracle 加密技术一起使用,所以我们选择“使用传统身份验证方法”很重要。然后单击下一步。然后,系统会要求您输入根密码。您必须在每次访问 MySQL 8 时使用此密码(这是一个新功能)。请确保输入一个您会记住的安全密码。然后单击下一步。您也可以选择在这个时候(见图 12-9 )或在服务器安装之后,根据角色创建其他用户标识和密码(也是一项新功能)。我们现在不会安装任何特殊的用户标识。
![img/314857_2_En_12_Fig9_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/prac-php7/img/314857_2_En_12_Fig9_HTML.jpg)

图 12-9

添加具有角色的新用户
  1. 下一个屏幕将询问您是否希望 MySQL 作为 Windows 服务启动(换句话说,自动启动)。我们将保留默认设置(或者如果您想要手动启动 MySQL 服务器,您可以更改它们)。单击下一步。下一个屏幕与文档存储相关。这是使用和创建 NoSQL 类型数据库的能力。由于这本书不包括 NoSQL 数据库,我们将保留默认选择(不选择任何内容),然后单击 Next。

  2. 我们将看到应用配置屏幕。单击执行,设置将被安装。更改完成后,单击“完成”。它现在会询问我们样本数据的配置。单击“下一步”按钮。要求时输入您的 root 密码。单击检查按钮。这将验证您可以使用提供的密码访问示例数据库。单击“下一步”按钮。将出现应用配置屏幕。单击执行。这将应用 SQL 脚本来安装示例数据。完成后点击完成。产品配置屏幕将再次出现。单击下一步。

  3. 您应该会看到一个安装完成屏幕。可选地,复选框被选中以启动 MySQL Workbench。因为我们将在下一节中查看 MySQL Workbench,所以您可以保持选中它。单击完成。

恭喜你!您已经安装了 MySQL 8.0。让我们来看一些特性。首先,我们将探索 MySQL Workbench。

探索 MySQL Workbench 的特性

如果您和我们一样,您喜欢 phpMyAdmin 的特性,并且您拒绝发现另一个工具。然而,MySQL Workbench 已经存在了一段时间。许多开发人员多年来一直用它的可视化流程图工具(而不是代码)来设计数据库。一旦设计好数据库,就可以生成 SQL 代码来安装数据库和表。它现在还包括许多更有用的工具。工作台是一个独立的产品,你可以自己安装。现在,如您所见,我们可以将它作为 MySQL 服务器包的一部分进行安装。让我们来看看它的一些特性。

如果 MySQL Workbench 还没有运行,您可以在 MySQL 文件夹下找到它。单击图标启动它。最初的屏幕实际上非常简单;它提供了一些基本信息,包括您已经安装的 MySQL 版本和 MySQL 用于通信的端口(3307)。欢迎界面如图 12-10 所示。

img/314857_2_En_12_Fig10_HTML.jpg

图 12-10

MySQL Workbench 的欢迎屏幕

让我们首先单击图表按钮,这是 shark 数据库图标下面的第二个项目,这将显示模型屏幕(图 12-11 )。

img/314857_2_En_12_Fig11_HTML.jpg

图 12-11

MySQL 工作台的设计模型屏幕

在我们安装的示例文件中,sakila_full 模型向我们展示了一个包含多个表的数据库的可视化表示。点击右边的型号名称查看详细信息。完整模型如图 12-12 所示。

img/314857_2_En_12_Fig12_HTML.jpg

图 12-12

salika _ 完整设计模型

如果你读过第九章或上过数据库设计课,这个图应该很熟悉。这是一个实体关系图。这些图表用于创建标准形式的数据库和表格。所显示的表的格式和表之间的链接表明了关系(如一对多)、属性和实体。学习这些技术需要几个章节。不过你可以在网上找到很多有用的视频(包括一些使用 SQL Workbench 的)。

正如我们前面提到的,Workbench 的一个很棒的特性是能够让它生成 SQL 代码(并安装它),以便在设计好数据库后创建数据库。您还可以将数据库“反向工程”回设计模式。该功能位于数据库菜单下,如图 12-13 所示。

img/314857_2_En_12_Fig13_HTML.jpg

图 12-13

设计数据库

如果您单击屏幕顶部的 MySQL 模型选项卡,您将看到 sakila 数据库的现有模式(图 12-14 )。

img/314857_2_En_12_Fig14_HTML.jpg

图 12-14

萨基拉数据库

在这里,我们可以用与 phpMyAdmin 类似的方式创建数据库和表。我们稍后将回到这个位置。点击屏幕顶部标签上的 X 图标,关闭数据视图。你现在应该回到欢迎页面(如图 12-10 所示)。找到包含 MySQL 版本和端口信息的盒子(在图 12-10 的左下角)。单击该框。将显示服务器仪表板(图 12-15 )。

img/314857_2_En_12_Fig15_HTML.jpg

图 12-15

服务器仪表板

服务器仪表板不仅能够监控我们的数据库,还能够监控服务器本身。单击左侧菜单顶部的服务器状态选项。将显示服务器状态屏幕(图 12-16 )。

img/314857_2_En_12_Fig16_HTML.jpg

图 12-16

服务器状态屏幕

这个屏幕提供了很多关于我们服务器的重要信息。除了文件的位置,我们还可以看到安装和打开了哪些功能。我们可以刷新服务器。此外,在右侧,我们可以看到我们的服务器运行得有多好(高效)。单击左侧菜单中的客户端连接。将显示客户端状态屏幕(图 12-17 )。

img/314857_2_En_12_Fig17_HTML.jpg

图 12-17

客户端状态屏幕

通过这个屏幕,我们可以监控谁在访问我们的服务器(和数据库)。单击左侧菜单中的用户和权限。用户和权限屏幕如图 12-18 所示。

img/314857_2_En_12_Fig18_HTML.jpg

图 12-18

用户和权限屏幕

该屏幕提供了创建访问和维护数据库和服务器的附加用户 id 的能力。正如安装说明中提到的,我们可以将 IT 角色分配给用户 id。我们很快会回到这里。单击左侧菜单中的数据导出选项。数据导出界面如图 12-19 所示。

img/314857_2_En_12_Fig19_HTML.jpg

图 12-19

数据导出屏幕

这是我们可以选择要导出的数据、选择要导出的位置,甚至安排何时要导出数据的位置之一。单击左侧菜单中的数据导入项目。数据导入界面如图 12-20 所示。

img/314857_2_En_12_Fig20_HTML.jpg

图 12-20

数据导入屏幕

这是我们很快将用来导入我们创建的一些数据库的屏幕。

也可以通过左侧菜单上的启动/关闭选项来启动和停止服务器。从服务器日志链接可以很容易地查看服务器日志。单击性能下的仪表板。性能仪表板如图 12-21 所示。

img/314857_2_En_12_Fig21_HTML.jpg

图 12-21

性能仪表板屏幕

MySQL 性能仪表板提供了服务器性能、MySQL 性能和 InnoDB 数据库引擎性能的完整快速视图。

您可能已经注意到,您还可以访问菜单左下角的数据库。现在我们已经快速浏览了 Workbench,我们如何使用 PHP 7 访问 MySQL 8 数据库呢?我们将在下一节中发现这一点。

将 PHP 7 连接到 MySQL 8 社区服务器

我们必须请求 PHP 与我们新版本的 MySQL 进行通信。您需要找到 php.ini 配置文件。在 XAMPP,你可以通过进入控制面板并点击 Apache 服务器右侧的 config 按钮来轻松找到该文件,如图 12-22 所示。

img/314857_2_En_12_Fig22_HTML.jpg

图 12-22

查找 php.ini 配置文件

点击 php.ini 菜单选项将其打开。对于 easyPHP,您必须深入开发服务器文件夹才能找到 php.ini 文件。它应该位于类似以下链接的位置:

C:\Users\yourcomputername\EasyPHP-Devserver-17\EasyPHP-Devserver-17\eds-binaries\php\php713vc14x86x170718155219

当然,您的版本名称和内部版本号将与显示的不同。找到文件后,用记事本或其他文本编辑器打开它。

立即用另一个名称保存当前版本的配置文件(并记住保存的位置),以防出错并需要恢复到原始设置。现在,找到您的搜索工具(在记事本的编辑下)并搜索 3306 的所有位置(或您的默认端口号)。如果您不确定您的 MySQL 端口,您可以在 XAMPP 控制面板(图 12-22 )或 EasyPHP 仪表板(在数据库服务器信息下)中看到它的显示。对于 php.ini 文件中端口(3306)的每个位置,用您的 MySQL 8 服务器端口号(3307)替换它。然后保存 php.ini 文件。

现在使用 XAMPP 控制面板或 easyPHP Dashboard 关闭 Apache,然后再打开。这将使 Apache 看到您的配置更改。

注意

更改 php.ini 配置文件后,XAMPP 和 easyPHP 将无法通信或打开或关闭 MySQL 8。此外,phpMyAdmin 可能无法与 MySQL 8 通信。理想情况下,这些工具的创建者将提供最终可以与 MySQL 8 通信的升级。我们可以在没有这些工具的情况下与 MySQL 8 建立通信,如下所述。

我们现在要确保 MySQL 8 已经启动并运行。一种快速的方法是在浏览器中测试端口。输入以下内容作为 URL: Localhost:3307

如果 MySQL 正在运行,您将会看到类似如下的消息:

Jʿʿʿ
8.0.11ʿʿʿʿsI[R%9BʿÿÿÿʿÿÃʿʿʿʿʿʿʿʿʿʿ▌}x+^{ʿmysql_native_passwordʿ!ʿʿÿ„#08S01Got packets out of order

这大部分并没有告诉我们人类太多。但是,您可以看到 MySQL 8.0.11 在这个示例中已经启动并运行。您还会在端口 3306 上看到 MySQL 5.7 的类似消息。如果您看到类似的消息,您已经准备好测试 PHP 7 和 MySQL 8 之间的通信。如果您看不到这条消息,您可以进入 MySQL Workbench,按照前面的讨论打开 MySQL 8。

为了测试我们的连接,让我们创建一个简单的测试文件,如下所示:

<?php
// Create a connection to the WORLD UNDER MYSQL 8 database
// Set the encoding to utf-8
// Set the database access details as constants
Define ('DB_USER', 'root'); // or whatever userid you created
Define ('DB_PASSWORD', 'yourserverpassword');
// or whatever password you created
Define ('DB_HOST', 'localhost');
Define ('DB_NAME', 'world');
// Make the connection:
$dbcon = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Set the encoding...optional but recommended
mysqli_set_charset($dbcon, 'utf8');
$query = "SELECT * FROM city";
$result = mysqli_query ($dbcon, $query); // Run the query.
if ($result) { // If it ran OK, display the records.
// Table header.
echo '<table class="table table-striped">
<tr><th scope="col">Name</th></tr>';
// Fetch and print all the records:
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
echo '<tr><td>' . $row['Name'] . '</td></tr>'; }
              echo '</table>'; // Close the table so that it is ready for displaying.
              mysqli_free_result ($result); // Free up the resources.
} else { // If it did not run OK.
// Error message:
echo '<p class="text-center">Nothing retrieved.';
// Debug message:
echo '<p>' . mysqli_error($dbcon) . '<br><br>Query: ' . $q . '</p>';
exit;
} // End of if ($result)
mysqli_close($dbcon); // Close the database connection.
?>

在我们之前创建的测试文件中,我们连接到 world 数据库(作为 MySQL 8 下的一个示例)和 city 表来显示城市名称。将这段代码或类似的代码放入之前执行 PHP 文件的位置之一。然后在浏览器中键入 URL 位置。如果连接成功,您将会看到一个类似于图 12-23 所示输出的列表。

img/314857_2_En_12_Fig23_HTML.jpg

图 12-23

测试我们的通信

恭喜你!PHP 7 现在正在和 MySQL 8 通信。要在 MySQL 版本之间来回切换,只需更改前面所示的 php.ini 文件中的端口值。现在我们想看看如何将我们的数据库迁移到 MySQL 8。

迁移到 MySQL 8 社区服务器

好消息是,您的数据库从 MySQL 5.7 迁移到 MySQL 8.11 不会那么困难。我们之前展示的所有代码都可以在 MySQL 8 中使用。理想情况下,您已经使用 InnoDB 数据库引擎(MySQL 5.5 以后的默认配置)和 UTF-8 配置(也是 MySQL 5.5 以后的默认配置)开发了代码(如图所示)。如果您发现您的表或数据库没有使用这些缺省值,您可以修改从数据库中转储的 SQL 代码。

让我们一步一步地完成前一章中迁移数据库的过程。

  1. 打开 phpMyAdmin。好吧,我们抓到你了。还记得我们将端口设置为 3307 吗?如果我们试图打开 phpMyAdmin,我们会得到错误消息。返回到 php.ini 中,将所有出现的 3307 重置为 3306,并重启 Apache 和 MySQL5/MariaDB。现在进入 phpMyAdmin。

  2. 选择 simpleDB 数据库(或者书中的任何数据库示例)。

  3. 单击顶部的导出选项卡。

  4. 输入 simpleDB 作为模板名称(如果下拉框中不存在),然后单击 Create。现在它应该显示在下拉框中。

  5. 单击开始。

  6. 代码将在默认的文本编辑器中打开。将文件另存为 simpleDB.sql 。查看文件的内容,确保它没有指示不同的数据库引擎(应该是 InnoDB 或根本没有列出)。如果列出了不同的代码,您可以从文件中删除相关的代码。然后它将作为 InnoDB 加载。

  7. 打开电脑上 MySQL 文件夹下的 MySQL 8 Workbench。

  8. 单击包含服务器级别和端口号的灰色框,打开服务器导航器。

  9. 在左侧菜单中,单击导入/恢复菜单选项。将出现图 12-24 中的屏幕。

    img/314857_2_En_12_Fig24_HTML.jpg

    图 12-24

    数据导入页面

  10. 选择从自包含文件导入单选按钮。

  11. 单击右边的三个点,选择并找到您刚刚创建的 SQL 文件。单击默认模式下拉框旁边的新建按钮。输入 simpleDB (或者您正在导入的数据库的名称)并单击 OK。现在从下拉框中选择模式。单击页面右下角的开始导入。SQL 代码现在将运行并导入您的数据库。

  12. 您可能没有注意到页面左下角的新数据库。点击包含数据库模式的框上方的 Reload 按钮(两个圆形箭头)。现在应该出现了。

  13. 让我们测试我们的数据库。转到顶部菜单并选择数据库。然后选择连接到数据库。在默认模式文本框中输入您的数据库名称( simpleDB )。单击确定。将出现图 12-25 中的画面。

![img/314857_2_En_12_Fig25_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/prac-php7/img/314857_2_En_12_Fig25_HTML.jpg)

图 12-25

架构查询窗口
  1. 在查询窗口中输入一个 SQL 命令,如图 12-25 所示。点按窗口顶部的任一闪电图标来运行脚本。如果一切正常,您应该会看到一个数据表出现。

现在我们的表已经存在了,让我们找点乐子,对它进行逆向工程。请遵循以下步骤:

  1. 转到顶部菜单上的数据库选项。选择逆向工程。

  2. 将出现一个参数屏幕。保留所有默认设置,然后单击下一步。

  3. 下一个屏幕将验证连接。单击下一步。

  4. 下一个屏幕将列出当前的数据库。列表中应该是您刚刚加载的数据库(simpledb)。选择它并单击下一步。

  5. 下一个屏幕将测试模式(数据库)以查看它是否可以被工程化。单击下一步。

  6. 您将看到一个验证屏幕,显示您正在使用的数据库。单击执行。

  7. 将出现一个进度屏幕。单击下一步。

  8. 最后,将显示一个屏幕,告诉您创建了什么。单击完成。您应该会看到类似于图 12-26 的显示。

    img/314857_2_En_12_Fig26_HTML.jpg

    图 12-26

    简单数据库关系图窗口

恭喜你,你刚刚逆向工程了你的数据库!现在,您可以进行设计更改,并设计它来生成更新的数据库。

您也可以以类似于 phpMyAdmin 的方式进行更改。通过关闭屏幕顶部的选项卡(点击 X )关闭 ER 图窗口。这将使你返回到模式查询窗口(图 12-25 )。在左下方的模式(数据库)窗口中找到要调整的表。在表格名称的右侧,您会看到一个信息图标、一个扳手图标和一个表格图标。信息图标(单击时)提供关于表格的一般信息。表格图标将显示表格中的数据。扳手图标将允许我们更改表格的结构。单击扳手图标。将出现图 12-27 中的窗口。

img/314857_2_En_12_Fig27_HTML.jpg

图 12-27

表格结构屏幕

您可能很难看到此屏幕的所有细节。转到“视图”菜单,单击“面板”,然后单击“隐藏输出区域”。这将为您提供如图 12-27 所示的视图。这个屏幕的使用类似于 phpMyAdmin 中同一个屏幕的使用。

现在让我们试着运行我们的 PHP 文件。

在 MySQL 8 社区服务器上使用我们的 PHP 文件

在开始使用我们在本书中创建的文件之前,我们需要设置用户 id 和密码来访问我们的模式(数据库)。在第二章中,我们用以下信息创建了用户 ID:

  • 用户 id : horatio

  • 密码 : Hmsv1ct0ry

  • 数据库(模式):simpledb

如前所述,在 MySQL Workbench 中,单击欢迎页面上的服务器信息框来访问服务器仪表板。单击左侧菜单中的用户和权限链接。将出现图 12-28 中的屏幕。

img/314857_2_En_12_Fig28_HTML.jpg

图 12-28

用户和权限屏幕

单击屏幕左下角的添加帐户按钮。将显示图 12-29 所示的屏幕。

img/314857_2_En_12_Fig29_HTML.jpg

图 12-29

新用户帐户屏幕

这个屏幕类似于 phpMyAdmin 屏幕。输入用户的信息。请注意,身份验证类型应该设置为标准。PHP 7 需要这个设置。输入所需信息后,单击应用。

现在单击“管理角色”选项卡。将显示图 12-30 中的屏幕。

img/314857_2_En_12_Fig30_HTML.jpg

图 12-30

设置用户角色

您可以在此屏幕中决定您的用户 ID 可以执行的角色。可以多选一个。您也可以从右侧的选项中单独选择角色。您可以选择显示的设置或自己的设置。但是,您还需要在右侧窗口中选择 Execute。如果未选择执行,程序将无法使用该 ID。对选择满意后,单击应用。您还可以选择为每个用户 ID 设置帐户限制,以限制允许使用帐户限制选项卡进行的查询和更新次数。

我们现在准备好测试我们的程序了!确保您在 php.ini 文件中的端口设置被设置为 3307。还要确保 Apache 正在运行。我们现在将测试 simpledb 数据库的注册页程序。输入以下内容作为您的 URL:

localhost/simpledb/register-page.php

应该会出现注册页面。输入一些信息,然后单击注册按钮。应该会显示“谢谢”页面。

注意

MySQL 8 中 SQL-MODE 的默认设置是严格的。这可能会导致您收到类似以下内容的错误:

Incorrect integer value " for column 'userid' at row 1

当试图将字符串放入数据库中的整数位置时,如下所示的 SQL INSERT 语句可能会导致此问题:

$query = "INSERT INTO users ( userid, first_name, last_name, email, password, registration_date) ";

$query .="VALUES( ' ', ?, ?, ?, ?, NOW() )";

此错误可通过以下两种方式消除:

  1. 将查询更改为不插入自动递增列。它仍然会自动递增。

    $query = "INSERT INTO users ( first_name, last_name, email, password, registration_date) ";

    $query .="VALUES( ?, ?, ?, ?, NOW() )";

  2. 找到 my.ini 文件。该文件保存在 c 盘下的程序数据文件夹中。该文件夹可能隐藏在您的视图中,因此您可能需要在 Windows 资源管理器窗口中单击“查看”,然后单击该框来查看隐藏的文件。该文件位于以下位置:

    C:/Program Data/My SQL/MYSQL Server 8.0

在文本编辑器中打开文件。搜索 sql 模式。设置如下所示:

sql-mode="STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"

现在将设置更改为 sql-mode= " "。保存文件并在 Workbench 中重启 MySQL 8。您的注册现在应该可以工作了。

如果您有其他错误,您可以查看迁移文档,可以从 MySQL Workbench 的欢迎页面上左侧菜单的底部图标访问该文档。您也可以在搜索引擎(Google)中输入您的错误信息,寻求解决方案的建议。

您可以转到客户端连接屏幕(图 12-17 )查看您创建的帐户的登录信息。

摘要

本章为您提供了使用 MySQL 8 的优势的快速解释。您还了解了如何安装服务器和迁移现有的数据库。此外,您还学习了如何创建可供程序或数据库管理员使用的用户 ID。您还了解了 MySQL Workbench 的特性。

posted @ 2024-08-03 11:23  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报