No formatting inside html tag

我目前用的 WordPress 版本为 2.8.4。发现一个 bug,就是在 post html 源码编辑状态(非 Visual 编辑),输入时为

<div>
我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。

不知 WordPress 能听明白吗?
</div>

保存后查看一下数据库,post_content 忠于原文,前台输出时却经过 WordPress 的格式转换,变成了


<div>
我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。</p>
<p>不知 WordPress 能听明白吗?
</p></div>

html p tag 都不配对了。

这个 bug 不严重,因为 WordPress 自带的 TinyMCE editor 不会插入 div tag。TinyMCE 倒可以插入 b-quote,输入时

<blockquote>
我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。

不知 WordPress 能听明白吗?
</blockquote>

前台输出

<blockquote><p>
我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。</p>
<p>不知 WordPress 能听明白吗?
</p></blockquote>

完全正确,没有触发 bug。这个小 bug 让我联想到很多 wysiwyg editor 和 output formatting 时不尽如人意的地方。

我在 magento 和 phplist 里都是禁用 wysiwyg editor 的,在 WordPress 里虽没有禁用它,但我有意识地仅仅使用它的 htmlentity。象我习惯于手写 html 代码以求更多输出控制的人,觉得 WordPress output formatting logic 也不好,更好的逻辑是:

  • 如果post_content是有效的DOM,仅对最外层的 text nodes 作 html p wrapping,对任何 html tag 内部的不做任何处理。其他诸如 htmlentity filter 都不需要,因为 wysiwyg editor 已经作了这项工作。
  • 如果post_content是无效的DOM,WordPress 默认的 formatting 会作一些修复。我没考察过修复的智能程度,我的要求是一开始就不要写出无效的 DOM,所以不想多讨论这种情况下的处理。

举个例子说,我的 formatting logic 就是把

anything outside html tag is wrapped by p tag.

do not touch anything inside html. user should be responsible for it.
<div>
我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。

不知 WordPress 能听明白吗?
</div>

输出为

<p>anything outside html tag is wrapped by p tag
</p><p>
do not touch anything inside html. user should be responsible for it.</p>
<div>
我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。

不知 WordPress 能听明白吗?
</div>

UK airports postcode

Gatwick

  • South Terminal: RH6 0NP
  • North Terminal: RH6 0PJ

Heathrow

  • Terminal 1: TW6 1AP
  • Terminal 2: Closed until 2014.
  • Terminal 3: TW6 1QG
  • Terminal 4: TW6 3XA
  • Terminal 5: TW6 2GA

BAA Heathrow 网站上把整个 Terminal 2 版块都下线了,暂查不到 postcode。我认为在 Terminal 2 关闭期间,应该保留 Terminal 2 版块,只是在显著位置提示该 Terminal 处于关闭状态。我原本不知道 Terminal 2 处于关闭状态,因为在 BAA Heathrow 网站上根本找不到 Terminal 2 版块,我去了其他的新闻站点(其实是去年的旧闻了)才知道这条消息。

虽然各个机场的 postcode 很容易查到,但每去一次机场都 google 一次然后在 airport website 上翻好几页找 postcode 也挺烦,干脆我就在自己的空间记个笔记。

Email notification best practice

我见很多购物网站花很多力气组织语言,把邮件正文用日常交流的通顺的口气写出来,以为顾客会喜欢读这样的邮件,但我认为事与愿违。在快节奏的社会里,大家阅读任何邮件时已经习惯最多扫一眼,通知邮件类似于营销邮件,能得到顾客一眼的停留亦属非常奢侈。

比如,货物发运通知:

xxx先生,

我很高兴地通知您,你的订单xxx号的货物已交由xxx物流公司隔天送达。订单项下xxx商品预定数量xxx件,因库存不足,此批发送xxx件,余下的xxx件将于xxx天后发出。

谢谢惠顾!

通知中有很多重要信息,如果写进大段的文章,客人skim阅读时认为这是“你好我好大家好”式的寒暄,往往被忽略。因此,通知还不如写成bullet mark style:

  • 订单号:xxx
  • 物流公司:xxx
  • 预计抵达:隔天
  • 预定数量:xxx件
  • 已发:xxx件
  • back order: xxx件
  • back order ETA: xxx天

当然,以上纯bullet mark格式是另一个极端。在实战中,应以bullet mark展示重要信息为基本原则,适当组织可读的礼貌用语,通知邮件才会有理想的效果。

Understanding layout.xml syntax in Magento

