Update index after moving category in Magento

这介于 bug 与 usability issue 之间:

Moving category 使 category url 发生变化后,该 category 所属产品无法 add to compare,Compare, Compared, Viewed sidebars 统统不正常。

我在 Magento 1.4.2.0 上发现这个问题,或许早先版本一直存在这个问题。我认为它象 bug,是因为 moving category 后 Magento 不提示 reindex index。

解决办法也很简单,就是 moving category 后,用 System >> Index Management reindex 一下。

Magento sitewatch module

在 ebuyer 上关注 OCZ 120GB RevoDrive SSD 很久了,它的价格一直在 £245 左右,比第二选择 OCZ 120GB Vertex 2E SSD 贵出近 £70。RevoDrive 比其他 SSD 的 data transfer rate 要快一倍左右,但我吃不准这一倍的快速能否在我的电脑上体现,所以一直犹豫着。

今天中午无意中上 ebuyer 又看了一下,发现 RevoDrive 突然降到 £230,这降价让我觉得多时的观望得到回报,刺激我立马就买了它。

下午,收到 dabs 的 newsletter,其中提示 DrayTek Vigor 2820 从 £155.78 降为 £134.99。我三天前刚从 broadbandbuyer 买过 Vigor 2820n,当时还上 dabs 比较过价格,所以对 Vigor 2820 系列比较敏感。幸好当时买的不是 Vigor 2820,否则现在有遗憾了,broadbandbuyer 目前 Vigor 2820 的价格是 £146.81 (还要另加 £4 邮费,dabs free shipping)。

废话讲了一堆。我想做一个 site watch 的程序,对一些想买又不急着买的产品价格进行跟踪,一旦价位合适,就在第一时间动手。象 ebuyer 故意每天有事没事搞一点价格波动,通常幅度在一两镑,纯属吊人眼球,如果我真每天用眼球去看,感觉就被人牵着鼻子走(或许这是一种市场手段?只是我不喜欢),所以想到用程序去看,直到一个实质性降价的时刻再用眼球去看。

Price watcher 有人做过,但只能跟踪几个名气大的网站,不对我胃口。我也写过一个简陋型的 sitewatch,但被我跟踪的网站 layout 变动比较快,它们一改我就要改程序,说明我的程序写得不好,所以用了几天就不用了。今天重拾这个话题,这次我想把 sitewatch 做成 Magento module。虽然 sitewatch module 跟 Magento ecommerce 风马牛不相及,只用到了 adminhtml 一些 GUI 和 crontab,但基于 Magento framework 的开发可以省我不少时间。

Magento layout handle name is misleading

在 Magento app/design/frontend/base/default/layout/catalog.xml 文件里有一段让我费解的配置:

<catalog_category_layered_nochildren translate="label">
    <label>Catalog Category (Without Subcategories)</label>
    <remove name="right.reports.product.viewed" />
    <reference name="right">
        <block type="reports/product_viewed" before="right.permanent.callout" name="left.reports.product.viewed" template="reports/product_viewed.phtml" />
    </reference>
</catalog_category_layered_nochildren>

catalog_category_layered_nochildren,从字面上看,应该是只对 is_anchor == 1 并没有子目录的目录有效;但事实上,它对所有没有子目录的目录有效,所以我想,handle 应该取名 catalog_category_nochildren 更合适。Mage_Catalog_CategoryController::viewAction() 里有一段

if (!$category->hasChildren()) {
    $update->addHandle('catalog_category_layered_nochildren');
}

由此可见这里仅检验 hasChildren,未检验 getIsAnchor。

catalog.xml 里把 right.reports.product.viewed 移除又添加,重新添加后 block 叫 left.reports.product.viewed,但实际位置仍在右边。这么做有什么意义?我费神地看了半天,得出的结论是:

没有意义。大概 Magento 是借此演示一下 layout syntax。

虽然没有意义,但是影响还是有的,有这一段,nochildren 的 category page sidebar 排序为 Compare, Compared, Viewed;否则排序为 Compare, Viewed, Compared。

Sidebar in the sequence of Compare, Compared, Viewed
Sidebar in the sequence of Compare, Compared, Viewed
Sidebar in the sequence of Compare, Viewed, Compared
Sidebar in the sequence of Compare, Viewed, Compared

I am on KDE

两年前开始使用 Fedora desktop 时,我盯着 Gnome 和 KDE 看了半天,想不好到底用哪个。我决定用 Gnome,仅是因为 Gnome 在 Fedora 众多 spin 中排第一个,在我不熟悉 Fedora desktop 时,使用 Fedora “推荐” 的 Gnome 更容易度过难关。

