PluginProbe
Polylang / 1.4.4
Polylang v1.4.4
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.4.4, at include/wpml-compat.php

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