Magento site was compromised

It was my first time to see a hacker is close to me.

I was flooded with notification emails from Google Adsense subjected “Your AdSense Publisher Policy Violation Report” from yesterday afternoon. I immediately tried to visit either or to see what was going on. I got a scary warning message saying either site contains malware by Chrome and Firefox (on desktop. Chrome for Android does not do safe browsing check. I don’t know if Firefox does so.)

It took me a while to find out which site contains malware. As was using AdSense but wasn’t, at first I thought it was that were hacked. was powered by WordPress, so I went through all the plugins and compared with my other WordPress sites, I didn’t find a suspicious plugin.

Then I started to look into the main domain site, which was installed with Magento. It took me quite a while for me to know there was a good tool from to check what malware was on the site.

Then it took me another while to find out how the hacker did it. Basically the hacker compromised one of admin’s (not mine) password, and add the following code in three places (design/footer/copyright, design/head/includes, design/footer/absolute_footer) in Magento System >> Configuration.

<iframe src=”″ frameborder=”0″ width=”0″ height=”0″></iframe><iframe src=”″ frameborder=”0″ width=”0″ height=”0″></iframe> and are malware sites. And if a site links to them, it is regarded as malware site as well. 

When I disabled access of compromised admin accounts (without removing the injected code), I found Chrome and Firefox stopped giving that scary warnings, and visits to or are redirected to Google. I don’t understand how Google can take control of these domains so quick, but anyway, it is not my business.

At last I removed all malware codes and requested AdSense a review of

FreePBX 13 does not work on php 7.0.1

前几天安装 Magento 2,竟然说要 php 5.5 以上版本。本不追新的我,想到反正要用到 remi repo,那就装个最新的 php 7.0.1。结果 Magento 2 倒是能运行,但 FreePBX 13 的 web UI 出不来了,显示成空白页。我估计是 FreePBX 所用的 php 语法不够规范,简单排查了一下,根本没看出是哪里的毛病。我估计对 php 7.0.1 来说,FreePBX 到处是毛病。虽然此时 asterisk 仍正常运行,但我不能让 web UI 长期瘫痪啊,赶紧找了个时间拆了 php 7.0.1,换上了 php 5.6。这是目前兼容性最好的版本,FreePBX 和 Magento 2 均能运行,证明对 php 环境不能过于追新。

Back to MySQL

不知从什么时候开始,Fedora 和 CentOS 里的 MySQL 都给换成了 MariaDB。我一直搞不懂好端端的 MySQL 为什么还要分出一个 MariaDB,更搞不懂 MariaDB 的优越性在哪里,反正 MySQL 对我来说够用了,倒是 MariaDB 安装和启动时使用了不同于 MySQL 的名称,让我很不习惯。比如

yum install mysql-server


yum install mariadb
service mysqld start


service mariadb start

幸好,除此之外,一切照旧,用到 MySQL 的 PHP 程序都能继续在 MariaDB 使用,所以我就一直用着 MariaDB。直到今天使用 Magento2,要求 MySQL 5.6 以上版本,而 CentOS 下把 MariaDB 用 yum update 到最新版本,仍是 5.5.40。

于是我被逼着去下载 MySQL 5.6。不用不知道,一用发现 CentOS 下安装 MySQL 超级方便,只要安装好 RPM 包——,就可以恢复使用

yum install mysql-server


早知如此,我真不该碰 MariaDB——这个并没有给我带来实际利益的程序也花了我一天去熟悉。

Magento not rebuilding image cache

I changed a server for Magento sites. When I migrated the sites, they looked fine. However after I flushed Magento image cache, all sites stopped working. Web pages were not completed. The code stopped rendering after the first product image’s “src”. But there was no error message afterwards.

At first I thought it was file permission problem. But it was not.

Then I thought it was some rubbish left over after flushing cache. So I took the advice by removing the folder media/catalog/product/cache and clearing everything under var. But the problem was still there.

Then I realised it was php not generating images for Magento. Magento requires php-gd to generate images. My new server did not have php-gd installed. If I was installing a Magento instance, I would not get through. But I migrated the sites. So they “looked” fine.

After installed php-gd, product images came back.

By the way, Magento requires some other PHP extensions to run. I took the chance to install them all.

When to use Magento layered navigation?

Rcah module for Magento
Rcah module for Magento

