内容管理系统

内容管理系统
(原著:Kevin Yank 翻译:处处 2001年12月04日 13:28)

迄今为止,我们已经看到了一些数据库驱动的网页:这些页面在被请求时可以显示从一个MySQL数据库中得到的信息。但是,一直到现在,我们还没看到这样的一个解决方案,那就是如何方便地管理一个诸如SitePoint.com这样复杂的大型网站。当然,我们的笑话数据库是挺不错的,但是在我们要管理目录和作者时,我们总是返回到MySQL命令行,去使用复杂的SELECT语句和INSERT语句,以及那些表名和列名。

要将一个仅仅是显示数据库中信息的网站改造成一个完全的数据库驱动的网站,我们需要增加一个内容管理系统。这样的一个系统通常包含了一系列的网页,对这些网页的访问应该是仅供被授权可以更新的网页的用户使用。这些页面提供了一个数据库管理的界面,通过这个界面,用户可以很容易地浏览并变更数据库中存储的信息,而不用再去理会那些晦涩的SQL语法。

实际上,我们在第四章的最后已经开始建立这样的内容管理系统了,当时我们允许网站的访问者使用一个Web的表单添加笑话并且可以通过一个“删除笑话”的连接删除笑话(如果你完成了那个挑战的话)。但是所有的这些功能是在一个对所访问者都可见的页面上的。而事实上你可能不想让任何人都可以未经你的授权就向你的网站添加一些可能是没用的材料。而且你也不希望任何人都可以从你的站点中删除笑话。

要克服这些“危险”,你需要一个限制访问的网站管理页面,这样你就可以避免将你的数据暴露给所有人,而你仍可以不再使用SQL查询语句而直接管理数据库的内容。在这一章里,我们会扩展我们的笑话管理系统的能力来有效利用我们在第五章中对数据库作的改进。特别地,我们会允许网站的管理者来管理作者和目录,并对于相应的笑话指定作者和目录。

正如我们在前面解释的,这些管理页面必须被一个适当的访问权限配置保护。将相应的PHP文件放置到一个包含授权用户的.htaccess文件的目录下是一种较好的方法。有关这方面的信息你可以查阅你的Web服务器的相关文档。

从这一章开始,我们会使用一些相当大的PHP文件,由于空间的限制,我们会省略其中的一些细节。

首页
在第五章的结尾,我们的数据库包括了描述下面三种事物的数据表:笑话、作者和笑话目录。请注意我们假定每一个作者只有一个email地址,所以我们没有一个单独的描述email地址的数据表。因此,我们的内容管理系统的首页将包含下面三个连接来管理这三项:



JMS


Joke Management System






管理作者
让我们从authors.php开始,这个文件可以让管理者增添新的作者、删除和编辑已经存在的作者。如果你对多功能页面的主意有兴趣,你也许会想要将所有的代码放到同一个文件authors.php中去。因为如果那样做的话,代码码将会相当长,所以在我们的例子中将这个文件分开了。

第一件事是我们想要呈现给管理者的是一份已经存储在数据库中的所有作者的清单。从代码的角度看,这和我们列出在数据库中的所有笑话没什么两样。因为我们需要可以删除和编辑已经存在的作者,所以我们在每个作者的姓名后面包含了这些功能的连接。就象我们在第四章的最后的挑战中的“删除笑话”的连接一样,在连接中包含了这个作者的ID,这样目标程序就可以知道我们想要编辑或删除哪个作者。最后,我们还提供一个“建立新作者”的连接使其导向一个表单,这就和我们在第四章中所做的“添加笑话”一样。



Manage Authors


Manage Authors



Create New Author



  • $cnx = mysql_connect('localhost','user','password');
    mysql_select_db('jokes'); $authors = mysql_query("SELECT ID, Name FROM Authors");
    if (!$authors) {
    echo("

    Error retrieving authors from database!
    ".
    "Error: " . mysql_error());
    exit();
    }
    while ($author = mysql_fetch_array($authors)) {
    $id = $author["ID"];
    $name = $author["Name"];
    echo("

  • $name ".
    "[Edit|".
    "Delete]");
    }
    ?>


Return to Front Page





删除作者
deleteauthor.php用来从数据库中删除指定ID的作者。正如我们前面看到的,这可以很容易地通过一个DELETE命令来实现,但是这儿要稍微复杂一点。要记得我们的Jokes数据表中有一个AID列是指出该笑话的作者的。当从数据库中删除作者时,我们必须删除其它数据表中提供这个作者的地方。如果我们不这样做,下一个作者被添加时,他的ID可能和我们现在删除的作者的ID相同,而属于被删除的作者的笑话也将错误地指向新的作者。

