Web Development
Archived Posts from this Category
Archived Posts from this Category
在最近的一篇blog中,使用了colgroup HTML标签。没想到,使用了colgroup的帖子经过XHTML Validator的检查,发现居然不符合XHTML规范。究其原因,说是发现了如下错误:
Error Line 78 column 139: document type does not allow element "br" here.
...ALIGN: center"></col></colgroup><br />
但是奇怪的是,我的源代码中,</colgroup>后并没有<br />。我们可以用W3Shool给出的最简单的例子进行实验:
试着在Wordpress (2.0.x,包括最新的2.0.10。在2.2上则没有这个bug) 中编辑一个新帖子,在代码画面敲入上面这段代码,发布,再去看生成的HTML时就会发现变成了这个样子:
由于</colgroup>和<tr>之间是不允许有<br/>的,所以导致XHTML验证错误。
那么追根到底是在什么时候这个<br/>被添加到输出的HTML中的呢?调查了一下数据库,发现在数据库里面的内容还是没有<br/>的,这说明它是在读取post的内容时,由相关的Filter添加的。一个post的内容在被从数据库读出来,到被显示在网页上之间使用的是叫做the_content的Filter Tag。在Wordpress中,默认有如下Filter被添加到the_content的处理中:
add_filter('the_content', 'wptexturize');
add_filter(’the_content’, 'convert_smilies’);
add_filter(’the_content’, 'convert_chars’);
add_filter(’the_content’, 'wpautop’);
经过检查,发现位于/wp-includes/functions-formatting.php中的wpautop函数中有如下代码:
可见,该函数中用preg_replace函数通过正则表达式删除掉了添加在table,thead,tfoot等后面的<br />,却没有删除colgroup后面的<br/>。因此,只要在这一行添加colgroup即可修正这个bug。将修改后的functions-formatting.php重新上传到服务器后,在此进行XHTML验证,正常通过。
刚刚发现了百分比(%)在IE和在Firefox中的表现并不相同,现在居然发现即便是在Firefox内部,em和%的表现也并不一样。
首先先要讨论一下,作为font-size等CSS属性的单位,78%和0.78em到底是否相同?
遗憾的是,我并不是CSS专家,所以我没法回答按照标准这两个单位是否完全相同。但是,根据某些权威网站的说明,
Both em and % are relative measures. The difference is what each is relative to. Em is always relative to font size. % is relative to the containing block, usually the body, a div, or a table, unless applied to font-size, in which case it applies to the parent font-size.
也就是说,在用于font-size的时候,两者的涵义应该是相同的。但是,当我用下面在Firefox (ver 2.0.0.3, 默认字体微软雅黑16px)上显示如下HTML片段时,却出现了令我不解的结果:

Firefox下的结果

IE下的结果
看了这些结果,我真的不明白了:到底font-size单位中的百分比和em是否相同?
此外,有一点让我安心的就是,如果使用em作为单位的话,就没有上一篇文章中提到的,IE和Firefox中大小不一致的现象了。
| font-size | IE6/IE7 | Firefox |
|---|---|---|
| 100% | 16px | 16px |
| 99% | ||
| 98% | ||
| 97% | 15px | |
| 96% | 15px | |
| 95% | ||
| 94% | ||
| 93% | ||
| 92% | ||
| 91% | ||
| 90% | 14px | 14px |
| 89% | ||
| 88% | ||
| 87% | ||
| 86% | ||
| 85% | ||
| 84% | 13px | 13px |
| 83% | ||
| 82% | ||
| 81% | ||
| 80% | ||
| 79% | ||
| 78% | 12px | 12px |
| 77% | ||
| 76% | ||
| 75% | ||
| 74% | ||
| 73% | ||
| 72% | 11px | |
| 71% | 11px | |
| 70% | ||
| 69% | ||
| 68% | ||
| 67% | ||
| 66% |
今天在测试IE7和firefox下blog的字体显示时,发现在某些特定的font-size下,IE和firefox所显示的字体大小并不相同。
这里要提前说明的是,我所遇到的问题是在如下环境下发生的:
为了找出问题的根本原因,我写了一段php代码用来输出font-size等于100% (也即1em) 到 50% (0.5em)之间的每隔1% (0.01em)的文字显示结果。该程序的运行结果如下图所示。

上图是在Firefox下的测试结果。然后,根据IE和Firefox下的结果,得出了右面的这张表格。
通过这张表格即可看出,虽然在大多数的的情况下IE和Firefox的结果是相同的,但是当设置font-size=97%的时候,IE会显示16px的字体,而Firefox则显示15px的字体。同样font-size=72%时,IE显示12px字体,Firefox则显示11px字体。
实际用计算器计算一下就知道,当font-size为97%时,16px X 97% = 15.52px。也就是说,在IE中15.52px被解释为16px字体,而在Firefox当中15.52px被解释为15px字体 (不知道是不是Firefox的计算方法有问题?或者是W3C规定这样做的?还是我计算错误?)。
补充一条最新发现:刚才试着用CSSViewer这个插件检查了一下font-size=97%时的像素值,发现结果并不是我想象的15.52px,而是15.4667px——不知道Firefox中究竟是按照什么标准计算百分比单位的font-size的呢?
IE和Firefox究竟谁对谁错,实在是难以说清,不过有一点是肯定的——就是在设计CSS的时候,要特别注意这些IE和Firefox之间不同的地方。
今天,正当我在Wordpress(版本:2.0.10)中开始一个新帖子,打算上传一张图片的时候,在点击了Upload按钮之后,出现的却是下面的这条消息:
The uploaded file could not be moved to .
奇怪,明明上传目录的权限都没问题,为什么会无法上传呢?查找了一下上面消息的来源,发现wp-admin/admin-functions.php的大概位于1776行处有如下代码:
可见,问题的原因是move_uploaded_file函数无法正常将文件拷贝所致。在允许显示错误信息后,可看到类似下面的错误信息:
Warning: move_uploaded_file(): SAFE MODE Restriction in effect. The script whose uid is 1525 is not allowed to access /somefolder/wp-content/uploads/2007/05 owned by uid 48 in /somefolder/wp-admin/admin-functions.php on line 1778
可见,这是因为SAFE MODE功能阻止了uid为1525的脚本访问uid为48的用户所拥有的上传目录(uploads/2007/05)。那么,什么是SAFE MODE呢?
SAFE MODE(安全模式)是为了解决共用服务器(例如一台服务器划分成多台虚拟主机的服务商)中的安全问题而被添加到PHP中的一种功能。该功能通过一些强制性的限制而提高安全性,但是反之这些限制使得程序受到约束,甚至产生问题。
SAFE MODE中很重要的一个功能就是,在PHP脚本进行文件系统操作时,对uid/gid进行比较、核查。当一个用户的脚本试图访问另一个用户的目录时,系统就会拒绝访问并给出上面那样的错误信息。
那么,为什么目录“uploads/2007/05”会是“另一个”用户的目录呢?这“另一个”用户,到底是谁呢?
继续调查,找到了创建这个目录的代码。该代码位于/wp-includes/functions-post.php的约867行开始的wp_mkdir_p()函数中:
问题就出在mkdir函数上面。在SAFE MODE模式下,PHP的mkdir()函数所创建的目录,owner并不是script,而是Apache。因此,上面消息中“uploads/2007/05”的owner ID 48,就是Apache进程的uid了。
由此可见,在不改变服务器端设置的前提下(跟服务商打交道比较麻烦,而且让他们更改SAFE MODE不大现实),要想解决这个问题,只能想办法不使用mkdir()了。幸好Wordpress里面,需要创建目录的也就是上传的时候,因此作为workaround,手动用ftp事先创建好每个月的上传目录就是了。因为ftp所使用的帐号是和自己的www服务挂钩的,因此不存在uid不同的问题。
那么,对于必须使用mkdir的程序,有什么办法呢?据说,PHP6里面就会取消SAFE MODE——不过,等PHP6大功告成,再等保守的服务提供商们更新换代,估计都到了Web 4.0时代了。目前的解决方案来说,既不改变服务器设置、也不对应用程序本身进行大规模的重新设计的workaround,就是以ftp_mkdir()替换mkdir()。原理就是因为ftp是以和script相同的用户操作文件系统的。当然,这个替换并不是简单的把mkdir换成ftp_mkdir,而是需要ftp服务器的IP地址,用户名和密码,很有局限性,使用前需要仔细权衡。
和许多人一样,我也是在转移blog时才发现这个问题。虽然是一个很老的问题了,为避免沉痛教训,这里就把相关知识做一总结,以方便后人。
【现象】
本来我的blog是放在家里的服务器的,最近因为要迁到租用的主机上,就开始了搬家工作。首先是文件的拷贝,一切顺利;接下来是把数据库从家里的MySQL中导出,然后导入到主机提供商的MySQL上去。由于两边虽然MySQL版本不同(家里是5.x,租用的主机那边是4.1x),但是由于都安装有PMA(PHPMyAdmin),应该没什么问题。
这么想着,等我在家里的PMA里执行“导出”之后,就犯嘀咕了——怎么打开生成的sql文件一看,wp_posts的贴子内容都是乱码啊?通过查看了一下数据库、各个表,发现collation一栏里面写的都是utf8_general_ci……虽然不大懂collation,但是应该字符编码都是utf-8,怎么会是乱码呢?不管三七二十一,先导入到租用的主机上再说!——结果,导入到租用主机上,仍然是乱码。
【预备知识】
为了解决问题,有必要学习和复习一下相关的基础知识。
首先是MySQL里面关于character set(字符集)和collation(整理?我认为翻译成比较规则可能更贴切)的概念。
Character set顾名思义,就是字符、以及字符对应的编码的集合。例如简体中文字符集gb2312就包括简体中文中的所有规定汉字,以及每个汉字对应的代码。
Collation,是指比较字符的规则的集合。有了比较规则,才能够将一组数据排序——例如按照英文字母顺序排序、汉字按照拼音顺序排序等等。显然,针对同样一组字符集可以有不同的排序标准、规则。例如汉字可以按照拼音排序,也可以按照笔画多少排序。尤其是Unicode的字符集,由于其可以包含不同种类的语言,所以可以按照各种语言的排序方法排序。此外,完全按照字符在字符集里的编码进行比较的方式称为binary比较。
到了这里我们就容易理解了。举例来说,MySQL支持的gb2312字符集中,有gb2312_bin和gb2312_general_ci两种collation。很显然前者是binary比较规则,后者是一般的中文字符比较规则。
每种字符集都有其默认的collation。对于utf8字符集来说,其默认collation是utf8_general_ci。要获得MySQL里面支持的字符集和默认collation列表,可以使用SHOW CHARACTER SET语句:
mysql> SHOW CHARACTER SET; +----------+-----------------------------+---------------------+ | Charset | Description | Default collation | +----------+-----------------------------+---------------------+ | big5 | Big5 Traditional Chinese | big5_chinese_ci | | dec8 | DEC West European | dec8_swedish_ci | | cp850 | DOS West European | cp850_general_ci | ...
其次,是MySQL中,在哪些地方需要这些字符集和collation。总体上分,在MySQL的体系中有三处字符集和collation:服务器(数据),连接,客户端。乍一看体系清楚明了,其实并不是这样。下面就一一介绍。
[1] 服务器(数据)端的字符集和collation,可以分成四级逐层指定——server, database, table, column。当MySQL存取位于某一列(column)的数据时,如果column的字符集和collation没有指定,就会向上追溯table的;如果table也没有指定字符集和collation,就以database的字符集和collation作为默认值;如果database仍旧没有指定,那么就以服务器的字符集和collation作为默认值。
那么server的字符集和collation的默认值又是从哪里来的呢?答案是,配置文件(my.ini)和mysqld(或者mysqld-nt)的命令行参数中都可以指定。如果不幸的,你根本没有在my.ini或者命令行中指定,那么MySQL就会使用编译MySQL时指定的默认字符集——latin1。
但是,需要注意的是,如果安装MySQL时选择了多语言支持(一般用中文的都会选择吧),安装程序会自动在配置文件中设置default-character-set=utf8
这样,所有创建的数据库、表,除非明确指出使用其它字符集,都会默认的使用utf作为数据的字符集(同时使用utf8_general_ci作为默认collation,因为它是utf8的默认collation)。
相关系统变量
[2] 客户端。对于客户端传送来的literal string(例如INSERT,UPDATE语句当中的值),MySQL需要知道它们是什么编码。同时,MySQL返回给客户端的值(例如SELECT语句的返回值),也可以按照指定的编码返回。
相关系统变量
[3] 连接。用于连接的字符集和collation,是指MySQL在接受到客户端发送来的文本之后,转换成何种字符集,用什么规则进行比较。需要注意的是,如果是将文本和数据库中某个column的值比较,将优先使用该column的字符集和collation。
相关系统变量
【问题的分析】
有了上面的预备知识,我们就开始分析最初的问题:本来是应该作为UTF-8字符保存的数据,为什么到了数据库中就变成了“乱码”?而且这些乱码居然还能毫无问题地被wordpress显示?为什么一旦导入到租用的主机那里就不能正常显示了呢?
首先让我们来看一下,我家里的服务器上,MySQL的系统变量(System Variables)是如何设置的。
注意:因为一些系统变量是根据客户端不同而不同的,所以用mysql命令行登陆所看到的和PHP下看到的并不相同。此外,似乎也不能用PMA查看——似乎在PMA中也已经更改了默认的系统变量。因此,要查看PHP作为客户端时的默认系统变量,可以编写一个类似下面的PHP小程序:
其中$result就包含着所有系统变量。在我家里的服务器上得到了如下结果(以下只列出跟字符集有关的系统变量):
可见,默认的客户端编码、默认的连接编码是latin1——这也就是说,虽然实际上wordpress传递给MySQL的文本都是用UTF-8编码的,但是由于上述系统变量设置不当,这些UTF-8编码的文本被MySQL当作是latin1编码的,并且由于数据库本身是utf8,因此把这些“latin1文本”又转换成了utf8。这样,一个汉字居然需要6bytes(一个汉字作为UTF-8是3bytes,被当作latin1进行了转换,每个latin1字符转换成2bytes的UTF-8编码)。这就不难理解为什么数据库存储的是“乱码”了。
那么为什么这些“乱码”在wordpress显示时没问题呢?这是因为,character_set_result也是latin1,也就是说MySQL在取出数据交给wordpress时,把这些数据从utf8转换回latin1,然后wordpress将这些latin1又当作了utf8——正好是上面的逆过程。
那么,为什么到另一台服务器上面就无法正常显示了呢?请看看那台租用主机的系统变量设置:
可见,其默认的客户端编码是ujis。也就是说,MySQL把utf8数据取出后,将会转换成ujis并传递给wordpress。这经历了latin1 - utf8 - ujis转换的原本是utf8的字符,早已面目全非了……
【解决方案】
解决方案在很多论坛、网页上已经有提到了,在wordpress的trac也已经有人提出过。
但是在解决问题之前,我却很想知道一个问题的答案,那就是:这到底是MySQL的问题,还是PHP(特别是php_mysql extension)的问题,还是wordpress的问题?甚至是用户配置的问题?我倾向于认为这是一个wordpress的问题。因为无论MySQL还是PHP都不知道wordpress使用了什么字符编码,所以无法更改客户端字符集;而作为一般的wordpress用户,要求他们设置字符编码——可以,但是必须要提供一个用户界面,而不是直接修改源程序。
那么解决方案(或者说只是一个workaround)就是,修改wordpress的\wp-uncludes\wp-db.php。在第40多行的function wpdb中,在$this->select($dbname);之前添加一句
SET NAMES语句的功能就是,执行了SET NAMES 'x'相当于下面三条语句的功能。
这样,在默认客户端字符集是ujis的租用主机上,导入的wordpress文章也能正常显示了。当然,这不是彻底的解决方案——这只是“将错就错”,反正数据库里面存储的已经是被当作latin1而转换成utf8的utf8了,那么就将其转换回所谓的latin1就是了。这样做将使其他程序无法读取wordpress的数据,并且更重要的是,数据库中存储的“utf8数据”无法真正按照utf8应有的排序规则来排序。
那么最彻底的做法,就是在安装wordpress时就添加上面所说的SET NAMES语句,并且设置客户端的字符集为utf8:
但是这样做的话,已经被当作latin1写到数据库里面的文章就会无法正常显示了。要让他们正常显示,必须经过utf8 - latin1的转换。如果数量较多,可以考虑编写一个程序进行转换;数量较少的话……手动转换吧。
BTW,国内高手们汉化的中文版的wordpress中已经添加好这一句了,上面的信息只适用于那些使用英文wordpress的朋友,以及喜欢追根问底的朋友。
最后推荐一篇参考文章:Portable php-mysql connection charset fix