Blog

  • Cleanup after upgrade Magento to 1.4

    升级总有这样那样的问题,我不想节外生枝,也就不想在项目中途升级。我把一个正在开发中的 Magento 商店开始开发时版本是 1.3.2.4,但今天一不小心,敲入了

    ./pear install magento-core/Mage_All_Latest

    就把 Magento 升级到了 1.4。

    (要怪 Magento 升级太方便了?其实,今天的不小心是有原因的:我发现 Magento Connect 启动不了。尝试进入 Magento Connect 时通常要求再次输入管理员密码,但今天这个页面只有 Magento Connect logo,看上去页面意外中止。而我急着想试用一下某个 community extension,就用了 pear 命令。在敲键盘的过程中,脑筋没多想,竟然敲入 ./pear install magento-core/Mage_All_Latest,就坏事了。)

    升级以后发现后台 System/Configuration 保存时经常出错,错误提示:

    Error while saving this configuration: Invalid mode for clean() method

    CMS/Pages 也无法保存页面,错误提示:

    Error while saving Page. Please try again later

    Catalog/Products 也无法保存修改…

    我猜想所有这些错误都来自一个地方。错误多了反而好办——肯定他人也会碰到。果然,google 一下不怎么费力就找到答案:删除 app/code/core/Zend/Cache 目录就可以了。

    这证明了我的一个保证升级后不出错的土办法:先保持原有文件不动进行升级,然后把新老版本混合在一起的文件夹删除,然后再上传一份干净的新版本(被新版本抛弃的老文件就被删除了)。当然也可以用版本比较工具来找不同,但土办法更省事。

  • Dull_Addressfields Magento extension

    I could not edit customer order delivery address at backend using Magento 1.3.2.4. Checkbox “Same As Billing Address” is always ticked. When I untick it, it automatically tick itself. I was able to edit it some while ago. What happened since then?

    Half an hour later I found the problem was caused by installation of Dull_Addressfields 1.0. This extension was installed because I wanted to “Same As Billing Address” as a default choice at checkout. But even if I set “Default delivery address to billing address” to “No” without uninstall Dull_Addressfields. I upgrade Magento to 1.4. Problem remains.

    So I have to uninstall Dull_Addressfields. Actually, with the skills I acquired nowadays, I can make “Same As Billing Address” as a default choice without 3rd party extension. But my boss don’t want to make “Same As Billing Address” as a default choice. Situation changed since I installed Dull_Addressfields.

    Btw, Dull_Addressfields rewrites two classes: Mage_Customer_Model_Address and Mage_Sales_Model_Quote_Address. As Magento can’t safely handle two extensions rewrite a same class, I don’t want Mage_Customer_Model_Address and Mage_Sales_Model_Quote_Address to be rewritten by Dull_Addressfields (just to achieve a simple goal?). I’d like these important classes be untouched for future rewriting for important functionalities.

  • My third DNS server down for a month

    今年初,我退掉了 godaddy vps,把原来 godaddy vps 担当的辅助 DNS 角色转给了 1&1 server,却忘了开 1&1 server firewall port (硬件防火墙那道关)。

    今天才发现这台辅助 DNS 在过去的一个月根本无法执行 DNS 解析(因为我设置了三台 DNS,挂了一台没引起警觉)。难怪最近 webceo 给我的评分只有 5 分上下,google 的排名也有下降。

    赶紧打开防火墙,希望分数能上去。

  • Magento 1.4 is released

    说来也巧,当我想用 Magento 1.4 时,正好 1.4 stable 也刚发布。Magento 1.4 的测试版一直诱惑着我,就是太忙没去测试它,再说我也不太爱测试版,所以我一直抵制着诱惑等待正式版。

    今天在一台机子上搭建了一个全新的服务器环境,同时也有用到 Magento,既然是全新的,我就想装个 Magento 1.4 吧。在 Magento download 页面,找了半天,没发现 Magento 1.4 rc 版。好久才回过神,原来 1.4 在 full release 里,那就是说已经有正式版啦。我喜欢正式版不喜欢测试版,因为我不喜欢和一大堆的 bugs 打交道。既然 Magento 1.4 已经有了正式版,那还犹豫什么,赶紧装一个。

    Magento 1.4 stable is now available
    Magento 1.4 stable is now available

    安装碰到一点小问题,不清楚算不算 bug: 这次使用的是 zend server,默认 AllowOverride None,安装时却选择了使用 rewrite,安装完成后才设置 AllowOverride All,重启了 zend server,前台正常,却进不了后台,即使清空了 cache 和 session 仍无济于事。错误提示为:

    There has been an error processing your request

    我删了 Magento 数据库和 local.xml,重新安装一次,终于正常。

  • How to override a Magento controller

    Say I want to override Magento Catalog Product controller, say my namespace is Magex and my module name is Powercat.

    Firstly, add the following to Powercat/etc/config.xml. What it does is adding another route “prowercat” in addition to “catalog” to frontend. When http://mydomain/catalog/somecontroller/someaction/ is routered, powercat/somecontroller/someaction will be loaded before (see the attribute “before”?) catalog/somecontroller/someaction. If powercat/somecontroller/someaction is available, people get some page when visiting http://mydomain/catalog/somecontroller/someaction/ or http://mydomain/powercat/somecontroller/someaction/. I will not publish the latter url because the whole point of overriding a controller is reusing the Magento original url.

    <?xml version="1.0"?>
    <config>
    <frontend>
    <routers>
    <catalog>
    <args>
    <modules>
    <anyname before="Mage_Catalog">Magex_Powercat</anyname><!-- the tag name can be any name -->
    </modules>
    </args>
    </catalog>
    </frontend>
    </config>
    
    

    Secondly, create a subclass in the following format. “require_once” is essential because controllers are not auto-loaded.

    <?php
    require_once 'Mage/Catalog/controllers/SomeController.php';
    class Magex_Powercat_SomeController extends Mage_Catalog_SomeController
    {
    //function someAction here
    }
    ?>
    
    

    Last but not the least, put the overriding controllers into a dedicated module. Otherwise it is very easy to break Magento fallback mechanism. For example, suppose Magex_Powercat has two controllers.

    1. Magex_Powercat_IndexController does its own logic (not extending Mage_Catalog_IndexController, and
    2. Magex_Powercat_ProductController extends Mage_Catalog_ProductController

    The scenarios are:

    • When people visit http://mydomain/catalog/product/someaction/, powercat/somecontroller/someaction will be loaded – OK.
    • When people visit http://mydomain/catalog/category/someaction/, catalog/category/someaction will be loaded (because Powercat does not have CategoryController, Magento will fallback to Mage’s CategoryController) – OK.
    • When people visit http://mydomain/catalog/index/someaction/, if Powercat’s IndexController does not have someAction (only Mage’s IndexController has) – Error happens!
    • When people visit http://mydomain/catalog/index/someaction/, if Powercat’s IndexController does have someAction, page will load without error. However, remember Magex_Powercat_IndexController does its own logic? No router to the original logic!
  • Magent Events

    Magento wiki 上有篇文章列出了所有的 events dispatched by Magento。但 wiki as a CMS 自作主张地转化了某些字符,比如把

    ->转化成了→

    =>转化成了⇒

    这让我觉得有必要自己来格式化这篇文档。虽然曾想用 OpenOffice 来存储,但它竟然连一张简单的表格都格式得很费劲,最后只好用了 Ms Word。

    Download magento-events

  • Mysql varchar could be transformed to memo in Ms Access

    Mysql varchar could be transformed to memo in Ms Access when Mysql table is used as linked table inside Access, but I want to prevent this from happening. Mainly because I can not join tables with memo field.

    As I observed, long Mysql varchar field is transformed to memo field, but short one stays as text field. The maximum length of varchar is 85 which allows varchar stays as text field in Access.

    This limitation is quite touch, isn’t it?

  • Geo Adwords

    众所周知,Google Adwords 可以根据访问者的地理位置选择性地投递广告。几番实验后,我发现其中的选择性非常微妙:

    我帮朋友的外卖店 Oriental Takeaway 开启了 Google Adwords 账户。非常遗憾地,朋友和我都没有很多时间去 refine adwords settings,仅做了一些非常基本的选择性投递,其中一项就是“仅限 Brighton 地区”。A primary keyword to target is brighton chinese takeaway (btw, we decided to use it as domain), together with other hundreds of keywords.

    我选择了三个地点测试,Brighton, Worthing, and some place in Germany。

    • 在 Brighton google,所有在 keyword list 上的广告都能出线,按下不表。
    • 在 Worthing google,搜索 brighton chinese takeaway,广告竟然也能出线。我很惊讶,一开始以为 Google 使用的 geo 数据不够精确,导致广告误投递。再搜索 brighton takeaway,广告也能出线;再搜索 chinese takeaway,广告不出线。
    • 在 Germany google,搜索 brighton takeaway,广告也能出线;再搜索 brighton takeaway,广告不出线;再搜索 chinese takeaway,广告不出线。

    由此我得出结论,广告是否出线跟 Adwords 的地域设置、地域距离以及关键词与网站的关联性多因素决定。

    总的来说,Google 非常聪明,我非常喜欢。

  • Understand how tax is calculated in Magento

    彻底理解 Magento tax 是如何计算的不是一件容易的事。我做了无数次组合测试,算是了解 Magento tax 是如何计算的,但我未及去读代码,不敢说彻底摸透它的来龙去脉,写下这段文字权作日后参考。

    首先,零税率不用设置,因为如果什么设置都没有的话税率就是零,所以大多数时候只要针对非零税率进行设置。稍后会讲到什么时候需要进行零税率设置。

    其次,要分清以下名词:

    • tax rate 税率
    • tax zone 税区域
    • tax rule 税规则
    • customer tax class 顾客税种
    • product tax class 产品税种
    • shipping tax class 运费税种
    • customer group 顾客属组
    • customer address 顾客地址

    它们的关系是:

    1. 税计算的起点是税规则。换言之,如果后台设置了若干税率、税区域、顾客税种、产品税种、运费税种,却没有设置税规则,那么没有任何税生效。
    2. 只有税规则可以设置优先级,税区域无法设置优先级(除非去改数据库,id 越小的税区域优先)。
    3. 税规则是产品税种(或运费税种)、顾客税种、税区域在三维空间决定的。
    4. 后台界面设置税率和税区域在一个表单里,似乎税率由税区域直接决定,但并不一定能生效,原因见第3点。
    5. 税区域与顾客税种无关。这点曾是我困惑的地方,因为地址是顾客的一个属性,所以我很自然地认为顾客地址就对应税区域。如果尝试着放弃“顾客地址就对应税区域”思维,一切都容易理解了。
    6. 税规则的三维空间较难描述,可以这么来简化:先拿产品税种(或运费税种)和顾客税种交叉排列组合出多个税规则,再拿税区域去套,套中的税规则就生效,套不中就不生效。
    7. 顾客属组和顾客税种是多对一的关系。
    8. 在前台 checkout 时,Magento 会根据顾客地址启用一条或多条税规则,但后台 create order 时,Magento 的税规则却无视顾客地址。这或许是 Magento 的一个 bug。

    我以一个实例说明如何在 Magento 中设置 UK VAT。以我目前所知,within the UK, postcode JE 和 GY9 开头的区域可以免 VAT,其他区域需加收 VAT,英国以外免 VAT。因为我没有免 VAT 区域的完整列表,必须照顾到今后能随时添加更多的 postcode 进免 VAT 区域。我是这么设置的:

    • 因为产品比较单一,运费税率与产品税率相同,目前都是17.5%,所以只设一个产品税种,不搞运费税种,运费就沿用产品税种,便于理解。
    • 顾客税种分两种,普通和VAT FREE。顾客属组也分两种,普通和VAT FREE,一一对应便于理解。请注意,普通类不是指一定要收 VAT,而是按税规则的第三维——税区域智能决定税率;但免 VAT 类下的顾客一定是零税率,这么做是为了照顾后台 create order 时的 bug(姑且当是)。
    • 建若干个税区域, UK postcode JE* 税率为0、UK postcode GY9* 税率为0、UK postcode * 税率为17.%。其他国家的税率不需要一个建,因为没有税规则,那就是零税率。

    现在开始创建税规则,按优先级别排序:

    1. 产品税种全选(反正只有一个),顾客税种选普通,税区域选 UK postcode JE*和UK postcode GY9*
    2. 产品税种全选(反正只有一个),顾客税种选普通,税区域选 UK postcode *
    3. 产品税种全选(反正只有一个),顾客税种选VAT FREE,税区域选 UK postcode JE*

    前两条规则是为了前台 checkout,第三条税规则是为了照顾后台 create order 时的 bug。Magento 目前版本 1.4.0.1,后台 create order 若把顾客归类普通,bug 无视税区域的存在,也不管税规则的优先级,似乎只会挑一个最高税率。普通顾客税种有0和17.5%两种可能,后台却总是用17.5%去收税。为避免激发 bug,干脆,顾客税种选VAT FREE,VAT FREE税种只有一个可能,永远是0,只在后台使用。

    如果今后得知 UK postcode XYZ* 也免 VAT,那就再建一个税区域 UK postcode XYZ* 税率为0;然后进入已有的税规则1,税区域增选 UK postcode XYZ* 即可。虽然税区域 UK postcode * 创建在先,UK postcode XYZ* 创建在后,但税规则的优先级别保证了 UK postcode XYZ* 先于 UK postcode *。

    再说一个极端的例子,若 UK 免 VAT 的区域只有 JE* 和 GY9*,今后也不会增加了,那么先创建税区域 UK postcode JE*和UK postcode GY9*,后创建 UK postcode *,然后只需要第1条税规则里同时选中UK postcode JE*、UK postcode GY9* 和 UK postcode *,删除税规则2也可以保证 UK postcode JE*和UK postcode GY9* 的优先性。但这么做有悖 Magento tax 的设计思路,不可取。

  • Nginx try_files syntax

    今天在一台很久不用的服务器上测试 Magento search result page,URL /catalogsearch/result/?q=searchword,发现它不工作,但其他页面正常。这个症状让我联想到以前碰到的类似问题,Magento 无法获得 query_string,所以含问号的 URL 都不能处理,页面重定向到 referring URL。应该是 server rewrite 规则没有写正确,我想。打开 nginx 的配置文件一看,果然,当中一条规则用的是很久以前的写法,后来在不同的服务器上几经改进,production server 都已经用上了新规则。

    新规则的写法:


    location @magento {
    root $php_script_root;
    index index.php;
    if ($uri ~ ^/(media|js|skin)/) {
    break;
    }
    if (!-e $request_filename) {
    rewrite .* /index.php last;
    }
    }

    而老规则的写法:


    location @magento {
    root $php_script_root;
    index index.php;
    if ($uri ~ ^/(media|js|skin)/) {
    break;
    }
    try_files $uri $uri/ /index.php;
    }

    效果略有区别,我在 Difference of try_files to rewrite in Nginx 文章里有提及。不过,今天我还有一个新发现。

    我倾向于使用简介语法,try_files 就比 rewrite 简洁得多,难道 try_files 就没有办法应付带问号的 URL 吗?非也,是我不知道 Nginx 原本可以这么奥妙——用 $args 变量!

    因此,最新一条完美规则出炉了:


    location @magento {
    root $php_script_root;
    index index.php;
    if ($uri ~ ^/(media|js|skin)/) {
    break;
    }
    try_files $uri $uri/ /index.php?$args;
    }