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

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