Browsed by
Month: January 2009

Zdmultilang v.1.2.2 has been released

Zdmultilang v.1.2.2 has been released

A new version of zdmultilang has been released. You can read more on the official website.

I did look at the changes that do not jeopardize the work I had done arround the improvment of the widget support. However, I did a ‘diff’ of the 2 versions so I can provide a new file: zd_multilang with support for multilingual widgets.

This should supports installation on a v.1.2.2 or less version of zdmultilang.

Work around the zdmultilang plugin

Work around the zdmultilang plugin

I’ve been testing several multilingual wordpress plugins (xLanguage, qTranslate and Gengo), before I stopped on zdmultilang.

This fit what I was looking for: the ability to manage translated pages / articles with a special relationship with the original. Moreover, in order to best meet the plugin spirit, I was looking for something that leaves the platform directly operational in the event of cancellation.

For me, it only lacked a multilingual widget management. So I tried to add this functionality as I describe here.

The standardization of widgets

WordPress always uses the same pattern to store widgets data: it is a parameter name / value hash array that is stored in the wp_option table through the get_option and update_option functions. Also, in the first instance, I modified the zdmultilang language switcher widget to comply with this standard.

On the road to options by language

Zdmultilang already manages language options: blogname and blogdescription in the wp_zd_ml_langs table. However, if we want to manage widgets by language we will have to change the architecture. My choice was to create the wp_zd_ml_options table whose structure is based on the wp_options table where I added the LanguageID field. This table contains all the options I wanted to keep in a specific language. So, I put there blogname and blogdescription as this place now seemed legitimate.

Exemple de la table wp_zd_ml_options

How to build dynamic hooks for our widgets?

As said before, the parameters of the widgets are stored in the wp_options table. So, in order to stay as closed as possible to the WordPress architecture, it seemed appropriate the use the pre_option_... and pre_update_option_... hooks (where ... is the name of the widget) provided by the get_option and update_option functions. The use of the “pre” hook is nice as it exits the initial get_option or update_option functions almost immediately. On the other hand, this requires to completly rewrite the initiale functions inside the plugin. For consistency, most of the functions regarding options have been rewritten: get_option, update_option, add_option, load_alloptions. It always has been done using the initial WordPress functions to take advantage of caching tools as alloption and notoption avoiding to much queries to the database. This is especially useful in our case. Indeed, widgets do not always have parameters and there is no way we can get this information. So we’re going to register as much shortcut filters as widgets based on the only information we have: the $wp_registered_widgets array. Thus, if a widget does not have a parameter, we should exit very quickly thanks to notoption. That way the database shouldn’t be stressed.

Then, we still have to register our new zd_get_option and zd_update_option functions to the hooks identified above. In order to accomplish this, we get inside the $wp_registered_widgets hash array and for each widget we build a dynamic function that we register to the hook. A sample of code :

foreach($wp_registered_widgets as $widget_name => $widget) {
	$funcname = 'zd_multilang_get_option_'.$widget['classname'];
	$function = "function $funcname".'() {
			$option=str_replace("zd_multilang_get_option_","",__FUNCTION__);
			return zd_multilang_get_option($option);
			}');
	eval($function);
	add_filter('pre_option_'.$widget['classname'], $funcname);
}

Here is an exemple with the widget-text :
Imagine get_option('widget_text'); is called. Its first action is $pre = apply_filters( 'pre_option_widget_text';. As widget-text is a widget we did add_filter('pre_option_widget_text', 'zd_multilang_get_option_widget_text'); where zd_multilang_get_option_widget_text return zd_multilang_get_option('widget_text');. So call to get_option('widget_text') will lead to zd_multilang_get_option('widget_text').

Once all those shortcut filters are installed, we can recall the wp_widgets_init() which resets all widgets. But this time, it will look for the parameters in our wp_zd_ml_options table through our zd_multilang_get_option function. That sounds good: it was our goal.

How to store sidebars by language?

The sidebars are registered by the theme. Here also, we have no idea of their number. So we are going to keep an additional option equivalent to sidebars_widgets option that contains information on the content of sidebars. Our option will contain nb of sidebars in the theme x nb of languages dealt by zd_multilangue elements. Then we will rebuild on the fly the result WordPress expects to find based on the context (simple blog reading or administering widgets through the admin screen).

How users will administer widgets in each language?

In the beginning I wanted to make a setup screen for widgets a bit like zdmultilang manages categories, tags … However, the management of widgets by WordPress (wp-adminwidgets.php) is not obvious ( work in case of javascript deactivation, compute modified data if ever before displaying anything …). Also, I finally resigned myself to add a language selector to the initial widgets admin page. Again, the hooks offered by WordPress don’t help so much and make the code non trivial. But, at last, it works.

Copie d'écran du sélecteur de langue dans le gestionnaire de widget

How to test those changes to the plugin?

You can access my code by following this link: zd_multilang with support for multilingual widgets but it comes with the status: it works for me. In addition, you may lose your blogname and blogdescription data by language if you decide to revert to an earlier version.

Don’t forget to leave your comments in case of test.