PageRenderTime 27ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/polylang/include/wpml-config.php

https://gitlab.com/Pibolo/plugin-wordpress
PHP | 341 lines | 187 code | 38 blank | 116 comment | 60 complexity | bb349962b77301e35dab9d56703941f4 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, GPL-3.0
  1. <?php
  2. /*
  3. * reads and interprets the file wpml-config.xml
  4. * see http://wpml.org/documentation/support/language-configuration-files/
  5. * the language switcher configuration is not interpreted
  6. * the xml parser has been adapted from http://php.net/manual/en/function.xml-parse-into-struct.php#84261
  7. * many thanks to wickedfather at hotmail dot com
  8. *
  9. * @since 1.0
  10. */
  11. class PLL_WPML_Config {
  12. static protected $instance; // for singleton
  13. protected $values, $index, $strings;
  14. public $tags;
  15. /*
  16. * constructor
  17. *
  18. * @since 1.0
  19. */
  20. public function __construct() {
  21. $this->init();
  22. }
  23. /*
  24. * access to the single instance of the class
  25. *
  26. * @since 1.7
  27. *
  28. * @return object
  29. */
  30. static public function instance() {
  31. if (empty(self::$instance))
  32. self::$instance = new self();
  33. return self::$instance;
  34. }
  35. /*
  36. * parses the wpml-config.xml file
  37. *
  38. * @since 1.0
  39. *
  40. * @param string wpml-config.xml file content
  41. * @param string $context identifies where the file was found
  42. */
  43. protected function xml_parse($xml, $context) {
  44. $parser = xml_parser_create();
  45. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  46. xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
  47. xml_parse_into_struct($parser, $xml, $this->values);
  48. xml_parser_free($parser);
  49. $this->index = 0;
  50. $arr = $this->xml_parse_recursive();
  51. $arr = $arr['wpml-config'];
  52. $keys = array(
  53. array('custom-fields', 'custom-field'),
  54. array('custom-types','custom-type'),
  55. array('taxonomies','taxonomy'),
  56. array('admin-texts','key')
  57. );
  58. foreach ($keys as $k) {
  59. if (isset($arr[$k[0]])) {
  60. if (!isset($arr[$k[0]][$k[1]][0])) {
  61. $elem = $arr[$k[0]][$k[1]];
  62. unset($arr[$k[0]][$k[1]]);
  63. $arr[$k[0]][$k[1]][0] = $elem;
  64. }
  65. $this->tags[$k[0]][$context] = $arr[$k[0]];
  66. }
  67. }
  68. }
  69. /*
  70. * recursively parses the wpml-config.xml file
  71. *
  72. * @since 1.0
  73. *
  74. * @return array
  75. */
  76. protected function xml_parse_recursive() {
  77. $found = array();
  78. $tagCount = array();
  79. while (isset($this->values[$this->index])) {
  80. extract($this->values[$this->index]);
  81. $this->index++;
  82. if ($type == 'close')
  83. return $found;
  84. if (isset($tagCount[$tag])) {
  85. if ($tagCount[$tag] == 1)
  86. $found[$tag] = array($found[$tag]);
  87. $tagRef = &$found[$tag][$tagCount[$tag]];
  88. $tagCount[$tag]++;
  89. }
  90. else {
  91. $tagCount[$tag] = 1;
  92. $tagRef = &$found[$tag];
  93. }
  94. if ($type == 'open') {
  95. $tagRef = $this->xml_parse_recursive();
  96. if (isset($attributes))
  97. $tagRef['attributes'] = $attributes;
  98. }
  99. if ($type == 'complete') {
  100. if (isset($attributes)) {
  101. $tagRef['attributes'] = $attributes;
  102. $tagRef = &$tagRef['value'];
  103. }
  104. if (isset($value))
  105. $tagRef = $value;
  106. }
  107. }
  108. return $found;
  109. }
  110. /*
  111. * finds the wpml-config.xml files to parse and setup filters
  112. *
  113. * @since 1.0
  114. */
  115. public function init() {
  116. $this->tags = array();
  117. // theme
  118. if (file_exists($file = ($template = get_template_directory()) .'/wpml-config.xml'))
  119. $this->xml_parse(file_get_contents($file), get_template()); // FIXME fopen + fread + fclose quicker ?
  120. // child theme
  121. if ($template != ($stylesheet = get_stylesheet_directory()) && file_exists($file = $stylesheet.'/wpml-config.xml'))
  122. $this->xml_parse(file_get_contents($file), get_stylesheet());
  123. // plugins
  124. // 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
  125. $plugins = (is_multisite() && $sitewide_plugins = get_site_option('active_sitewide_plugins')) && is_array($sitewide_plugins) ? array_keys($sitewide_plugins) : array();
  126. $plugins = array_merge($plugins, get_option('active_plugins'));
  127. foreach ($plugins as $plugin) {
  128. if (file_exists($file = WP_PLUGIN_DIR.'/'.dirname($plugin).'/wpml-config.xml'))
  129. $this->xml_parse(file_get_contents($file), dirname($plugin));
  130. }
  131. // custom
  132. if (file_exists($file = PLL_LOCAL_DIR.'/wpml-config.xml'))
  133. $this->xml_parse(file_get_contents($file), 'polylang');
  134. if (isset($this->tags['custom-fields']))
  135. add_filter('pll_copy_post_metas', array(&$this, 'copy_post_metas'), 10, 2);
  136. if (isset($this->tags['custom-types']))
  137. add_filter('pll_get_post_types', array(&$this, 'translate_types'), 10, 2);
  138. if (isset($this->tags['taxonomies']))
  139. add_filter('pll_get_taxonomies', array(&$this, 'translate_taxonomies'), 10, 2);
  140. if (!isset($this->tags['admin-texts']))
  141. return;
  142. // get a cleaner array for easy manipulation
  143. foreach ($this->tags['admin-texts'] as $context => $arr)
  144. foreach ($arr as $keys)
  145. $this->strings[$context] = $this->admin_texts_recursive($keys);
  146. foreach ($this->strings as $context => $options) {
  147. foreach ($options as $option_name => $value) {
  148. if (PLL_ADMIN) { // backend
  149. $option = get_option($option_name);
  150. if (is_string($option) && $value == 1)
  151. pll_register_string($option_name, $option, $context);
  152. elseif (is_array($option) && is_array($value))
  153. $this->register_string_recursive($context, $value, $option); // for a serialized option
  154. }
  155. else
  156. add_filter('option_'.$option_name, array(&$this, 'translate_strings'));
  157. }
  158. }
  159. }
  160. /*
  161. * arranges strings in a cleaner way
  162. *
  163. * @since 1.0
  164. *
  165. * @param array $keys
  166. * @return array
  167. */
  168. protected function admin_texts_recursive($keys) {
  169. if (!isset($keys[0])) {
  170. $elem = $keys;
  171. unset($keys);
  172. $keys[0] = $elem;
  173. }
  174. foreach ($keys as $key)
  175. $strings[$key['attributes']['name']] = isset($key['key']) ? $this->admin_texts_recursive($key['key']) : 1;
  176. return $strings;
  177. }
  178. /*
  179. * recursively registers strings for a serialized option
  180. *
  181. * @since 1.0
  182. *
  183. * @param string $context the group in which the strings will be registered
  184. * @param array $strings
  185. * @param array $options
  186. */
  187. protected function register_string_recursive($context, $strings, $options) {
  188. foreach ($options as $name => $value) {
  189. if (isset($strings[$name])) {
  190. // allow numeric values to be translated
  191. // https://wordpress.org/support/topic/wpml-configxml-strings-skipped-when-numbers-ids
  192. if ((is_numeric($value) || is_string($value)) && $strings[$name] == 1)
  193. pll_register_string($name, $value, $context);
  194. elseif (is_array($value) && is_array($strings[$name]))
  195. $this->register_string_recursive($context, $strings[$name], $value);
  196. }
  197. }
  198. }
  199. /*
  200. * adds custom fields to the list of metas to copy when creating a new translation
  201. *
  202. * @since 1.0
  203. *
  204. * @param array $metas the list of custom fields to copy or synchronize
  205. * @param bool $sync true for sync, false for copy
  206. * @return array the list of custom fields to copy or synchronize
  207. */
  208. public function copy_post_metas($metas, $sync) {
  209. foreach ($this->tags['custom-fields'] as $context) {
  210. foreach ($context['custom-field'] as $cf) {
  211. // copy => copy and synchronize
  212. // translate => copy but don't synchronize
  213. // ignore => don't copy
  214. // see http://wordpress.org/support/topic/custom-field-values-override-other-translation-values?replies=8#post-4655563
  215. if ('copy' == $cf['attributes']['action'] || (!$sync && 'translate' == $cf['attributes']['action']))
  216. $metas[] = $cf['value'];
  217. else
  218. $metas = array_diff($metas, array($cf['value']));
  219. }
  220. }
  221. return $metas;
  222. }
  223. /*
  224. * language and translation management for custom post types
  225. *
  226. * @since 1.0
  227. *
  228. * @param array $types list of post type names for which Polylang manages language and translations
  229. * @param bool $hide true when displaying the list in Polylang settings
  230. * @return array list of post type names for which Polylang manages language and translations
  231. */
  232. public function translate_types($types, $hide) {
  233. foreach ($this->tags['custom-types'] as $context) {
  234. foreach ($context['custom-type'] as $pt) {
  235. if ($pt['attributes']['translate'] == 1 && !$hide)
  236. $types[$pt['value']] = $pt['value'];
  237. elseif ($hide)
  238. unset ($types[$pt['value']]); // the author decided what to do with the post type so don't allow the user to change this
  239. }
  240. }
  241. return $types;
  242. }
  243. /*
  244. * language and translation management for custom taxonomies
  245. *
  246. * @since 1.0
  247. *
  248. * @param array $taxonomies list of taxonomy names for which Polylang manages language and translations
  249. * @param bool $hide true when displaying the list in Polylang settings
  250. * @return array list of taxonomy names for which Polylang manages language and translations
  251. */
  252. public function translate_taxonomies($taxonomies, $hide) {
  253. foreach ($this->tags['taxonomies'] as $context) {
  254. foreach ($context['taxonomy'] as $tax) {
  255. if ($tax['attributes']['translate'] == 1 && !$hide)
  256. $taxonomies[$tax['value']] = $tax['value'];
  257. elseif ($hide)
  258. unset ($taxonomies[$tax['value']]); // the author decided what to do with the taxonomy so don't allow the user to change this
  259. }
  260. }
  261. return $taxonomies;
  262. }
  263. /*
  264. * translates the strings for an option
  265. *
  266. * @since 1.0
  267. *
  268. * @param array|string either a string to translate or a list of strings to translate
  269. * @return array|string translated string(s)
  270. */
  271. public function translate_strings($value) {
  272. if (is_array($value)) {
  273. $option = substr(current_filter(), 7);
  274. foreach ($this->strings as $context => $options) {
  275. if (array_key_exists($option, $options))
  276. return $this->translate_strings_recursive($options[$option], $value); // for a serialized option
  277. }
  278. }
  279. return pll__($value);
  280. }
  281. /*
  282. * recursively translates strings for a serialized option
  283. *
  284. * @since 1.0
  285. *
  286. * @param array $strings
  287. * @param array|string $values either a string to translate or a list of strings to translate
  288. * @return array|string translated string(s)
  289. */
  290. protected function translate_strings_recursive($strings, $values) {
  291. foreach ($values as $name => $value) {
  292. if (isset($strings[$name])) {
  293. // allow numeric values to be translated
  294. // https://wordpress.org/support/topic/wpml-configxml-strings-skipped-when-numbers-ids
  295. if ((is_numeric($value) || is_string($value)) && $strings[$name] == 1)
  296. $values[$name] = pll__($value);
  297. elseif (is_array($value) && is_array($strings[$name]))
  298. $values[$name] = $this->translate_strings_recursive($strings[$name], $value);
  299. }
  300. }
  301. return $values;
  302. }
  303. } // class PLL_WPML_Config