Although I released a Magento module “Root Category as Homepage“, which enables replacing the default CMS page with the root category as homepage, thus the users can start layered navigation from the very top level of category, the usage of this module should be limited, for the sake of usability.

In short, use Magento layered navigation only to narrow down the choice of products. Do not use it for the purpose of grouping products by some filter attributes. There are many other ways to group products together.

I will explain in details when I have some more time.

Change PHP directives in Magento

Today a Magento store owner asked me why he could not create a new attribute set based on an existing one. I took a look into his store and found out the existing attribute set has many custom attributes, which led to new attribute set creation would take more than 30 seconds. But 30 seconds is the default PHP max execution time, which is set in php.ini and has not been optimised for Magento backend operation.

My immediate reaction was changing the default value of max_execution_time to 120. I added a line in /etc/php-fpm.d/www.conf, where is my preferential place to set server wide PHP directives rather than /etc/php.ini.

php_admin_value[max_execution_time] = 120

After a while, I had another thought. I do not like to extend PHP max execution time for most of other websites on the same server. Even for the Magento store, longer execution time is only needed for the backend operations. So how can I specify different PHP directives for different websites? Particularly, how can I specify longer execution time or larger memory limit for Magento admin users?

My first idea was creating separate php-fpm (Yes, I am using php-fpm.) profiles for this purpose. However, I rejected this idea after 2 seconds, because:

  1. I do not like to create more PHP daemons into the valuable server memory.
  2. If Magento frontend and backend are on the same domain, I have no way to assign a corresponding daemon.

So, can I implement changing run-time PHP directives using a Magento module? Of course. I had a Magento module called Msdk (Magento SDK). I just need to add this feature to Msdk.

Firstly, I need to find a proper event to catch. The event should be dispatched only once per request, and earliest possible when it can distinguish frontend or adminhtml route. The earliest event is “controller_front_init_before” except some database events, which are not recommend to catch anyway. But “controller_front_init_before” can not distinguish frontend or adminhtml route, i.e. it can only be put into global section in config.xml. Following the event timeline, I find “frontend or adminhtml route” is the best event to catch.

Secondly, I put one “controller_front_init_before” event catching into frontend section and another into adminhtml section in config.xml of Msdk module.

Thirdly, write two observers in a Helper or a Model to apply PHP directives.

Lastly, modify system.xml to allow users to set PHP directives in Magento System Configuration.

It sounds complicated but actually not. I finished it in a couple of hours.

BTW, Msdk is one of my free modules. But I am not going to launch it now with only one new feature. Watch the space for updated news.

Favicon Controller in Msdk

昨晚在 Magento 里写了个 FaviconController.php,是为了解决 Magento 根目录下的 favicon.ico 静态文件无法根据多网店不同的 favicon.ico 而变化。

Magento 有支持多网店多 favicon.ico,但直接访问 和 怎么回应不同的 favicon.ico?我是把静态文件删了,由 FaviconController.php 读取 SystemConfig 从 media 或 skin 目录里读出相应的 favicon.ico,响应 /favicon.ico 的请求。

逻辑很简单,我调试好了以后决定把这个功能归于我的 Msdk 模块,可是天黑眼花,竟然塞错进了另一个不相干的 Misc 模块,于是怎么看 /favicon.ico 都是 404 Error。这事折腾了有2个小时才发现错误所在。我痛下决心,要把 Misc 模块改个名。

P.S. Msdk 模块是免费的。下次发布新版大家就会看到 FaviconController.php 了。

Using Nivo Slider with Prototype

我想要一个 jQuery slider plugin 用在 Magento 上。jQuery slider 类不乏优秀的程序。我不及精挑细选,用了 Nivo Slider。

虽然 slider 能运行,但 FireBug 停止响应。查错发现 too much recursion occured in prototype.js 1132 行。自然想到了 jQuery 和 Prototype 的 conflict,但我有做 noConflict.js,而且 jquery.nivo.slider.js 也没有在全局使用 $,看上去挺规范的呀。

我让 Magento 暂不加载 prototype.js,too much recursion 错误就没有了,但 Magento 不能没有 prototype.js (盼望 Magento 哪天自动放弃 prototype,用 jQuery 重写)。

我有意放弃 Nivo Slider,无意中又发现别人竟做过同样的事情——把 Nivo Slider 用在Magento 下,而且没有 javascript 错误。

我以为是我那段 slider 的 html 写得不好,全盘拷了别人的来用,还是出错。然后我认为是版本问题,可是用别人的 jQuery 库,放在我这里,还是出错。