I write this article about my understanding of layout.xml up to now. I use it to show you

  • How to add buttons in order/invoice/shipment/creditmemo without override Block class?
  • How to define an array as a parameter in layout.xml using <action method=”addButton”><param>array</param></action>?
  • How to assign any type of variables in layout.xml?
  • How to avoid overriding controller class but still get the result you want on a specific url?

as well.

All my understanding was inspired by the task: adding buttons in order view at backend to offer administrators extra order process facilities.

add-buttons-to-backend-order-view

I love Magento class overriding architecture, but as I have learned the downside of overriding (Basically, there is only one chance of overriding for each class among all extension classes. Therefore, the more classes overridden, the greater chance of conflict between extensions.), I create a principle for my programming in Magento – do not override Magento class unless there is no other way around it.

So, back to the task. It would be very easy to add the button if I had overridden Mage_Adminhtml_Block_Sales_Order_View. It could be

public function __construct() {
parent::__construct();
$this->_addButton('new_button', array(
'label'    => Mage::helper('sales')->__('New Button'),
'onclick'  => "window.open('{$this->getUrlForNewButton()}')",
))
;
}

However, I do not want to override Mage_Adminhtml_Block_Sales_Order_View. How about firing addButton method in layout.xml? I see an example of addTab at the left side

<block type="adminhtml/sales_order_view_tabs" name="sales_order_tabs">
<action method="addTab"><name>order_info</name><block>order_tab_info</block></action>
<action method="addTab"><name>order_invoices</name><block>adminhtml/sales_order_view_tab_invoices</block></action>
<action method="addTab"><name>order_creditmemos</name><block>adminhtml/sales_order_view_tab_creditmemos</block></action>
<action method="addTab"><name>order_shipments</name><block>adminhtml/sales_order_view_tab_shipments</block></action>
<action method="addTab"><name>order_history</name><block>adminhtml/sales_order_view_tab_history</block></action>
</block>

In the example, addTab method takes two parameters, one for tab name and the other for block name. Inside addTab method, it creates a block object from the block name, which is passed in as a string. In fact, the tag name <name> or <block> is not significant in layout.xml. It does not represent parameter name in method. But the position is significant – the first tag is passed into method as the first parameter, and ditto the followings.

Back to addButton method in Mage_Adminhtml_Block_Widget_Container. It takes at least two parameters, one as string and the second as associated array. So the layout.xml can be something like this:


<reference name="sales_order_edit">
<action method="addButton" translate="label">
<name>new_button</name>
<button_array><!-- this tag name is not significant -->
<label>New Button</label>
<onclick>window.open('{$this->getUrlForNewButton()}')</onclick>
</button_array>
</action>
</reference>

The difficult part is in <onclick> – it requires a variable of current order id. After some digging, I find another example:


<reference name="footer_links">
<action method="addLink" translate="label title" module="catalog" ifconfig="catalog/seo/site_map"><label>Site Map</label><url helper="catalog/map/getCategoryUrl" /><title>Site Map</title></action>
</reference>

So, I create a helper method to return url for new button and modify layout.xml to


<reference name="sales_order_edit">
<action method="addButton" translate="label">
<name>new_button</name>
<button_array><!-- this tag name is not significant -->
<label>New Button</label>
<onclick helper="mymodule/getUrlForNewButton"/>
</button_array>
</action>
</reference>

Unfortunately, it did not work. I assume the helper callback does not work if it is in the array. So I change the helper method to return an associated array directly as the second parameter for layout.xml and the final layout.xml is


<reference name="sales_order_edit">
<action method="addButton" translate="label">
<name>new_button</name>
<button helper="mymodule/getNewButtonSecondParam"/>
</action>
</reference>

It works perfectly. I am very proud of myself.

Some while ago, I overrode Mage_Catalog_ProductController just to register a variable in viewAction


public function viewAction() {
Mage::register('my_var', $value);
parent::viewAction();
}

Now I reviewing that approach stupid. I could register a variable using layout.xml without any class overriding!

cp -f does not switch off prompt?

我在 Fedora 下执行大量的文件拷贝操作,我使用了

cp -fr [SOURCE] [DEST]

我使用了 -f 开关就是不想被询问文件是否覆盖。但