我们有两个选择来处理这个情况:
在删除一个作者时,同时删除属于这个作者的所有笑话。
当删除一个作者时,将属于这个作者的所有笑话的AID设为NULL,以标志它们没有作者。
因为绝大多数的作者都不希望我们使用他们的笑话而不署名,所以我们采用第一种方案。这也使得我们不必在显示我们的笑话库时对AID列的NULL值进行处理。



Delete Author


$cnx = mysql_connect('localhost','user','password');
mysql_select_db('jokes');
// Delete all jokes belonging to the author
// along with the entry for the author.
$ok1 = mysql_query("DELETE FROM Jokes WHERE AID=$id");
$ok2 = mysql_query("DELETE FROM Authors WHERE ID=$id");
if ($ok1 and $ok2) {
echo("

Author deleted successfully!

");
} else {
echo("

Error deleting author from database!
".
"Error: " . mysql_error());
}
?>

Return to Authors list





增添作者
下一个是newauthor.php,它使得管理者可以向数据库中添加新的作者。再说一次,这和我们在第四章中所做的添加新笑话非常类似。



Add New Author



if ($submit): // A new author has been entered
// using the form below.

$dbcnx = @mysql_connect(
"localhost", "root", "mypasswd");
mysql_select_db("jokes");

$sql = "INSERT INTO Authors SET " .
"Name='$name', " .
"EMail='$email'";
if (mysql_query($sql)) {
echo("

New author added

");
} else {
echo("

Error adding new author: " .
mysql_error() . "

");
}

?>


Add another Author


Return to Authors list


else: // Allow the user to enter a new author
?>

Enter the new author:

Name: MAXLENGTH=100>

eMail: MAXLENGTH=100>







编辑作者
剩下来的是editauthor.php,它应该提供一个界面以用来编辑一个已经存在的作者的详细资料。这个页面其实和newauthor.php非常相似,只不过在这里表单域会初始地包含一个数据库中存储的数据,此外,在表单提交时我们将使用一个UPDATE命令而不再是一个INSERT命令。

这儿还有另外一个小麻烦。要以存储在数据库中的值初始化表单的值,这个页面肯定会通过从authors.php传递过来的$id值得到相关信息并将其存储到PHP变量中(比如说,$name和$email)。我们的表单的代码看上去应该是这样的:

Edit the author:

Name:

eMail:





请注意我们这里使用了一个表单的隐藏域,在表单提交时用来传递更改的作者的ID。

但是考虑到作者的姓名可能是"The Jokester"(带有引号)。PHP脚本将会产生这样的代码:


很明显,这是一个有问题的HTML。我们必须通过一个反斜杠“忽略”姓名中的引号,以使得Web浏览器知道这是属性的值的一部分,而不是代表属性值的开始或结束:


PHP提供了一个称为addslashes的函数来自动在需要的地方添加反斜杠(例如引号和撇号)。对所有变量值使用该函数,可以从数据库中获取变量值而将其作为一个HTML属性值使用,而不会因为引号的存在而发生问题。
$name = addslashes($name);
同样的问题也存在于我们在SQL查询中使用这些变量之前。让我们先来看看下面的SQL INSERT命令:
mysql> INSERT INTO Authors SET
-> Name='Jennifer O'Reilly',
-> eMail='jen@hotmail.com';

很明显,作者的姓中的撇号会导致问题。为什么之前我们没有担心过这个问题呢?PHP有一个被称之为"magic quotes"的极好的特征,我们可以在你的php.ini(或者php3.ini)文件中进行设置:
magic_quotes_gpc = On

这个设置会告诉PHP在任何变量在页面的请求中传递时自动使用addslashes函数。gpc意味着“get、post和cookies”,这是信息作为页面的请求时的三种方法。现在,因为我们插入到数据库中的所有值被作为表单提交的一部分进行传递,PHP的Magic Quotes特征会每次自动在其中添加斜杠。然而,在从MySQL数据库中获取值时,我们无法再得到Magic Quotes特征的帮且了,所以我们必须手工添加斜杠以避免因为引号、撇号或其它特殊符号的存在而可能带来的问题。

我们解决了讨厌的特殊符号的问题,但是作者中出现的其它符号也会导致问题。例如, <和> 被定义为HTML的标识符,它们会输出效果产生破坏。有一个函数可以将其转换成“安全的”格式。这个函数是htmlspecialchars,它的使用方法几乎和addslashes完全一样:
$text = htmlspecialchars(" can be dangerous!");
echo($text); // output: can be dangerous!

考虑到这些问题后,我们现在可以建立editauthor.php了。

管理目录
当你对作者和笑话目录在数据库的角色进行比较时,你会发现它们是非常相似的。它们都有一个自己的数据表,它们都通过一定的途径与一组笑话发生联系。所以,目录可以使用我们之前为作者开发的几乎一样的代码赤处理,只是我们必须考虑到一个重要的不同点。

当删除一个目录时,我们不能同时删除属于这个目录的笑话,因为那些笑话可能还属于其它的目录。我们可以对所有笑话进行检查,看看它们是否属于其它的目录,而只删除那些不属于其它目录的笑话,但是这种做法很费时,事实上,我们可以允许我们的数据库的笑话不属于任何目录。这些笑话对我们的网站的访问者可能是不可见的,但是它们被保留在数据库中,我们将来可以为它们指定目录。

这样,删除一个目录,我们也同时删除JokeLookup表中所有与该目录有关的记录:
...
// Delete all joke lookup entries for the
// category along with the entry for the category.
$ok1 = mysql_query(
"DELETE FROM JokeLookup WHERE CID=$id");
$ok2 = mysql_query(
"DELETE FROM Categories WHERE ID=$id");
...

除了这个细节外,目录管理在机能上和作者管理一样。

管理笑话
除了添加、删除和修改我们的数据库中的笑话外,我们还需要为我们的笑话指定目录和作者。此外,我们的笑话可能会比作者或目录的数量多得多。因此,象在作者和目录中那样显示全部的笑话列表将会是一个很难管理的长清单,从中也不容易找出我们想找到的笑话。我们需要更方便的方法来浏览我们的笑话库。

因为在不同的时候,我们可能知道想要管理的笑话的目录、作者或其中的某一段文字,所以我们支持所有的这些从数据库中寻找笑话的方法。这个表单会提示管理者为想要得到的笑话确定目录和作者。代码是这样的:



Manage Jokes


Manage Jokes


Create New Joke


$dbcnx = @mysql_connect(
"localhost", "root", "mypasswd");
mysql_select_db("jokes");
$authors = mysql_query(
"SELECT ID, Name FROM Authors");
$cats = mysql_query(
"SELECT ID, Name FROM Categories");
?>

View jokes satisfying the following criteria:

By Author:


By Category:


Containing Text:






Return to Front Page





请注意在被echo函数输出的字符串结尾的是指换行,这将增加这个脚本输出的HTML代码的可读性。

下面一个问题是由jokelist.php来使用表单提交的数据来以令人满意的指定的标准生成笑话的清单。明显的,这会使用到一个SELECT查询,但是这个查询的格式必须取决于我们刚才在表单中的输入。因为建立这个SELECT语句是个相当复杂的过程,让我们先对jokelist.php好好分析一下。

首先,让我们先做一下准备:



Manage Jokes


Manage Jokes


New Search



$dbcnx = @mysql_connect(
"localhost", "root", "mypasswd");
mysql_select_db("jokes");

现在,我们开始定义一些字符串,当这些字符串结合到一起时,将生成一个不受我们表单中输入条件约束的SELECT查询:
// The basic SELECT statement
$select = "SELECT DISTINCT ID, JokeText";
$from = " FROM Jokes";
$where = " WHERE ID > 0";

你也许会对上面的WHERE子句感到迷感。我们要根据表单选择的约束在基本的SELECT语句的基础上建立我们的SELECT语句。这样的约束需要我们往SELECT语句的FROM和WHERE子句中添加一些东西。但是,如果没指定约束(也就是管理者想要列出数据库中全部的笑话),那么我们根本就不需要一个WHERE子句!因为要增加一个不存在的WHERE子句是相当困难的,所以我们需要一个“什么都不做”的WHERE子句。要求Jokes.ID大于零是一个好主意,因为MySQL的AUTO_INCREMENT的特性(它用来指定这一列的值)使得总是选择大于零的整型值。

下一个任务是检查每一个表单中可能设置的约束(作者、目录或搜索文本),并相应地调整我们的SQL。首先,我们来处理可能设置的作者,表单中的“Any Author”选项我们最初设定为""(空字符串),因此如果这个表单域的值(存储在$aid中)不等于"",这就意味着指定了作者,我们将相应调整我们的查询:
if ($aid != "") { // An author is selected
$where .= " AND AID=$aid";
}

.=操作符用来向一个已经存在的字符串后面添加新的字符串。现在,我们是在WHERE子句中添加有关 Jokes表中AID的条件以匹配表单中选择的作者ID($aid)。
下面,我们来处理指定的作者目录:
if ($cid != "") { // A category is selected
$from .= ", JokeLookup";
$where .= " AND ID=JID AND CID=$cid";
}

因为与具体的笑话关联的目录被存储在JokeLookup表中,我们需要将这个表添加到查询中,并通过将这个表名添加到$from变量后面以建立一个连接。为了完全这个连接,我们还需要指定ID列(在Jokes表中)必须和JID列(在JokeLookup表中)匹配,实现的方式是在$where变量中添加这个条件。最后,我们需要CID列(在JokeLookup表中)匹配我们在表单中选择的目录ID($cid)。

处理搜索文本相当简单。我们只需要使用我们在第二章中学习过的SQL的LIKE条件:
if ($searchtext != "") { // Search text specified
$where .= " AND JokeText LIKE '%$searchtext%'";
}

当我们的SQL查询建立好之后,我们可以使用它来得到我们的笑话并将其与相应的编辑和删除的连接一起显示出来(就象我们对作者和笑话目录所做的那样)。为了增加可读性,我们以一个HTML表的格式显示笑话:








?>





Joke TextOptions
$jokes = mysql_query($select . $from . $where);if (!$jokes) {echo("
");
echo("

Error retrieving jokes from database!
".
"Error: " . mysql_error());
exit();
}

while ($joke = mysql_fetch_array($jokes)) {
echo("

");$id = $joke["ID"];$joketext = $joke["JokeText"];echo("
$joketext
");echo("
[".
"Edit
|".
"".
"Delete
]
");echo(" ");}?>



处理完jokelist.php后,让我们来处理jokes.php顶端连接的newjoke.php。这个页面和newauthor.php、newcat.php非常相似。然而,除了录入笑话的文本外,这个页面还需要允许管理者指定一个作者和目录。因为这个特征的存在,使得我们有兴趣仔细研究一下这段代码。

在newauthor.php的代码中我们可以看到我们是在表单的代码前面添加了一段代码来处理表单的提供(这样做并不是必须的,但是到目前为止,这是我们的一种风格)。让我们先来看看表单的代码。
我们首先得到数据库中的所有作者和目录的清单:
else:
$dbcnx = @mysql_connect(
"localhost", "root", "mypasswd");
mysql_select_db("jokes");
$authors = mysql_query(
"SELECT ID, Name FROM Authors");
$cats = mysql_query(
"SELECT ID, Name FROM Categories");
?>
然后,我们建立我们的表单。我们首先需要一个标准的文本输入域以录入笑话的内容:

Enter the new joke:


我们会提示管理者从一个从数据库中获得的作者的下拉列表中选择作者:

Author:



一个下拉列表对于选择目录是不足够的,因我们想要允许管理者为一个笑话指定多个目录。这样,我们需要使用一个多选框--其中的每一项对应一个目录。这个多选框的名字将是cat1、cat2、cat3等等。多选框将被标志为目录的名字。

Place in categories:

while ($cat = mysql_fetch_array($cats)) {
$cid = $cat["ID"];
$cname = $cat["Name"];
echo("".
"$cname
");
}
?>



最后,我们和通常一样结束我们的表单:





处理这个表单并不是一件太简单的事,所以我们会详细介绍这段代码。将一个笑话添加到Jokes中开始是相当简单的。因为一个作者是必须指定的,所以我们必须确保$aid包含一个值。这可以防止管理者选择作者下拉列表中的“Select One”选项,因为这个选择对应值为""(空字符串)。

if ($submit): // A new joke has been entered
// using the form.

if ($aid == "") {
echo("

You must choose an author " .
"for this joke. Click 'Back' " .
"and try again.

");
exit();
}

$dbcnx = @mysql_connect(
"localhost", "root", "mypasswd");
mysql_select_db("jokes");

$sql = "INSERT INTO Jokes SET " .
"JokeText='$joketext', " .
"AID='$aid'";
if (mysql_query($sql)) {
echo("

New joke added

");
} else {
echo("

Error adding new joke: " .
mysql_error() . "

");
}

$jid = mysql_insert_id();

上面代码的最后一行我们使用了一个我们以前没看过的函数: mysql_insert_id。这个函数返回MySQL的AUTO_INCREMENT特征为最后插入的记录指定的数值。换句话说,我们得到了新插入的笑话的ID,在下面我们将用到它。

根据选中的多选框向JokeLookup添加记录并不那么简单。首先,在之前我们并不知道一个多选框如何向PHP传递变量;此外。我们还需要处理这样的情况:我们并不能预告知道表单有多少多选框(因为我们并没有限制数据库中目录的数量)。

一个多选框如果被选中将会将它的值传递到一个PHP的变量,而如果没有被选中,将不会传递任何值。在我们上面的表单中,我们并没有定义多选框的值。未定义值的多选框在被选中时仍然会向相应的变量传递一个值。因为PHP考虑到字符串值“true”可以被一个if语句作为一个条件使用,而一个空字符串(或未指定)将对应变量“false”,我们可以仅仅使用多选框变量来检验多选框是否被选中。
对于处理我们不知道多选框数量的情况,最好的方法是对代码进行逐行的解释。首先,我们获得数据库中所有目录的列表,包含它们的ID:
$cats = mysql_query(
"SELECT ID, Name FROM Categories");

因为在建立多选框时,我们使用了同样的清单,这使我们意识到我们可以在这里使用它去进行处理。我们象往常一样使用一个while循环去逐步处理这个清单:
while ($cat = mysql_fetch_array($cats)) {
$cid = $cat["ID"];
$cname = $cat["Name"];

对于清单中的每一个目录,我们想要使用相应的多选框的值来决定是否向这个目录中添加新笑话。现在的问题是多选框的命名取决于相应的目录的ID。我们因此必须使用目录的ID建立变量名。下面是代码:
$var = "cat$cid"; // The name of the variable
if ($$var) { // The checkbox is checked

两个美元符并不是一个排印错误。名为$var的变量的值将是"cat#",在这里,#是相应的目录的ID。而$$var值将会和$cat#相等。这是PHP的一个相当晦涩的特性,被称之为“变量的变量”,在这种情况下,它很有作用。总之,我们使用在if语句中使用$$var以完成在多选框被选中时向JokeLookup中添加记录:
$sql = "INSERT IGNORE INTO JokeLookup " .
"SET JID=$jid, CID=$cid";
$ok = mysql_query($sql);
if ($ok) {
echo("

Joke added to category: $cname

");
} else {
echo("

Error inserting joke ".
"into category $cname:" .
mysql_error() . "

");
}
} // end of if ($$var)
} // end of while loop
?>


Add another Joke


Return to Joke Search



在INSERT查询中使用IGNORE仅仅是一种预防措施。回忆一下,我们在定义JokeLookup表时,我们将JID和CID列设置为了这个表的主键。如果因为未知原因,被插入的JID/CID对在表中已存在。试图再次插入通常会导致一个错误。而在命令中增加了IGNORE之后,重新插入相同的对会被MySQL简单地忽略,而不会发生错误。这种情况也许根本不会发生,但是考虑到它肯定更为安全。

剩下的两个文件:editjoke.php和deletejoke.php与我们管理作者和目录时的情况相当类似,只需要作一些小的调整。editjoke.php必须和addjoke.php一样提供作者的下拉列表框的目录的多选框,只是我们在这里必须根据被选中的笑话在数据库中的相应的值对它们赋一个初值。对于deletejoke.php,我们不仅仅需要从Jokes表中删除选中的笑话,还需要从JokeLookup表中删除与该笑话有关的记录。但是我们不会花费时间进行详细的研究,因为这两个文件并没有涉及到什么其它的技术。

结语
对于我们的内容管理系统还有一些细节问题没解决。例如,现在还不能提供一个不属于任何目录的笑话列表--当我们数据库中的笑话日益增多时,这个功能早晚会用得上。我们也许还需要按不同的条件对笑话清单进行排序。这些功能的实现还需要其它的一些SQL的技巧,在下面我们将学习它。

如果忽略这些小细节,现在我们已经有了一个可以让一个没有任何SQL或数据库知识的人来很方便地管理我们的笑话数据库的系统了!加上一系列PHP的页面以向访问者显示笑话,这个内容管理系统使得我们可以建立一个可以由一个完全没有数据库知识的人来维护的数据库驱动的Web站点。

事实上,我们的站点只需要用到一个方面的特殊的知识(不包括Web浏览器的使用):内容格式。你不要对这个说法感到奇怪,例如,如果有人想要输入一个包含几段文本的笑话。在我们目前的系统中,可以直接在“Create New Joke”表单中输入HTML代码来完全。那么为什么这是不可接受的呢?

让我们回到前言,一个数据库驱动的Web站点的一个最主要的特征就是向其中添加内容的人不需要熟悉HTML。如果将一个笑话分段需要用到HTML的知识,这就违背了我们最初的原则。

在第七章中,我们会看到如何使用PHP的一些特性去提供一个有关格式化的内容的简单的方法而不需要管理者了解HTML的细节。我们还会恢复“Submit Your Own Joke”连接以使我们可以安全地接受由访问者提供的内容。

(责任编辑 尤北 lvye@staff.ccidnet.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值