webform_localization.i18n.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <?php
  2. /**
  3. * @file
  4. * Webform Localization i18n_string integration.
  5. */
  6. /**
  7. * Provides interface with the i18n_string module.
  8. * Based in patch http://drupal.org/node/245424#comment-5244256
  9. * by Calin Marian. Further development sponsored by Riot Games.
  10. *
  11. * @author German Martin <gmartin.php@gmail.com>
  12. */
  13. /**
  14. * Translates the component properties that are translatable.
  15. *
  16. * These are found in under 'translated_strings' in the 'extra' array of the
  17. * component, which is build when the component is inserted / updated, or
  18. * when all webform strings are updated from
  19. * admin/config/regional/translate/i18n_string.
  20. *
  21. * @param array $element
  22. * The FAPI renderable array of the component instance.
  23. * @param array $component
  24. * The component.
  25. */
  26. function _webform_localization_translate_component(&$element, $component) {
  27. if (isset($component['extra']['translated_strings']) && is_array($component['extra']['translated_strings'])) {
  28. foreach ($component['extra']['translated_strings'] as $name) {
  29. $name_list = explode(':', $name);
  30. $current_element = &$element;
  31. if (strpos($name_list[3], '[') !== FALSE) {
  32. // The property is deeper in the renderable array, we must extract the
  33. // the place where it is.
  34. list ($children, $property) = explode(']#', $name_list[3]);
  35. // Remove the '[' from the begining of the string.
  36. $children = drupal_substr($children, 1);
  37. $children_array = explode('][', $children);
  38. foreach ($children_array as $child) {
  39. if (isset($current_element[$child])) {
  40. $current_element = &$current_element[$child];
  41. }
  42. else {
  43. continue;
  44. }
  45. }
  46. }
  47. else {
  48. // Remove the '#' from the begining of the property, for consistency.
  49. $property = drupal_substr($name_list[3], 1);
  50. }
  51. if (strpos($property, '-') !== FALSE) {
  52. // If property is array, we extract the key from the property.
  53. list ($property, $key) = explode('-', $property);
  54. if (isset($current_element['#' . $property][$key])) {
  55. $current_element['#' . $property][$key] = i18n_string($name, $current_element['#' . $property][$key], array('sanitize' => FALSE));
  56. }
  57. }
  58. else {
  59. // If we are dealing with option groups.
  60. if (isset($name_list[4]) && strpos($name_list[4], '/-') !== FALSE) {
  61. $option_group = str_replace('/-', '', $name_list[4]);
  62. // If it's a element.
  63. if (isset($name_list[5])) {
  64. $current_element['#' . $property][$option_group][$name_list[5]] = i18n_string($name, $current_element['#' . $property][$option_group][$name_list[5]]);
  65. }
  66. else {
  67. // If it's a option group we translate the key.
  68. $translated_option_group = i18n_string($name, $option_group);
  69. if ($translated_option_group != $option_group) {
  70. _webform_localization_array_key_replace($current_element['#' . $property], $option_group, $translated_option_group);
  71. }
  72. }
  73. }
  74. else {
  75. // Else we can treat the property as string.
  76. if (isset($current_element['#' . $property])) {
  77. if ($property == 'markup' && $current_element['#type'] == 'markup') {
  78. $current_element['#' . $property] = i18n_string($name, $current_element['#' . $property], array('format' => $current_element['#format']));
  79. }
  80. elseif ($property == 'description') {
  81. $current_element['#' . $property] = i18n_string($name, $current_element['#' . $property], array('format' => I18N_STRING_FILTER_XSS));
  82. }
  83. else {
  84. $current_element['#' . $property] = i18n_string($name, $current_element['#' . $property]);
  85. }
  86. }
  87. }
  88. }
  89. }
  90. }
  91. }
  92. /**
  93. * Translates the analysis component properties that are translatable.
  94. *
  95. * These are found in under 'translated_strings' in the 'extra' array of the
  96. * component, which is build when the component is inserted / updated, or
  97. * when all webform strings are updated from
  98. * admin/config/regional/translate/i18n_string.
  99. *
  100. * @param array $data
  101. * The data array of component results.
  102. * @param array $component
  103. * The component.
  104. */
  105. function _webform_localization_translate_analysis_component(&$data, &$component) {
  106. if (!isset($component['extra']['translated_strings']) || !is_array($component['extra']['translated_strings'])) {
  107. return;
  108. }
  109. // Attempt to translate select options.
  110. if ($component['type'] == 'select') {
  111. $item_key_lookup = _webform_localization_string_to_key($component['extra']['items']);
  112. }
  113. // Attempt to translate grid options / questions.
  114. if ($component['type'] == 'grid') {
  115. $options_key_lookup = _webform_localization_string_to_key($component['extra']['options']);
  116. $questions_key_lookup = _webform_localization_string_to_key($component['extra']['questions']);
  117. }
  118. foreach ($component['extra']['translated_strings'] as $name) {
  119. $name_list = explode(':', $name);
  120. // Translate component name from title property.
  121. if ($name_list[3] == '#title') {
  122. $component['name'] = i18n_string($name, $component['name']);
  123. continue;
  124. }
  125. // Translate options for select elements.
  126. if ($component['type'] == 'select' && strpos($name_list[3], '-') !== FALSE) {
  127. list (, $key) = explode('-', $name_list[3]);
  128. if (isset($item_key_lookup[$key])) {
  129. foreach ($data['table_rows'] as $index => $row) {
  130. if ($row[0] == $item_key_lookup[$key]) {
  131. $data['table_rows'][$index][0] = i18n_string($name, $row[0]);
  132. }
  133. }
  134. }
  135. }
  136. // Translate options / questions for grid elements.
  137. if ($component['type'] == 'grid' && $name_list[3] !== '#title') {
  138. list (, $key) = explode('-', $name_list[3]);
  139. if (strpos($name_list[3], 'grid_options')) {
  140. if (isset($options_key_lookup[$key])) {
  141. foreach ($data['table_header'] as $index => $row) {
  142. if ($row == $options_key_lookup[$key]) {
  143. $data['table_header'][$index] = i18n_string($name, $row);
  144. }
  145. }
  146. }
  147. }
  148. if (strpos($name_list[3], 'grid_questions')) {
  149. if (isset($questions_key_lookup[$key])) {
  150. foreach ($data['table_rows'] as $index => $row) {
  151. if (trim($row[0]) == trim($questions_key_lookup[$key])) {
  152. $data['table_rows'][$index][0] = i18n_string($name, $row[0]);
  153. }
  154. }
  155. }
  156. }
  157. }
  158. }
  159. }
  160. /**
  161. * Update / create translation source for all the translatable poperties.
  162. *
  163. * @param array $component
  164. * A webform component.
  165. */
  166. function webform_localization_component_update_translation_strings(&$component) {
  167. // Fill in the the default values for the missing properties.
  168. module_load_include('inc', 'webform', 'includes/webform.components');
  169. webform_component_defaults($component);
  170. // Render the 'render' FAPI array for the component.
  171. $element = webform_component_invoke($component['type'], 'render', $component, NULL, 'html');
  172. // Parse the renderable array to find the translatable properties and
  173. // update / create translation source for them.
  174. $component['extra']['translated_strings'] = _webform_localization_component_translation_parse($element, $component);
  175. // Render the 'display' FAPI array for the component.
  176. $element = webform_component_invoke($component['type'], 'display', $component, NULL, 'html');
  177. // Parse the renderable array to find the translatable properties and
  178. // update / create translation source for them.
  179. $component['extra']['translated_strings'] = array_merge($component['extra']['translated_strings'], array_diff(_webform_localization_component_translation_parse($element, $component), $component['extra']['translated_strings']));
  180. }
  181. /**
  182. * Parse a component renderable array to find the translatable properties.
  183. *
  184. * Create / update or remove translation source for translatable properties
  185. * of a webform component.
  186. *
  187. * @param array $element
  188. * The renderable array to be parsed.
  189. * @param array $component
  190. * The component which was rendered.
  191. * @return
  192. * An array of translatabled webform properties.
  193. *
  194. */
  195. function _webform_localization_component_translation_parse($element, $component) {
  196. $translated_properies = array();
  197. if (!isset($element['#parents'])) {
  198. $element['#parents'] = array();
  199. }
  200. if (isset($element['#translatable']) && is_array($element['#translatable'])) {
  201. foreach ($element['#translatable'] as $key) {
  202. if (isset($element['#' . $key]) && $element['#' . $key] != '') {
  203. if (isset($element['#parents']) && count($element['#parents'])) {
  204. $property = '[' . implode('][', $element['#parents']) . ']#' . $key;
  205. }
  206. else {
  207. $property = '#' . $key;
  208. }
  209. if (is_array($element['#' . $key])) {
  210. // If the translatable property is an array, we translate the
  211. // children.
  212. foreach ($element['#' . $key] as $elem_key => $elem_value) {
  213. // If the child if an array, we translate the elements.
  214. if (is_array($elem_value)) {
  215. foreach ($elem_value as $k => $v) {
  216. $name = webform_localization_i18n_string_name($component['nid'], $component['cid'], $property, '/-' . $elem_key . '/-', $k);
  217. $translated_properies[] = $name;
  218. i18n_string($name, $v, array('update' => TRUE));
  219. }
  220. $name = webform_localization_i18n_string_name($component['nid'], $component['cid'], $property, '/-' . $elem_key . '/-');
  221. $translated_properies[] = $name;
  222. i18n_string($name, $elem_key, array('update' => TRUE));
  223. }
  224. else {
  225. // If the child is not an array.
  226. $name = webform_localization_i18n_string_name($component['nid'], $component['cid'], $property . '-' . $elem_key);
  227. $translated_properies[] = $name;
  228. i18n_string($name, $elem_value, array('update' => TRUE));
  229. }
  230. }
  231. }
  232. else {
  233. /**
  234. * If the translatable property is not an array,
  235. * it can be treated as a string.
  236. */
  237. $name = webform_localization_i18n_string_name($component['nid'], $component['cid'], $property);
  238. $translated_properies[] = $name;
  239. i18n_string($name, $element['#' . $key], array('update' => TRUE));
  240. }
  241. }
  242. }
  243. }
  244. // Recursevly call the function on the children, after adding the children
  245. // name to its #parents array.
  246. foreach (element_children($element) as $child) {
  247. $element[$child]['#parents'] = $element['#parents'];
  248. $element[$child]['#parents'][] = $child;
  249. // Add the translated propertied to the list.
  250. $translated_properies = array_merge(
  251. $translated_properies,
  252. _webform_localization_component_translation_parse($element[$child], $component)
  253. );
  254. }
  255. return $translated_properies;
  256. }
  257. /**
  258. * Utility function to create i18n string name.
  259. *
  260. * Additional arguments can be passed to add more depth to context
  261. *
  262. * @param int $node_identifier
  263. * webform nid
  264. *
  265. * @return string
  266. * i18n string name grouped by nid or uuid if module is available
  267. */
  268. function webform_localization_i18n_string_name($node_identifier) {
  269. if (module_exists('uuid')) {
  270. $node_identifier = current(entity_get_uuid_by_id('node', array($node_identifier)));
  271. }
  272. $name = array('webform', $node_identifier);
  273. $args = func_get_args();
  274. // Remove $node_identifier from args
  275. array_shift($args);
  276. foreach ($args as $arg) {
  277. $name[] = $arg;
  278. }
  279. return implode(':', $name);
  280. }
  281. /**
  282. * Delete translation source for all the translatable poperties
  283. *
  284. * Process components matching webforms configuration.
  285. */
  286. function webform_localization_delete_all_strings() {
  287. $query = db_select('webform_component', 'wc');
  288. $query->fields('wc');
  289. $query->condition('wl.expose_strings', 0, '=');
  290. $query->innerJoin('webform_localization', 'wl', 'wc.nid = wl.nid');
  291. $components = $query->execute()->fetchAllAssoc('cid');
  292. foreach ($components as $component) {
  293. $component = (array) $component;
  294. $component['extra'] = unserialize($component['extra']);
  295. webform_localization_component_delete_translation_strings($component);
  296. $component['extra'] = serialize($component['extra']);
  297. drupal_write_record('webform_component', $component, array('nid', 'cid'));
  298. }
  299. }
  300. /**
  301. * Remove translation source for all the translatable poperties.
  302. *
  303. * @param array $component
  304. * A webform component array.
  305. */
  306. function webform_localization_component_delete_translation_strings($component) {
  307. if (isset($component['extra']['translated_strings'])) {
  308. foreach ($component['extra']['translated_strings'] as $name) {
  309. i18n_string_remove($name);
  310. }
  311. }
  312. }
  313. /**
  314. * Update / create translation source for general webform poperties.
  315. *
  316. * @param array $properties
  317. * The form_state values that have been saved.
  318. */
  319. function webform_localization_update_translation_strings($properties) {
  320. if (!empty($properties['confirmation']['value'])) {
  321. $name = webform_localization_i18n_string_name($properties['nid'], 'confirmation');
  322. i18n_string($name, $properties['confirmation']['value'], array('update' => TRUE));
  323. }
  324. if (!empty($properties['submit_text'])) {
  325. $name = webform_localization_i18n_string_name($properties['nid'], 'submit_text');
  326. i18n_string($name, $properties['submit_text'], array('update' => TRUE));
  327. }
  328. // Allow to translate the redirect url if it's not set to none or the
  329. // default confirmation page.
  330. if (!in_array($properties['redirect_url'], array('<confirmation>', '<none>'))) {
  331. $name = webform_localization_i18n_string_name($properties['nid'], 'redirect_url');
  332. i18n_string($name, $properties['redirect_url'], array('update' => TRUE));
  333. }
  334. }
  335. /**
  336. * Translate general webform properties.
  337. *
  338. * @param $node
  339. * A node object.
  340. */
  341. function webform_localization_translate_strings(&$node, $update = FALSE) {
  342. $option = array('update' => $update, 'sanitize' => FALSE);
  343. $name = webform_localization_i18n_string_name($node->webform['nid'], 'confirmation');
  344. $node->webform['confirmation'] = i18n_string(
  345. $name,
  346. $node->webform['confirmation'],
  347. $option);
  348. $name = webform_localization_i18n_string_name($node->webform['nid'], 'submit_text');
  349. $node->webform['submit_text'] = i18n_string(
  350. $name,
  351. $node->webform['submit_text'],
  352. $option);
  353. // Allow to translate the redirect url if it's not set to none or the
  354. // default confirmation page.
  355. if (!in_array($node->webform['redirect_url'], array('<confirmation>', '<none>'))) {
  356. $name = webform_localization_i18n_string_name($node->webform['nid'], 'redirect_url');
  357. $node->webform['redirect_url'] = i18n_string($name, $node->webform['redirect_url'], $option);
  358. }
  359. }
  360. /**
  361. * Update / create translation source for webform email poperties.
  362. *
  363. * @param array $properties
  364. * The form_state values that have been saved.
  365. */
  366. function webform_localization_emails_update_translation_string($properties) {
  367. $nid = $properties['node']->webform['nid'];
  368. $eid = $properties['eid'];
  369. if (!empty($properties['subject_custom'])) {
  370. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'subject_custom');
  371. i18n_string($name, $properties['subject_custom'], array('update' => TRUE));
  372. }
  373. // Allow to translate the mail recipients if not based on a component.
  374. if (!empty($properties['email']) && !is_numeric($properties['email'])) {
  375. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'email');
  376. i18n_string($name, $properties['email'], array('update' => TRUE));
  377. }
  378. if (!empty($properties['from_name_custom'])) {
  379. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'from_name_custom');
  380. i18n_string($name, $properties['from_name_custom'], array('update' => TRUE));
  381. }
  382. if (!empty($properties['template'])) {
  383. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'template');
  384. i18n_string($name, $properties['template'], array('update' => TRUE));
  385. }
  386. }
  387. /**
  388. * Update / create translation source for webform email poperties.
  389. *
  390. * @param $emails
  391. * An array of webform emails.
  392. * @param $nid
  393. * The node Id of the webform.
  394. */
  395. function webform_localization_emails_translation_string_refresh($emails, $nid) {
  396. foreach ($emails as $email) {
  397. $eid = $email['eid'];
  398. if (!empty($email['subject']) && $email['subject'] != 'default') {
  399. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'subject_custom');
  400. i18n_string($name, $email['subject'], array('update' => TRUE));
  401. }
  402. // Allow to translate the mail recipients if not based on a component.
  403. if (!empty($email['email']) && !is_numeric($email['email'])) {
  404. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'email');
  405. i18n_string($name, $email['email'], array('update' => TRUE));
  406. }
  407. if (!empty($email['from_name']) && $email['from_name'] != 'default') {
  408. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'from_name_custom');
  409. i18n_string($name, $email['from_name'], array('update' => TRUE));
  410. }
  411. if (!empty($email['template']) && $email['template'] != 'default') {
  412. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'template');
  413. i18n_string($name, $email['template'], array('update' => TRUE));
  414. }
  415. }
  416. }
  417. /**
  418. * Translate webform email poperties.
  419. *
  420. * @param $node
  421. * A node object.
  422. */
  423. function webform_localization_email_translate_strings(&$node) {
  424. $nid = $node->webform['nid'];
  425. foreach ($node->webform['emails'] as $eid => &$email) {
  426. if (!empty($email['subject']) && $email['subject'] != 'default') {
  427. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'subject_custom');
  428. $email['subject'] = i18n_string($name, $email['subject']);
  429. }
  430. // Allow to translate the mail recipients if not based on a component.
  431. if (!empty($email['email']) && !is_numeric($email['email'])) {
  432. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'email');
  433. $email['email'] = i18n_string($name, $email['email']);
  434. }
  435. if (!empty($email['from_name']) && $email['from_name'] != 'default') {
  436. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'from_name_custom');
  437. $email['from_name'] = i18n_string($name, $email['from_name']);
  438. }
  439. if (!empty($email['template']) && $email['template'] != 'default') {
  440. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'template');
  441. $email['template'] = i18n_string($name, $email['template']);
  442. }
  443. }
  444. }
  445. /**
  446. * Remove translation source for webform email poperties.
  447. *
  448. * @param $eid
  449. * A webform email Id.
  450. * @param $nid
  451. * A node Id.
  452. */
  453. function webform_localization_emails_delete_translation_string($eid, $nid) {
  454. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'subject_custom');
  455. i18n_string_remove($name);
  456. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'from_name_custom');
  457. i18n_string_remove($name);
  458. $name = webform_localization_i18n_string_name($nid, 'email', $eid, 'template');
  459. i18n_string_remove($name);
  460. }
  461. /**
  462. * Translate general webform poperties.
  463. *
  464. * @param $node
  465. * A node object.
  466. */
  467. function webform_localization_delete_translate_strings($node) {
  468. $name = webform_localization_i18n_string_name($node->webform['nid'], 'confirmation');
  469. i18n_string_remove($name);
  470. $name = webform_localization_i18n_string_name($node->webform['nid'], 'submit_text');
  471. i18n_string_remove($name);
  472. foreach ($node->webform['emails'] as $eid => $value) {
  473. webform_localization_emails_delete_translation_string($eid, $node->nid);
  474. }
  475. }
  476. /**
  477. * Update i18n string contexts if uuid module is enabled/disabled.
  478. *
  479. */
  480. function webform_localization_uuid_update_strings($disabling_uuid = FALSE) {
  481. module_load_install('i18n_string');
  482. $old_ids = db_query('SELECT distinct type FROM {i18n_string} WHERE textgroup = :webform', array(
  483. ':webform' => 'webform'
  484. ))->fetchCol();
  485. variable_set('webform_localization_using_uuid', !$disabling_uuid);
  486. if (empty($old_ids)) {
  487. return;
  488. }
  489. if (!$disabling_uuid) {
  490. $old_context_ids = entity_get_uuid_by_id('node', array($old_ids));
  491. }
  492. else {
  493. // entity_get_id_by_uuid() do not work properly on hook_disable.
  494. $old_context_ids = webform_localization_get_id_by_uuid('node', array($old_ids));
  495. }
  496. foreach ($old_context_ids as $old_id => $new_id) {
  497. $old_context = 'webform:' . $old_id . ':*';
  498. $new_context = 'webform:' . $new_id . ':*';
  499. i18n_string_install_update_context($old_context, $new_context);
  500. }
  501. }
  502. /**
  503. * Helper function that retrieves entity IDs by their UUIDs.
  504. *
  505. *
  506. * @param $entity_type
  507. * The entity type we should be dealing with.
  508. * @param $uuids
  509. * An array of UUIDs for which we should find their entity IDs. If $revision
  510. * is TRUE this should be revision UUIDs instead.
  511. * @return
  512. * Array of entity IDs keyed by their UUIDs. If $revision is TRUE revision
  513. * IDs and UUIDs are returned instead.
  514. */
  515. function webform_localization_get_id_by_uuid($entity_type, $uuids) {
  516. if (empty($uuids)) {
  517. return array();
  518. }
  519. $info = entity_get_info($entity_type);
  520. $table = $info['base table'];
  521. $id_key = $info['entity keys']['id'];
  522. // The uuid key is not available at hook_disable.
  523. $core_info = uuid_get_core_entity_info();
  524. $uuid_key = $core_info['node']['entity keys']['uuid'];
  525. // Get all UUIDs in one query.
  526. return db_select($table, 't')
  527. ->fields('t', array($uuid_key, $id_key))
  528. ->condition($uuid_key, array_values($uuids), 'IN')
  529. ->execute()
  530. ->fetchAllKeyed();
  531. }
  532. /**
  533. * Helper function to replace an array key and its content.
  534. *
  535. * @param $array
  536. * Array To process.
  537. * @param $old_key
  538. * Array key to be replaced.
  539. * @param $new_key
  540. * The new array key.
  541. *
  542. */
  543. function _webform_localization_array_key_replace(&$array, $old_key, $new_key) {
  544. $keys = array_keys($array);
  545. $values = array_values($array);
  546. foreach ($keys as $k => $v) {
  547. if ($v == $old_key) {
  548. $keys[$k] = $new_key;
  549. }
  550. }
  551. $array = array_combine($keys, $values);
  552. }
  553. /**
  554. * Helper function to convert select / grid strings to array.
  555. *
  556. * @param $string_array
  557. * Array To process.
  558. *
  559. */
  560. function _webform_localization_string_to_key($string_array) {
  561. $key_array = array();
  562. $items = explode("\n", trim($string_array));
  563. foreach ($items as $item) {
  564. $item_data = explode('|', $item);
  565. $key_array[$item_data[0]] = $item_data[1];
  566. }
  567. return $key_array;
  568. }