最后,直到最后,我意识到一点不同之处,我用不带任何参数启动 Nivo Slider,即



        effect: 'sliceDown', 
        slices: 15, 
        animSpeed: 500, 
        pauseTime: 3000, 
        startSlide: 0, 
        directionNav: true, 
		        directionNavHide: true, 
		        controlNav: true, 
                keyboardNav: true, 
		        pauseOnHover: true, 
                captionOpacity: 1, 
        prevText: 'Prev', 
        nextText: 'Next' 

就这点造成了 too much recursion。

Can’t retrieve entity config: core/store_group

老版本的 Magento ( 跑在新版本的 php (5.4.3) 上有个问题:

Can't retrieve entity config: core/store_group

这问题有点莫名其妙,最后发现是因为 SimpleXML 这个库太老了,在新版本的 php 下无法处理 Magento 的一些 xml 文件,所以出错。

从新版本的 Magento ( 里拷贝出 lib/Varien/SimpleXML 覆盖原文件就好了。

Manage gallery image with customised URL in Magento

Magento 后台对产品图片的管理操作非常简单,直接在浏览器里从本地上传,然后指定哪个作大图(image)、哪个作小图(small image)、哪个作缩略图(thumbnail)。这种傻瓜式的操作有三大缺点:

  • 不适合大批量图片的管理;
  • 图片上传后图片的存放位置乱序,不方便远程文件管理。(abc.jpg 上传后被存放在 /media/catalog/product/a/b/abc.jpg。如果之前已经有同名文件,新上传的文件自动更名为abc_1.jpg, abc_2.jpg,以此类推。)
  • 搜索引擎会从图片的 URL 里获取图片的部分信息,杂乱的 URL 不利于图片 SEO。

我认为最理想的图片管理模式是:在本地按产品分类分级维护一个图片库,用 FTP 上传到服务器,在 Magento 后台可以浏览这些文件(对 Magento 来说是本地文件),然后为某个产品选定它的大图、小图和缩略图。这样 Magento 里保存的图片位置信息就保持了自定义的产品分类信息。

Magento 的自动缩放图功能很好用,但自动缩放图生成的文件 URL 又臭又长,肯定不利于 SEO,而且服务器硬盘上留下一大堆乱序规则生成的文件夹,实在难看(有种屋子没打扫的感觉)。鉴于一个优质的电子商务网站本应该对整站的图片大小有统一的规范,不妨在本地制作好小图和缩略图,不依赖 Magento 的自动缩放图功能。

好多年前我就想做个 Magento module 来优化 Magento 的图片管理,但事务繁忙,也不知道什么时候能静下心写代码。与其让听众苦等我的 module,不如我介绍一下 Magento 数据库中 gallery 的结构,让大家懂得直接操作数据库去搭 product 和 local images 之间的桥。


一是查好每个产品的 ID 备用。如果人可以 SKU 识别产品,那就准备一张 SKU – ID 的一一对应表;如果人可以 product name 识别产品,那就准备一张 product name – ID 的一一对应表。

二是把每个产品的大图、小图和缩略图命名得有意义,比如是大图是 product-name-1.jpg, product-name-2.jpg, product-name-3.jpg,小图是 product-name-s.jpg,缩略图是 product-name-t.jpg(因为 small 和 thumbnail 不是产品的关键字,所以没必要拼写完整,用自己人能看明白的代号就可以)。FTP 上传图片至 /media/catalog/product/category-name/sub-category-name/SKU/,一个产品的图片归在一个文件夹下。

现在开始正式操作数据库。操作涉及到 4 个产品属性(attribute)和 4 张表(table)。

4 个产品属性:image, small_image, thumbnail, media_gallery.

4 张表:magento_eav_attribute, magento_catalog_product_entity_media_gallery, magento_catalog_product_entity_media_gallery_value, magento_catalog_product_entity_varchar.

第一步:在 magento_eav_attribute 中查出 4 个产品属性的 attribute_id。在我碰到的 Magento 早期版本中,4 个产品属性的 attribute_id 分别是:

  • image: 70
  • small_image: 71
  • thumbnail: 72
  • media_gallery: 73

在最新的 中,4 个产品属性的 attribute_id 分别是:

  • image: 85
  • small_image: 86
  • thumbnail: 87
  • media_gallery: 88

当然可以顺便看一下 product 的 entity_type_id,不出意外的话,这应该是 4。后面会用到。

第二步:在 magento_catalog_product_entity_media_gallery 插入记录。一张图片就是一条记录,插入记录就是定义产品和图片之间一对多的关系。

Insert record to magento_catalog_product_entity_media_gallery
Insert record to magento_catalog_product_entity_media_gallery

magento_catalog_product_entity_media_gallery 中各 column 的意义是:

  • value_id:记录 ID,可以留空让数据库自动生成。
  • attribute_id:media_gallery 的 attribute_id。
  • entity_id:产品 ID。
  • value:文件存放位置信息(略去 /media/catalog/product 部分)。
Gallery records for one product
Gallery records for one product

做完这两步就可以在 Magento 后台 Manage Products 的 Images 那一页上看到属于这产品的图片。后面几步可以移至 Magento 后台完成。我继续介绍如何直接操作数据库,是让大家知道如何用数据库的导入功能去批量处理。

第三步:在 magento_catalog_product_entity_media_gallery_value 插入记录。这等效于在 Magento 后台为每个图片在各个商店设定 Label, Sort Order, Exclude 值。

如果只有一个商店,一条 magento_catalog_product_entity_media_gallery 记录就对应一条 magento_catalog_product_entity_media_gallery_value 记录。

如果有多个商店,default store_id 就是 0,先按一条 magento_catalog_product_entity_media_gallery 记录对应一条 magento_catalog_product_entity_media_gallery_value 插入记录。假设另有两个商店,store_id 分别是 1 和 2,store_id 1 沿用 store_id 0 的 default 值,store_id 2 则使用一组不同的 label/position/disable 值。这样,不需要为 store_id 1 多插入一条记录,因为 Magento 的 Website/Storegroup/Storeview 的规则是没有额外记录就是使用 default value。只需要为 store_id 2 多插入一条记录,这条记录优先于 default value,但只为 store_id 2 而生效。

magento_catalog_product_entity_media_gallery_value 中各 column 的意义是:

  • value_id:匹配 magento_catalog_product_entity_media_gallery 的记录 ID。
  • store_id:商店 ID。
  • label:图片说明。
  • position:图片排序。
  • disabled:0 就是不 Exclude,1 就是 Exclude。
Insert record to magento_catalog_product_entity_media_gallery_value
Insert record to magento_catalog_product_entity_media_gallery_value
Disable (Exclude) small image and thumbnail
Disable (Exclude) small image and thumbnail

第四步:在 magento_catalog_product_entity_varchar 插入记录。这等效于在 Magento 后台为每个产品在各个商店设定哪个是默认大图(大图可以有多张,只有一张是默认的)、哪个是小图、哪个是缩略图。

magento_catalog_product_entity_varchar 中各 column 的意义是:

  • value_id:记录 ID,可以留空让数据库自动生成。
  • entity_type_id:不出意外的话,应该填 4。
  • attribute_id:image/small_image/thumbnail 的 attribute_id。
  • store_id:商店 ID。
  • entity_id:产品 ID。
  • value:文件存放位置信息(略去 /media/catalog/product 部分)
Insert record to magento_catalog_product_entity_varchar
Insert record to magento_catalog_product_entity_varchar

假设以前用 Magento 后台上传过产品图片,删除了,保存产品,这时,服务器硬盘上的图片不会随之删除,数据库里的 image/small_image/thumbnail 记录也不会随之删除(只是 value column 的值被 NULL 代替)。这也是我不喜欢用 Magento 后台来管理产品图片的一个原因。

Duplicate record error
Duplicate record error

这些 NULL 值的记录会导致插入不成功,因为 magento_catalog_product_entity_vartype 有规定 entity_id, attribute_id, store_id 三值组的唯一性。不让插的话编辑原记录。

Search by attribute_id and entity_id
Search by attribute_id and entity_id
Search result by attribute_id and entity_id
Search result by attribute_id and entity_id

或者,把原有的 image/small_image/thumbnail 记录全删了,再插。

Search by attribute_id
Search by attribute_id
Search result by attribute_id
Search result by attribute_id

这四步做完后,Magento 前后台就显示了指定的图片。

Magento GUI manage products images
Magento GUI manage products images
Magento store front product page
Magento store front product page

这时 Magento 的自动缩放图功能仍在生效,需要修改模板让 Magento 直接使用指定的大图、小图和缩略图,这里不再多述。