我想我的决定没有错。只是,最近要更新 Fedora 到 Feora 14(我一直用最初的 Fedora 11 没跟上来),我又想起 KDE,不敢说我已摸透了 Fedora 的习性,但是不是可以换换口味?看了几个 KDE 的视频,非常的 tempting,于是决定一试。

试过几天,我直想说——真应该早点试上 KDE。

KDE 很华丽,但决不是 apple 产品徒有其表。默认状态任务栏(在 KDE 中叫 panel)在下方,跟我的使用习惯一样,在 Gnome 中我要调整老半天才能出一个顺我手的桌面。

KDE 也很稳定,我一直担心很眩的东西不稳定,这下看来担心是多余的。或许 KDE 消耗的资源比 Gnome 更多一些(这是我的猜想,我没研究怎么去 benchmark 它),但我的电脑也负担得起。

KDE 默认的 file manager Dolphin 功能也比 Nautilus 强。用Dolphin 能给 file 和 folder 注 tag,我估计 Dolphin 自己维护一个 tag 的数据库,tag 信息无法共享给其他程序,可作为我寻求多年 tag everthing 的解决方案,是很大的进步。

KDE widgets 还支持 keyboard shortcut,我把 Show Desktop widget 的快捷键定义为 Meta + D(等同于 Win + D),让沉寂两年的 Win 键第一次在 Linux 下派上了用场,好开心。

展示一下我的 KDE 桌面:

Gorgeous KDE Desktop
Gorgeous KDE Desktop

以下我安装 KDE 前后用到的命令(做个笔记而已,没有什么技术含量):
yum groupinstall KDE (用于 Fedora 14)
yum groupinstall “K Desktop Environment” (用于 Fedora 11)
yum install openoffice.org-writer openoffice.org-impress openoffice.org-base openoffice.org-calc openoffice.org-draw openoffice.org-math (这么装下载量比 yum install openoffice.org* 小了一半以上,但功能不缺)
yum install sshfs
yum install filezilla
yum install gcc dkms (required by VirtualBox)

但有两个问题一时解决不了:

  • 在 HP Linux Imaging and Printing (HPLIP) 下载了 HP LaserJet 1010 驱动程序,安装后仍无法打印,提示”cups-missing-filter”。想删了它重装,却删不掉。当初在 Fedora 11 Gnome 时是能打印的,按我的理解,不能打印不是 KDE 的问题,是 Fedora 14 的问题可能性大些。
  • 找不到 Google Pinyin for Fedora。Again,这不是 KDE 的问题,是我喜欢 Google Pinyin 甚过 Fedora 内置的两个拼音输入法。很奇怪,有人想到要做一个 Google Pinyin for Android,为什么没人想到要做一个 Google Pinyin for Fedora 呢?

Ajax Layer Navigation module for Magento

有人要我对 Magento layer navigation 做些小修改,我觉得修修补补没有意义,要做就做一系列的修改,于是按我觉得可以改进的方向,列了以下几点。

Add ajax load/change products on page loads or filter changes (TODO)

Add option to show/hide category filter

Add option to show price filter as slidebar (TODO)

Add option to control price accuracy

Add up to 3 sort by’s (TODO)
(Someone already asked for a second sort by)

Add option to show/hide sort by’s (TODO)

Add option to or not to merge sort asc/desc into sort by dropdown (TODO)

Add sort by’s position (TODO)
(Magento native code can control filter attributes’ position, but cannot control sort by attributes’ position)

Add recursive walk-through function to sort by settings for category and its child categories (TODO)

Keep pace with Magento

I tried Magento 1.4.2.0 RC1 today, and noticed it had added “Include in Navigation Menu” option in Manage Category.

I am very happy seeing this because I made it happen before Magento did. I call it “Exclude in Top Navigation”. I also have added an option “Exclude in Sitemap” which is not available in newest version of Magento.

So, I keep pace with Magento, and vice versa?

Install / uninstall Adobe Reader on Fedora

在 Fedora 上安装 Adobe Reader (最新版本 9.4) 本来是非常简单的一件事,很久以前我装过一次,今天在新系统上第二次装,竟然花了两个小时。

最主要的原因是我下载了错误的文件,我以为老马识途,没仔细看下载了一个 bin 文件,安装后尝试着执行被 SELinux 阻止。

Summary:

SELinux is preventing /opt/Adobe/Reader9/Reader/intellinux/bin/acroread from
making the program stack executable.

Detailed Description:

