PluginProbe
Polylang / 1.3
Polylang v1.3
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / include / wpml-compat.php

wpml-compat.php in Polylang 1.3, at include/wpml-compat.php

668 lines 20.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * compatibility with WPML API. See http://wpml.org/documentation/support/wpml-coding-api/
4 */
5
6 /*
7 * defines two WPML constants once the language has been defined
8 * the compatibility with WPML is not perfect on admin side as the constants are defined
9 * in 'setup_theme' by Polylang (based on user info) and 'plugins_loaded' by WPML (based on cookie)
10 * moreover I believe that WPML can set ICL_LANGUAGE_CODE to 'all' which I dont want to do with Polylang
11 *
12 * @since 0.9.5
13 */
14 function pll_define_wpml_constants() {
15 $code = PLL_ADMIN ? get_user_meta(get_current_user_id(), 'pll_filter_content', true) : pll_current_language();
16
17 if(!defined('ICL_LANGUAGE_CODE') && !empty($code))
18 define('ICL_LANGUAGE_CODE', $code);
19
20 if(!defined('ICL_LANGUAGE_NAME') && !empty($code))
21 define('ICL_LANGUAGE_NAME', $GLOBALS['polylang']->model->get_language($code)->name);
22 }
23
24 add_action('pll_language_defined', 'pll_define_wpml_constants');
25
26 /*
27 * link to the home page in the active language
28 *
29 * @since 0.9.4
30 *
31 * @return string
32 */
33 if (!function_exists('icl_get_home_url')) {
34 function icl_get_home_url() {
35 return pll_home_url();
36 }
37 }
38
39 /*
40 * used for building custom language selectors
41 * available only on frontend
42 *
43 * list of paramaters accepted in $args
44 *
45 * skip_missing => wether to skip missing translation or not, 0 or 1, defaults to 0
46 * orderby => 'id', 'cod', 'name', defaults to 'id'
47 * order => 'ASC' or 'DESC', defaults to 'ASC'
48 * link_empty_to => link to use when the translation is missing {$lang} is replaced by the language code
49 *
50 * list of parameters returned per language:
51 *
52 * id => the language id
53 * active => wether this is the active language or no, 0 or 1
54 * native_name => the language name
55 * missing => wether the translation is missing or not, 0 or 1
56 * translated_name => empty, does not exist in Polylang
57 * language_code => the language code (slug)
58 * country_flag_url => the url of the flag
59 * url => the url of the translation
60 *
61 * @since 1.0
62 *
63 * @param string|array $args optional
64 * @return array array of arrays per language
65 */
66 if (!function_exists('icl_get_languages')) {
67 function icl_get_languages($args = '') {
68 global $polylang;
69 if (empty($polylang) || empty($polylang->curlang))
70 return array();
71
72 $args = extract(wp_parse_args($args));
73 $orderby = (isset($orderby) && $orderby == 'code') ? 'slug' : (isset($orderby) && $orderby == 'name' ? 'name' : 'id');
74 $order = (!empty($order) && $order == 'desc') ? 'DESC' : 'ASC';
75
76 $arr = array();
77
78 foreach ($polylang->model->get_languages_list(array('hide_empty' => true, 'orderby' => $orderby, 'order' => $order)) as $lang) {
79 $url = $polylang->get_translation_url($lang);
80
81 if (empty($url) && !empty($skip_missing))
82 continue;
83
84 $arr[] = array(
85 'id' => $lang->term_id,
86 'active' => isset($polylang->curlang->slug) && $polylang->curlang->slug == $lang->slug ? 1 : 0,
87 'native_name' => $lang->name,
88 'missing' => empty($url) ? 1 : 0,
89 'translated_name' => '', // does not exist in Polylang
90 'language_code' => $lang->slug,
91 'country_flag_url' => $lang->flag_url,
92 'url' => $url ? $url :
93 (empty($link_empty_to) ? $polylang->links->get_home_url($lang) :
94 str_replace('{$lang}', $lang->slug, $link_empty_to))
95 );
96 }
97 return $arr;
98 }
99 }
100
101 /*
102 * used for creating language dependent links in themes
103 *
104 * @since 1.0
105 *
106 * @param int $id object id
107 * @param string $type optional, post type or taxonomy name of the object, defaults to 'post'
108 * @param string $text optional the link text. If not specified will produce the name of the element in the current language
109 * @param array $args optional an array of arguments to add to the link, defaults to empty
110 * @param string $anchor optional the anchor to add to teh link, defaults to empty
111 * @return string a language dependent link
112 */
113 if (!function_exists('icl_link_to_element')) {
114 function icl_link_to_element($id, $type = 'post', $text = '', $args = array(), $anchor = '') {
115 global $polylang;
116
117 if ($type == 'tag')
118 $type = 'post_tag';
119
120 if (isset($polylang) && ($lang = pll_current_language()) && $tr_id = $polylang->model->get_translation($type, $id, $lang))
121 $id = $tr_id;
122
123 if (post_type_exists($type)) {
124 $link = get_permalink($id);
125 if (empty($text))
126 $text = get_the_title($id);
127 }
128 elseif (taxonomy_exists($type)) {
129 $link = get_term_link($id, $type);
130 if (empty($text) && ($term = get_term($id, $type)) && !empty($term) && !is_wp_error($term))
131 $text = $term->name;
132 }
133
134 if (empty($link) || is_wp_error($link))
135 return '';
136
137 if (!empty($args))
138 $link .= (false === strpos($link, '?') ? '?' : '&' ) . http_build_query($args);
139
140 if (!empty($anchor))
141 $link .= '#' . $anchor;
142
143 return sprintf('<a href="%s">%s</a>', esc_url($link), esc_html($text));
144 }
145 }
146
147 /*
148 * used for calculating the IDs of objects (usually categories) in the current language
149 *
150 * @since 0.9.5
151 *
152 * @param int $id object id
153 * @param string $type, post type or taxonomy name of the object, defaults to 'post'
154 * @param bool $return_original_if_missing true if Polylang should return the original id if the translation is missiing
155 * @param string $lang optional language code, defaults to current language
156 * @return int|null the object id of the translation, null if the translation is missing and $return_original_if_missing set to false
157 */
158 if (!function_exists('icl_object_id')) {
159 function icl_object_id($id, $type, $return_original_if_missing, $lang = false) {
160 global $polylang;
161 return isset($polylang) && ($lang = $lang ? $lang : pll_current_language()) && ($tr_id = $polylang->model->get_translation($type, $id, $lang)) ? $tr_id :
162 ($return_original_if_missing ? $id : null);
163 }
164 }
165
166 /*
167 * registers a string for translation in the "strings translation" panel
168 *
169 * @since 0.9.3
170 *
171 * @param string $context the group in which the string is registered, defaults to 'polylang'
172 * @param string $name a unique name for the string
173 * @param string $string the string to register
174 */
175 if (!function_exists('icl_register_string')) {
176 function icl_register_string($context, $name, $string) {
177 $GLOBALS['pll_wpml_compat']->register_string($context, $name, $string);
178 }
179 }
180
181 /*
182 * removes a string from the "strings translation" panel
183 *
184 * @since 1.0.2
185 *
186 * @param string $context the group in which the string is registered, defaults to 'polylang'
187 * @param string $name a unique name for the string
188 */
189 if (!function_exists('icl_unregister_string')) {
190 function icl_unregister_string($context, $name) {
191 $GLOBALS['pll_wpml_compat']->unregister_string($context, $name);
192 }
193 }
194
195 /*
196 * gets the translated value of a string (previously registered with icl_register_string or pll_register_string)
197 *
198 * @since 0.9.3
199 *
200 * @param string $context not used by Polylang
201 * @param string $name not used by Polylang
202 * @param string $string the string to translated
203 * @return string the translated string in the current language
204 */
205 if (!function_exists('icl_t')) {
206 function icl_t($context, $name, $string) {
207 return pll__($string);
208 }
209 }
210
211 /*
212 * undocumented function used by NextGen Gallery
213 * seems to be used to both register and translate a string
214 * FIXME: tested only with NextGen gallery
215 *
216 * @since 1.0.2
217 *
218 * @param string $context the group in which the string is registered, defaults to 'polylang'
219 * @param string $name a unique name for the string
220 * @param string $string the string to register
221 * @param bool $bool not used by Polylang
222 * @return string the translated string in the current language
223 */
224 if (!function_exists('icl_translate')) {
225 function icl_translate($context, $name, $string, $bool) {
226 $GLOBALS['pll_wpml_compat']->register_string($context, $name, $string);
227 return pll__($string);
228 }
229 }
230
231 /*
232 * undocumented function used by Types
233 * FIXME: tested only with Types
234 * probably incomplete as Types locks the custom fields for a new post, but not when edited
235 * this is probably linked to the fact that WPML has always an original post in the default language and not Polylang :)
236 *
237 * @since 1.1.2
238 *
239 * @return array
240 */
241 if (!function_exists('wpml_get_copied_fields_for_post_edit')) {
242 function wpml_get_copied_fields_for_post_edit() {
243 if (empty($_GET['from_post']))
244 return array();
245
246 // don't know what WPML does but Polylang does copy all public meta keys by default
247 foreach ($keys = array_unique(array_keys(get_post_custom($_GET['from_post']))) as $k => $meta_key)
248 if (is_protected_meta($meta_key))
249 unset ($keys[$k]);
250
251 // apply our filter and fill the expected output (see /types/embedded/includes/fields-post.php)
252 $arr['fields'] = array_unique(apply_filters('pll_copy_post_metas', empty($keys) ? array() : $keys, false));
253 $arr['original_post_id'] = (int) $_GET['from_post'];
254 return $arr;
255 }
256 }
257
258 /*
259 * undocumented function used by Warp 6 by Yootheme
260 *
261 * @since 1.0.5
262 *
263 * @return string default language code
264 */
265 if (!function_exists('icl_get_default_language')) {
266 function icl_get_default_language() {
267 return pll_default_language();
268 }
269 }
270
271 /*
272 * registers strings in a persistant way as done by WPML
273 *
274 * @since 1.0.2
275 */
276 class PLL_WPML_Compat {
277 protected $strings; // used for cache
278
279 /*
280 * constructor
281 *
282 * @since 1.0.2
283 */
284 public function __construct() {
285 add_action('pll_get_strings', array(&$this, 'get_strings'));
286 }
287
288 /*
289 * unlike pll_register_string, icl_register_string stores the string in database
290 * so we need to do the same as some plugins or themes may expect this
291 * we use a serialized option to do this
292 *
293 * @since 1.0.2
294 *
295 * @param string $context the group in which the string is registered, defaults to 'polylang'
296 * @param string $name a unique name for the string
297 * @param string $string the string to register
298 */
299 public function register_string($context, $name, $string) {
300 if (empty($this->strings))
301 $this->strings = get_option('polylang_wpml_strings', array());
302
303 // registers the string if it does not exist yet
304 $to_register = array('context' => $context, 'name'=> $name, 'string' => $string, 'multiline' => false, 'icl' => true);
305 if (!in_array($to_register, $this->strings) && $to_register['string']) {
306 $this->strings[] = $to_register;
307 update_option('polylang_wpml_strings', $this->strings);
308 }
309 }
310
311 /*
312 * removes a string from the registered strings list
313 *
314 * @since 1.0.2
315 *
316 * @param string $context the group in which the string is registered, defaults to 'polylang'
317 * @param string $name a unique name for the string
318 */
319 public function unregister_string($context, $name) {
320 if (empty($this->strings))
321 $this->strings = get_option('polylang_wpml_strings', array());
322
323 foreach ($this->strings as $key=>$string) {
324 if ($string['context'] == $context && $string['name'] == $name) {
325 unset($this->strings[$key]);
326 update_option('polylang_wpml_strings', $this->strings);
327 }
328 }
329 }
330
331 /*
332 * adds strings registered by icl_register_string to those registered by pll_register_string
333 *
334 * @since 1.0.2
335 *
336 * @param array $strings existing registered strings
337 * @return array registered strings with added strings through WPML API
338 */
339 public function get_strings($strings) {
340 if (empty($this->strings))
341 $this->strings = get_option('polylang_wpml_strings');
342
343 return empty($this->strings) ? $strings : array_merge($strings, $this->strings);
344 }
345 } // class PLL_WPML_Compat
346
347 $GLOBALS['pll_wpml_compat'] = new PLL_WPML_Compat;
348
349 /*
350 * reads and interprets the file wpml-config.xml
351 * see http://wpml.org/documentation/support/language-configuration-files/
352 * the language switcher configuration is not interpreted
353 * the xml parser has been adapted from http://php.net/manual/en/function.xml-parse-into-struct.php#84261
354 * many thanks to wickedfather at hotmail dot com
355 *
356 * @since 1.0
357 */
358 class PLL_WPML_Config {
359 protected $values, $index, $wpml_config, $strings;
360
361 /*
362 * constructor
363 *
364 * @since 1.0
365 */
366 public function __construct() {
367 $this->init();
368 }
369
370 /*
371 * parses the wpml-config.xml file
372 *
373 * @since 1.0
374 *
375 * @param string wpml-config.xml file content
376 * @param string $context identifies where the file was found
377 */
378 protected function xml_parse($xml, $context) {
379 $parser = xml_parser_create();
380 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
381 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
382 xml_parse_into_struct($parser, $xml, $this->values);
383 xml_parser_free($parser);
384
385 $this->index = 0;
386 $arr = $this->xml_parse_recursive();
387 $arr = $arr['wpml-config'];
388
389 $keys = array(
390 array('custom-fields', 'custom-field'),
391 array('custom-types','custom-type'),
392 array('taxonomies','taxonomy'),
393 array('admin-texts','key')
394 );
395
396 foreach ($keys as $k) {
397 if (isset($arr[$k[0]])) {
398 if (!isset($arr[$k[0]][$k[1]][0])) {
399 $elem = $arr[$k[0]][$k[1]];
400 unset($arr[$k[0]][$k[1]]);
401 $arr[$k[0]][$k[1]][0] = $elem;
402 }
403
404 $this->wpml_config[$k[0]][$context] = $arr[$k[0]];
405 }
406 }
407 }
408
409 /*
410 * recursively parses the wpml-config.xml file
411 *
412 * @since 1.0
413 *
414 * @return array
415 */
416 protected function xml_parse_recursive() {
417 $found = array();
418 $tagCount = array();
419
420 while (isset($this->values[$this->index])) {
421 extract($this->values[$this->index]);
422 $this->index++;
423
424 if ($type == 'close')
425 return $found;
426
427 if (isset($tagCount[$tag])) {
428 if ($tagCount[$tag] == 1)
429 $found[$tag] = array($found[$tag]);
430
431 $tagRef = &$found[$tag][$tagCount[$tag]];
432 $tagCount[$tag]++;
433 }
434 else {
435 $tagCount[$tag] = 1;
436 $tagRef = &$found[$tag];
437 }
438
439 if ($type == 'open') {
440 $tagRef = $this->xml_parse_recursive();
441 if (isset($attributes))
442 $tagRef['attributes'] = $attributes;
443 }
444
445 if ($type == 'complete') {
446 if (isset($attributes)) {
447 $tagRef['attributes'] = $attributes;
448 $tagRef = &$tagRef['value'];
449 }
450 if (isset($value))
451 $tagRef = $value;
452 }
453 }
454
455 return $found;
456 }
457
458 /*
459 * finds the wpml-config.xml files to parse and setup filters
460 *
461 * @since 1.0
462 */
463 public function init() {
464 $this->wpml_config = array();
465
466 // child theme
467 if (($template = get_template_directory()) != ($stylesheet = get_stylesheet_directory()) && file_exists($file = $stylesheet.'/wpml-config.xml'))
468 $this->xml_parse(file_get_contents($file), get_stylesheet()); // FIXME fopen + fread + fclose quicker ?
469
470 // theme
471 if (file_exists($file = $template.'/wpml-config.xml'))
472 $this->xml_parse(file_get_contents($file), get_template());
473
474 // plugins
475 // don't forget sitewide active plugins thanks to Reactorshop http://wordpress.org/support/topic/polylang-and-yoast-seo-plugin/page/2?replies=38#post-4801829
476 $plugins = (is_multisite() && $sitewide_plugins = get_site_option('active_sitewide_plugins')) && is_array($sitewide_plugins) ? array_keys($sitewide_plugins) : array();
477 $plugins = array_merge($plugins, get_option('active_plugins'));
478
479 foreach ($plugins as $plugin) {
480 if (file_exists($file = dirname(POLYLANG_DIR).'/'.dirname($plugin).'/wpml-config.xml'))
481 $this->xml_parse(file_get_contents($file), dirname($plugin));
482 }
483
484 // custom
485 if (file_exists($file = PLL_LOCAL_DIR.'/wpml-config.xml'))
486 $this->xml_parse(file_get_contents($file), 'polylang');
487
488 if (isset($this->wpml_config['custom-fields']))
489 add_filter('pll_copy_post_metas', array(&$this, 'copy_post_metas'), 10, 2);
490
491 if (isset($this->wpml_config['custom-types']))
492 add_filter('pll_get_post_types', array(&$this, 'translate_types'), 10, 2);
493
494 if (isset($this->wpml_config['taxonomies']))
495 add_filter('pll_get_taxonomies', array(&$this, 'translate_taxonomies'), 10, 2);
496
497 if (!isset($this->wpml_config['admin-texts']))
498 return;
499
500 // get a cleaner array for easy manipulation
501 foreach ($this->wpml_config['admin-texts'] as $context => $arr)
502 foreach ($arr as $keys)
503 $this->strings[$context] = $this->admin_texts_recursive($keys);
504
505 foreach ($this->strings as $context => $options) {
506 foreach ($options as $option_name => $value) {
507 if (PLL_ADMIN) { // backend
508 $option = get_option($option_name);
509 if (is_string($option) && $value == 1)
510 pll_register_string($option_name, $option, $context);
511 elseif (is_array($option) && is_array($value))
512 $this->register_string_recursive($context, $value, $option); // for a serialized option
513 }
514 else
515 add_filter('option_'.$option_name, array(&$this, 'translate_strings'));
516 }
517 }
518 }
519
520 /*
521 * arranges strings in a cleaner way
522 *
523 * @since 1.0
524 *
525 * @param array $keys
526 * @return array
527 */
528 protected function admin_texts_recursive($keys) {
529 if (!isset($keys[0])) {
530 $elem = $keys;
531 unset($keys);
532 $keys[0] = $elem;
533 }
534 foreach ($keys as $key)
535 $strings[$key['attributes']['name']] = isset($key['key']) ? $this->admin_texts_recursive($key['key']) : 1;
536
537 return $strings;
538 }
539
540 /*
541 * recursively registers strings for a serialized option
542 *
543 * @since 1.0
544 *
545 * @param string $context the group in which the strings will be registered
546 * @param array $strings
547 * @param array $options
548 */
549 protected function register_string_recursive($context, $strings, $options) {
550 foreach ($options as $name => $value) {
551 if (isset($strings[$name])) {
552 if (is_string($value) && $strings[$name] == 1)
553 pll_register_string($name, $value, $context);
554 elseif (is_array($value) && is_array($strings[$name]))
555 $this->register_string_recursive($context, $strings[$name], $value);
556 }
557 }
558 }
559
560 /*
561 * adds custom fields to the list of metas to copy when creating a new translation
562 *
563 * @since 1.0
564 *
565 * @param array $metas the list of custom fields to copy or synchronize
566 * @param bool $sync true for sync, false for copy
567 * @return array the list of custom fields to copy or synchronize
568 */
569 public function copy_post_metas($metas, $sync) {
570 foreach ($this->wpml_config['custom-fields'] as $context) {
571 foreach ($context['custom-field'] as $cf) {
572 // copy => copy and synchronize
573 // translate => copy but don't synchronize
574 // ignore => don't copy
575 // see http://wordpress.org/support/topic/custom-field-values-override-other-translation-values?replies=8#post-4655563
576 if ('copy' == $cf['attributes']['action'] || (!$sync && 'translate' == $cf['attributes']['action']))
577 $metas[] = $cf['value'];
578 else
579 $metas = array_diff($metas, array($cf['value']));
580 }
581 }
582 return $metas;
583 }
584
585 /*
586 * language and translation management for custom post types
587 *
588 * @since 1.0
589 *
590 * @param array $types list of post type names for which Polylang manages language and translations
591 * @param bool $hide true when displaying the list in Polylang settings
592 * @return array list of post type names for which Polylang manages language and translations
593 */
594 public function translate_types($types, $hide) {
595 foreach ($this->wpml_config['custom-types'] as $context) {
596 foreach ($context['custom-type'] as $pt) {
597 if ($pt['attributes']['translate'] == 1 && !$hide)
598 $types[$pt['value']] = $pt['value'];
599 elseif ($hide)
600 unset ($types[$pt['value']]); // the author decided what to do with the post type so don't allow the user to change this
601 }
602 }
603 return $types;
604 }
605
606 /*
607 * language and translation management for custom taxonomies
608 *
609 * @since 1.0
610 *
611 * @param array $taxonomies list of taxonomy names for which Polylang manages language and translations
612 * @param bool $hide true when displaying the list in Polylang settings
613 * @return array list of taxonomy names for which Polylang manages language and translations
614 */
615 public function translate_taxonomies($taxonomies, $hide) {
616 foreach ($this->wpml_config['taxonomies'] as $context) {
617 foreach ($context['taxonomy'] as $tax) {
618 if ($tax['attributes']['translate'] == 1 && !$hide)
619 $taxonomies[$tax['value']] = $tax['value'];
620 elseif ($hide)
621 unset ($types[$tax['value']]); // the author decided what to do with the taxonomy so don't allow the user to change this
622 }
623 }
624
625 return $taxonomies;
626 }
627
628 /*
629 * translates the strings for an option
630 *
631 * @since 1.0
632 *
633 * @param array|string either a string to translate or a list of strings to translate
634 * @return array|string translated string(s)
635 */
636 public function translate_strings($value) {
637 if (is_array($value)) {
638 $option = substr(current_filter(), 7);
639 foreach ($this->strings as $context => $options) {
640 if (array_key_exists($option, $options))
641 return $this->translate_strings_recursive($options[$option], $value); // for a serialized option
642 }
643 }
644 return pll__($value);
645 }
646
647 /*
648 * recursively translates strings for a serialized option
649 *
650 * @since 1.0
651 *
652 * @param array $strings
653 * @param array|string $values either a string to translate or a list of strings to translate
654 * @return array|string translated string(s)
655 */
656 protected function translate_strings_recursive($strings, $values) {
657 foreach ($values as $name => $value) {
658 if (isset($strings[$name])) {
659 if (is_string($value) && $strings[$name] == 1)
660 $values[$name] = pll__($value);
661 elseif (is_array($value) && is_array($strings[$name]))
662 $value = $this->translate_strings_recursive($strings[$name], $value);
663 }
664 }
665 return $values;
666 }
667 } // class PLL_WPML_Config
668