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

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