The acroread application attempted to make its stack executable. This is a
potential security problem. This should never ever be necessary. Stack memory is
not executable on most OSes these days and this will not change. Executable
stack memory is one of the biggest security problems. An execstack error might
in fact be most likely raised by malicious code. Applications are sometimes
coded incorrectly and request this permission. The SELinux Memory Protection
Tests (http://www.akkadia.org/drepper/selinux-mem.html) web page explains how to
remove this requirement. If acroread does not work and you need it to work, you
can configure SELinux temporarily to allow this access until the application is
fixed. Please file a bug report.

Allowing Access:

Sometimes a library is accidentally marked with the execstack flag, if you find
a library with this flag you can clear it with the execstack -c LIBRARY_PATH.
Then retry your application. If the app continues to not work, you can turn the
flag back on with execstack -s LIBRARY_PATH. Otherwise, if you trust acroread to
run correctly, you can change the context of the executable to execmem_exec_t.
“chcon -t execmem_exec_t ‘/opt/Adobe/Reader9/Reader/intellinux/bin/acroread'”
You must also change the default file context files on the system in order to
preserve them even on a full relabel. “semanage fcontext -a -t execmem_exec_t
‘/opt/Adobe/Reader9/Reader/intellinux/bin/acroread'”

Fix Command:

chcon -t execmem_exec_t ‘/opt/Adobe/Reader9/Reader/intellinux/bin/acroread’

SELinux 非常友好,但我这次尝试着它的建议 chcon -t execmem_exec_t ‘/opt/Adobe/Reader9/Reader/intellinux/bin/acroread’ 无济于事。

最后,我重上 adobe 网站,发现还有 rpm 可供下载,有 rpm 当然用 rpm 啦,这次安装一次成功。

PS: 如要卸载,yum erase AdobeReader_enu

How is customer_logged_in/out handle added to Magento layout?

I was wondering how Magento merge or handle into handle in layout.

I was expecting something like

<default>
    <update handle="customer_logged_in" if="logged_in"/>
    <update handle="customer_logged_out" if="logged_out"/>
</default>

Obviously, there is not such codes in layouts.

I searched within Magento folder for keyword “customer_logged_in”, but only occurrences I could find in layout/customer.xml.

So, how is customer_logged_in/out handle added to Magento layout?

At last, I found

class Mage_Customer_Model_Observer
{
    public function beforeLoadLayout($observer)
    {
        $loggedIn = Mage::getSingleton('customer/session')->isLoggedIn();

        $observer->getEvent()->getLayout()->getUpdate()
           ->addHandle('customer_logged_'.($loggedIn?'in':'out'));
    }
}

by tracing addHandle() in app/core/Mage/Core/Model/Layout/Update.php.

I must say using observer adding layout handle makes very easy for other modules to detect customer login status.

Never use dotless domain for Magento installation

Magento 说不要使用 localhost 安装,我也就不用,也没深究原因。

我为了在本地调试一个网站,先拿了生产网站 euro-bags.eu 的一个子域名。后来该子域名被挪作他用,那时我想,如果换个子域名再被挪作他用,我干脆用一个用于域名保护而未建站的域名,于是开始用 euro-bags.com 来调试。

没过很久,Euro Bags 要在生产网站上实施一条网站提速实践:静态文件使用单独的 cookieless domain。因为 euro-bags.eu 使用了顶级域名,顶级域名上使用了 cookie,那么子域名上无法实现 cookieless。Euro Bags 也不想依赖第三方服务 cookieless domain,于是启用 euro-bags.com 专门为生产网站提供静态文件。

我的调试域名再次被挪作他用。为了防止此类不可预测的事情发生,我想到干脆用一个不可能用于生产网站的域名来完成调试,于是想到用 euro-bags。

一开始没发现问题,用了几天后想登录 euro-bags admin area 发现登录不了,输入密码后总是弹回到登录页。

百思后才得其解,原来在 Magento 里使用任何 dotless hostname 都犯了跟使用 localhost 同样的禁忌。Magento 基于安全考虑在 cookie 里打上 domain 烙印,而大部分浏览器用拒绝接受含 dotless hostname 的domain 烙印。虽然可以 comment out app/code/core/Mage/Core/Model/Session/Abstract/Varien.php 某些行让 Magento 在 cookie 里不打 domain 烙印,从而让浏览器接受它的 cookie,但我觉得这么做是南辕北辙:不含 domain 烙印的 cookie 固然通行,但 open to cookie hijacking。Magento 最值我称道的就是每个细节(我没见其它 ecommerce software 有如 Magento 的周到)。

既然 Magento 要在 cookie 里打 domain 烙印,而大部分浏览器又不接受 cookie 里有 dotless hostname,那我就不要用 dotless hostname 来安装 Magento 不就得了。于是我最新启用euro-bags.net 来继续本地调试,因为未有迹象表明 Euro Bags 要把 euro-bags.net 纳入域名保护,也没有要用这个域名来提供服务的计划,所以我不用再频频更换调试域名了。

Non working day calculation is included in msdk module

Although I am not ready to release msdk module for Magento, I finish a model called Qian_Msdk_Model_Nwd. It is to judge if a given date is a non working day by some pre-defined and user-defined rules. It can also work out what date of next n’th working day is.

There are two types of rules, rule by date or rule by week. For Non Working Day (NWD) fixed by date, there is normally a make-up following working day as NWD if the original one is Saturday or Sunday. For NWD fixed by week, there is no make-up, usually.

For the rule simplicity, I always assume make-up. When you are writing your own rules and do not want make-up to some rules, add “=” modifier to the beginning of each of them.

How to read and write NWD rules?

First thing to remember, as a general checking rule, I go through each line separated by “\n”, and verify if the supplied date (if omitted, assume today’s date) fall into the line of rule. There is no connection between lines, so do not expect a rule can make up another one.

Second thing to remember, as PHP allows a date like “2010/13/33”, I allow you write rules like that too. However, avoid it if you can.

Third thing to remember, PHP regards Sunday as 0 Saturday as 6, and I do the same.

Fourth thing to remember, when ISO number weeks, it regards week starts Monday, and I do the same. If you do not know what I am talking about, read ISO-8601.

Fifth thing to remember, I do not have a good algorithm to work out dates by lunar calendar. So that is no smart way to write a rule for Easter. Ditto many Chinese holidays. You have to use fixed date every year. I hope it won’t cost you long time write down all fixed dates of 10 years lunar NWD and just sit while this programme runs for 10 years. After 10 years, aha, you’ll find your way.

OK, let’s get on to rule writing.

1. Rule by date is something like “yyyy/mm/dd”, or “yyyy/mm/dd,dd,dd”. yyyy, mm or dd can take asterisk “*” as wild-card.

For new year day, it is “*/01/01”. This is simplest scenario. For Christmas and Boxing day, it is “*/12/25,26”. Be careful here, do not write two rules “*/12/25” and “*/12/26”. They can not achieve the same result as “*/12/25,26”, because a rule can make up a following working day if the rule date is Saturday or Sunday, but it can not exclude days by other rules.

You have to write in one rule if two or more NWDs are connected. However, the dates are not necessarily continuous. In theory, you can write a rule like “*/12/24,26”. In reality, I did not see any NWDs are scheduled like this.

2. Rule by week is something like “yyyy/mm/dd/weekNumber/weekDay”, or “yyyy///weekNumber/weekDay”. yyyy, mm or dd can take asterisk “*” as wild-card. “yyyy///weekNumber/weekDay” use ISO-8601 to find the week and weekDay. I will cover it in section 3.

“yyyy/mm/dd/weekNumber/weekDay” uses a reference year/month/day to find the n’th weekDay. N is specified by weekNumber.

2.1 If both mm and dd are “*”, it uses a reference year. For example, “*/*/*/10/1” means 10th Monday of any year.

2.2 If mm is a number, but dd is “*”, it uses a reference month. For example, “*/05/*/1/1” means 1st Monday in May. A lot of England bank holidays are specified this way.

2.3 If dd is a number, it uses a reference day. For example, “*/06/06/0/1” means the Monday of the week where June 6 sits.

In case of 2.1 and 2.2, weekNumber can take a negative number which means the last n’th weekDay. For example, “*/05/*/-1/1” means the last Monday in May. WeekNumber zero does not make sense here.

In case of 2.3, weekNumber can take zero (means the same week), a positive number (n’th week forward), or a negative number (n’th week backward).

3. “yyyy///weekNumber/weekDay” ISO-8601 style looks similar to case 2.1. But they are totally different.

For example, “2011///1/6” means Saturday of week 1 of year 2011, which is 2011/01/08. But “2011/*/*/1/6” means the 1st Saturday of year 2011, which is 2011/01/01.

Another example, “2010///52/6” means Saturday of week 52 of year 2010, which is 2011/01/01 (Yes, it goes into the following year). But “2010/*/*/52/6” means the 52th Saturday of year 2010, which is 2010/12/25.

I think writing documentation helps programming. I have revised NWD documentation for 3 times or more, and every time I found a new way to structure the model.

I also think any date calculation look simple but actual coding is very complex. This model took me half a year to finish although I am full time on it.