cp: overwrite `(filename)’? 提示还是出现。

这是什么原因?原来 Fedora 带有几个 alias,可以用 alias 命名查看。

# alias
alias cp=’cp -i’
alias l.=’ls -d .* –color=auto’
alias ll=’ls -l –color=auto’
alias ls=’ls –color=auto’
alias mv=’mv -i’
alias rm=’rm -i’
alias which=’alias | /usr/bin/which –tty-only –read-alias –show-dot –show-tilde’

cp 被 alias 了!-i switch 优先于 -f,所以 cp -fr [SOURCE] [DEST] 其实是执行了

cp -ir [SOURCE] [DEST]

怎么才能让 overwrite 提示不出现呢?办法有二:

办法一是使用 unalias 解开 cp -i。但 cp 默认使用 -i 确实很不错,我不想放弃 Fedora 的体贴。于是——

办法二是使用
/bin/cp -ir [SOURCE] [DEST]

Absolutely secure online banking

一年前我老板中了个毒导致银行账号密码失窃差点损失7,800镑,之后他来问我公司应该建立怎么的安全制度(security protocol)。

我认为,要绝对的安全很容易,但往往增强安全的同时给用户日常操作带来不便,抵消了网上银行的优势。如何加强安全又不失方便性?我当时的解决方案是:

  1. 设立一台独立主机服务器,打上所有的安全补丁,启用必要的防火墙。服务器上只要有个浏览器,其他的都是不必要的。建议使用Firefox with Linux。
  2. 客户端以ssh+vnc方式连入服务器。
  3. 客户端连入服务器后,只能操作网上银行,不得浏览其他任何网站,也不得进行其他任何操作。

第2步是个技术活,可以保证客户端即使在中木马等病毒的情况下,病毒也无法探知客户端通过服务器中转的输入。除非病毒能监视并分析所有的键盘、鼠标操作。

第3步是关键,必须严格执行。安全隐患大多是人为的因素,如何用户没有好的安全意识,再好的技术支持也枉然。

但这个方案未能得到贯彻。没过几天老板好了伤疤忘了痛,仍用日常电脑登录网上银行。在我看来不复杂的绝对安全的方案对他来说还是太复杂了,没有方便性的方案就不是解决方案。如何保证网上银行的绝对安全作为悬而未决的课题在我脑袋里盘绕了一年,直到最近尝试了 Chromium OS,我看到曙光。

稍后谈谈我对 Chromium OS 的看法。

How to run p7zip

我还是不免有 Windows 式思维。为了能在 fedora 下解压 rar 文件,我

yum install p7zip

然后,找遍菜单,没有发现哪个可以启动 7zip 或 p7zip;在终端窗口敲遍可能的命令也没一个有效。我只好放弃,启动 windows 下的 7zip 解决问题。

过了好几小时,我启动 Archive Manager 去压缩一个文件夹,无意中发现 .7z 成了 Archive Manager 支持的一种格式。恍然大悟,原来 p7zip 不需要自己的 GUI。

当然,还有一个发现:p7zip 不支持 rar 文件,要在 fedora 下解压 rar 文件,得用

yum install unrar

当然, unrar 只解不压,不过我从不把文件压成 rar,也不建议别人使用 rar 格式,更不推荐 winrar —— 7zip 这么强,又免费,何苦用 winrar?!

Magento 1.3 cannot work with memcached 1.4

今天我注意到一个还未来得及升级的 Magento 1.3.2.3 的安装 down 了,原因是 Magento 1.3.2.3 无法和 memcached 1.4.4.2 协同工作(memcached 1.4.4.2 服务器环境自动升级带来的)。

错误提示如下

Notice: MemcachePool::delete() [memcachepool.delete]: Server 127.0.0.1 (tcp 11211, udp 0) failed with: CLIENT_ERROR bad command line format. Usage: delete [noreply]
(0) in /……/magento/app/code/core/Zend/Cache/Backend/Memcached.php on line 271

我按提示找到 Memcached.php 把第271行改为

return $this->_memcache->delete($id, 0);

仍旧无效。

尝试着升级安装到 Magento 1.4.0.1,能支持 memcached 1.4.4.2 了。

但网站布局变得乱七八糟,需要调整很多 template, layout 及 css 文件,这不是一时半会能完成的,只好暂用备份文件降级到 Magento 1.3.2.3,改用 apc backend cache。还好网站在单台服务器上运行,没涉及到分布式缓存,memcached 不是必需的。我装了 memcached 也没怎么玩它,缺乏掌控,暂时先搁置。

Customise Magento order number

After I put a lot of test orders into Magento, now I want to delete them for a clean start. I also want to shorten the length of order number, and the order number from various stores share the global increment. I want the similar settings on Matgento invoice, shipment, and creditmemo.

I have concluded these steps after some trials and errors.

Firstly, empty orders, invoices, shipments, and creditmemo by executing mysql commands:


TRUNCATE `magento_sales_flat_order_item`;
TRUNCATE `magento_sales_flat_quote`;
TRUNCATE `magento_sales_flat_quote_address`;
TRUNCATE `magento_sales_flat_quote_address_item`;
TRUNCATE `magento_sales_flat_quote_item`;
TRUNCATE `magento_sales_flat_quote_item_option`;
TRUNCATE `magento_sales_flat_quote_payment`;
TRUNCATE `magento_sales_flat_quote_shipping_rate`;
TRUNCATE `magento_sales_invoiced_aggregated`;
TRUNCATE `magento_sales_invoiced_aggregated_order`;
TRUNCATE `magento_sales_order`;
TRUNCATE `magento_sales_order_aggregated_created`;
TRUNCATE `magento_sales_order_datetime`;
TRUNCATE `magento_sales_order_decimal`;
TRUNCATE `magento_sales_order_entity`;
TRUNCATE `magento_sales_order_entity_datetime`;
TRUNCATE `magento_sales_order_entity_decimal`;
TRUNCATE `magento_sales_order_entity_int`;
TRUNCATE `magento_sales_order_entity_text`;
TRUNCATE `magento_sales_order_entity_varchar`;
TRUNCATE `magento_sales_order_int`;
TRUNCATE `magento_sales_order_tax`;
TRUNCATE `magento_sales_order_text`;
TRUNCATE `magento_sales_order_varchar`;
TRUNCATE `magento_sales_payment_transaction`;
TRUNCATE `magento_sales_refunded_aggregated`;
TRUNCATE `magento_sales_refunded_aggregated_order`;
TRUNCATE `magento_sales_shipping_aggregated`;
TRUNCATE `magento_sales_shipping_aggregated_order`;

Then, go to table magento_eav_entity_type, find entity order, invoice, shipment, creditmemo, change increment_per_store to or not to share global increment (default is 1 which means each store has its own increment), change increment_pad_length to shorten or longer numbers (increment_pad_length does not include prefix: 1 digit by default).

Then, empty table magento_eav_entity_store by executing:


TRUNCATE `magento_eav_entity_store`;

With an empty table, Magento generate default increment_prefix which is store id (with global increment setting, it is 0), and increment starts from 0.

I want the first order, invoice, shipment, creditmemo number look like 100001, I set magento_eav_entity_type.increment_pad_length at 5, and insert 4 records into magento_eav_entity_store.

Customise order, invoice, shipment, creditmemo number in Magento
Customise order, invoice, shipment, creditmemo number in Magento

And that’s it.

2010年6月22日更新:我想提醒一下,光有几个 store_id = 0 的记录不够保险。因为几天后,我发现每个 storeview 都有了各自的记录,显然 storeview 的记录优先于 default (store_id = 0)。increment_prefix 默认为 store_id,这让每个 storeview 有了不同的前缀,与我初衷(所有 storeview 统一使用 “1” 为前缀)相悖。我不清楚后来这些自动添加的记录是由什么事件引发的。既然 increment_prefix 被改了,我只好接受这个事实,改来改后会让后台订单号看起来没有连续性。

More data added to eav_entity_store
More data added to eav_entity_store

Comparison in PHP

除非使用 === 或 !== 进行比较,PHP 尽可能地将值转化为数字进行比较。因此,看到比较 “10” == “1e1” 的结果是 true,千万不要大惊小怪。

“尽可能”如何理解?

  1. 如果等式一边是数字,另一边是字符串,那么字符串一定要 cast to int 后再比较。因此,比较 “10” == 1e1 的结果是 true,比较 “10 ” == 1e1 的结果也是 true。
  2. 如果等式两边都是字符串,且字符串中均不含数学字符以外的字符(包括white space),那么两边的字符串会在 cast to int 后再比较;否则,直接比较字符串。因此,比较 “10” == “1e1” 的结果是 true,但比较 “10 ” == “1e1” 的结果是 false。
  3. 如果等式有一边或两边是数组,则先比较数组结构是否相同,再逐个比较数组元素。在比较数组元素时,索引不会 cast,但值按上述两条规则“尽可能” cast。因此,比较 1 == array(1) 的结果是 false,比较 array(“10”) == array(“1e1”) 的结果是 true,比较 array(10 => 10) == array(“1e1” => 10) 的结果是 false。