{"id":8,"date":"2009-01-19T23:20:42","date_gmt":"2009-01-19T22:20:42","guid":{"rendered":"http:\/\/www.e-gaulue.com\/en\/?p=8"},"modified":"2014-12-17T21:21:19","modified_gmt":"2014-12-17T21:21:19","slug":"work-around-the-zdmultilang-plugin","status":"publish","type":"post","link":"http:\/\/www.e-gaulue.com\/en\/2009\/01\/19\/work-around-the-zdmultilang-plugin\/","title":{"rendered":"Work around the zdmultilang plugin"},"content":{"rendered":"<p>I&#8217;ve been testing several multilingual wordpress plugins (<a href=\"http:\/\/hellosam.net\/lang\/en\/project\/xlanguage\">xLanguage<\/a>, <a href=\"http:\/\/www.qianqin.de\/qtranslate\/\">qTranslate<\/a> and <a href=\"http:\/\/wordpress.org\/extend\/plugins\/gengo\/\">Gengo<\/a>), before I stopped on <a href=\"http:\/\/www.zen-dreams.com\/en\/zdmultilang\/\">zdmultilang<\/a>.<\/p>\n<p>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.<\/p>\n<p>For me, it only lacked a multilingual widget management. So I tried to add this functionality as I describe here.<\/p>\n<h2>The standardization of widgets<\/h2>\n<p>WordPress always uses the same pattern to store widgets data: it is a parameter name \/ value hash array that is stored in the <code>wp_option<\/code> table through the <code>get_option<\/code> and <code>update_option<\/code> functions. Also, in the first instance, I modified the zdmultilang language switcher widget to comply with this standard.<\/p>\n<h2>On the road to options by language<\/h2>\n<p>Zdmultilang already manages language options: <code>blogname<\/code> and <code>blogdescription<\/code> in the <code>wp_zd_ml_langs<\/code> table. However, if we want to manage widgets by language we will have to change the architecture. My choice was to create the <code>wp_zd_ml_options<\/code> table whose structure is based on the <code>wp_options<\/code> table where I added the <code>LanguageID<\/code> field. This table contains all the options I wanted to keep in a specific language. So, I put there <code>blogname<\/code> and <code>blogdescription<\/code> as this place now seemed legitimate.<\/p>\n<p style=\"text-align: center\"><a href=\"http:\/\/www.e-gaulue.com\/wp-content\/uploads\/2009\/01\/zd_ml_options.jpg\"><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-90 aligncenter\" title=\"zd_ml_options\" src=\"http:\/\/www.e-gaulue.com\/wp-content\/uploads\/2009\/01\/zd_ml_options.jpg\" alt=\"Exemple de la table wp_zd_ml_options\" width=\"610\" height=\"94\" \/><\/a><\/p>\n<h2>How to build dynamic hooks for our widgets?<\/h2>\n<p>As said before, the parameters of the widgets are stored in the <code>wp_options<\/code> table. So, in order to stay as closed as possible to the WordPress architecture, it seemed appropriate the use the <code>pre_option_...<\/code> and <code>pre_update_option_...<\/code> hooks (where <code>...<\/code> is the name of the widget) provided by the <code>get_option<\/code> and <code>update_option<\/code> functions. The use of the &#8220;pre&#8221; hook is nice as it exits the initial <code>get_option<\/code> or <code>update_option<\/code> 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: <code>get_option, update_option, add_option, load_alloptions<\/code>. It always has been done using the initial WordPress functions to take advantage of caching tools as <code>alloption<\/code> and <code>notoption<\/code> 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&#8217;re going to register as much shortcut filters as widgets based on the only information we have: the <code>$wp_registered_widgets<\/code> array. Thus, if a widget does not have a parameter, we should exit very quickly thanks to <code>notoption<\/code>. That way the database shouldn&#8217;t be stressed.<\/p>\n<p>Then, we still have to register our new <code>zd_get_option<\/code> and <code>zd_update_option<\/code> functions to the hooks identified above. In order to accomplish this, we get inside the <code>$wp_registered_widgets<\/code> hash array and for each widget we build a dynamic function that we register to the hook. A sample of code :<\/p>\n<pre>\r\nforeach($wp_registered_widgets as $widget_name =&gt; $widget) {\r\n\t$funcname = 'zd_multilang_get_option_'.$widget['classname'];\r\n\t$function = \"function $funcname\".'() {\r\n\t\t\t$option=str_replace(\"zd_multilang_get_option_\",\"\",__FUNCTION__);\r\n\t\t\treturn zd_multilang_get_option($option);\r\n\t\t\t}');\r\n\teval($function);\r\n\tadd_filter('pre_option_'.$widget['classname'], $funcname);\r\n}\r\n<\/pre>\n<p>Here is an exemple with the widget-text :<br \/>\nImagine <code>get_option('widget_text');<\/code> is called. Its first action is <code>$pre = apply_filters( 'pre_option_widget_text';<\/code>. As widget-text is a widget we did <code>add_filter('pre_option_widget_text', 'zd_multilang_get_option_widget_text');<\/code> where <code>zd_multilang_get_option_widget_text<\/code> return <code>zd_multilang_get_option('widget_text');<\/code>. So call to <code>get_option('widget_text')<\/code> will lead to <code>zd_multilang_get_option('widget_text')<\/code>.<\/p>\n<p>Once all those shortcut filters are installed, we can recall the <code>wp_widgets_init()<\/code> which resets all widgets. But this time, it will look for the parameters in our <code>wp_zd_ml_options<\/code> table through our <code>zd_multilang_get_option<\/code> function. That sounds good: it was our goal.<\/p>\n<h2>How to store sidebars by language?<\/h2>\n<p>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 <code>sidebars_widgets<\/code> 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).<\/p>\n<h2>How users will administer widgets in each language?<\/h2>\n<p>In the beginning I wanted to make a setup screen for widgets a bit like zdmultilang manages categories, tags &#8230; 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 &#8230;). Also, I finally resigned myself to add a language selector to the initial widgets admin page. Again, the hooks offered by WordPress don&#8217;t help so much and make the code non trivial. But, at last, it works.<\/p>\n<p><a href=\"http:\/\/www.e-gaulue.com\/wp-content\/uploads\/2009\/01\/zd_widget_switcher.jpg\"><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-69 aligncenter\" title=\"zd_widget_switcher\" src=\"http:\/\/www.e-gaulue.com\/wp-content\/uploads\/2009\/01\/zd_widget_switcher.jpg\" alt=\"Copie d'\u00e9cran du s\u00e9lecteur de langue dans le gestionnaire de widget\" width=\"598\" height=\"282\" \/><\/a><\/p>\n<h2>How to test those changes to the plugin?<\/h2>\n<p>You can access my code by following this link: <a href=\"http:\/\/www.e-gaulue.com\/wp-content\/uploads\/2009\/01\/zd_multilang.txt\">zd_multilang with support for multilingual widgets<\/a> but it comes with the status: it works for me. In addition, you may lose your <code>blogname<\/code> and <code>blogdescription<\/code> data by language if you decide to revert to an earlier version.<\/p>\n<p>Don&#8217;t forget to leave your comments in case of test.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;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&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"http:\/\/www.e-gaulue.com\/en\/2009\/01\/19\/work-around-the-zdmultilang-plugin\/\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[2],"tags":[],"_links":{"self":[{"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/posts\/8"}],"collection":[{"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/comments?post=8"}],"version-history":[{"count":1,"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/posts\/8\/revisions"}],"predecessor-version":[{"id":14,"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/posts\/8\/revisions\/14"}],"wp:attachment":[{"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/media?parent=8"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/categories?post=8"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.e-gaulue.com\/en\/wp-json\/wp\/v2\/tags?post=8"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}