tmgmt_i18n_string.module 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. /**
  3. * @file
  4. * Source plugin for the Translation Management system that handles i18n strings.
  5. */
  6. /**
  7. * Implements hook_tmgmt_source_plugin_info().
  8. */
  9. function tmgmt_i18n_string_tmgmt_source_plugin_info() {
  10. $info['i18n_string'] = array(
  11. 'label' => t('i18n String'),
  12. 'description' => t('Source handler for i18n strings.'),
  13. 'plugin controller class' => 'TMGMTI18nStringSourcePluginController',
  14. 'ui controller class' => 'TMGMTI18nStringDefaultSourceUIController',
  15. );
  16. foreach (i18n_object_info() as $object_type => $object_info) {
  17. // Only consider object types that have string translation information.
  18. if (isset($object_info['string translation'])) {
  19. $info['i18n_string']['item types'][$object_type] = $object_info['title'];
  20. }
  21. }
  22. return $info;
  23. }
  24. /**
  25. * Gets i18n strings for given type and label.
  26. *
  27. * @param string $type
  28. * i18n object type.
  29. * @param string $search_label
  30. * Label to search for.
  31. * @param string $target_language
  32. * Target language.
  33. * @param string $target_status
  34. * Target status.
  35. *
  36. * @return array
  37. * List of i18n strings data.
  38. */
  39. function tmgmt_i18n_string_get_strings($type, $search_label = NULL, $target_language = NULL, $target_status = 'untranslated_or_outdated') {
  40. $info = i18n_object_info($type);
  41. $languages = drupal_map_assoc(array_keys(language_list()));
  42. $select = db_select('i18n_string', 'i18n_s');
  43. $select->addTag('tmgmt_sources_search');
  44. $select->addMetaData('plugin', 'i18n_string');
  45. $select->addMetaData('type', $type);
  46. $select->condition('i18n_s.textgroup', $info['string translation']['textgroup']);
  47. if (!empty($target_language) && in_array($target_language, $languages)) {
  48. if ($target_status == 'untranslated_or_outdated') {
  49. $or = db_or();
  50. $or->isNull("lt_$target_language.language");
  51. $or->condition("lt_$target_language.i18n_status", I18N_STRING_STATUS_UPDATE);
  52. $select->condition($or);
  53. }
  54. elseif ($target_status == 'outdated') {
  55. $select->condition("lt_$target_language.i18n_status", I18N_STRING_STATUS_UPDATE);
  56. }
  57. elseif ($target_status == 'untranslated') {
  58. $select->isNull("lt_$target_language.language");
  59. }
  60. }
  61. if (isset($info['string translation']['type'])) {
  62. $select->condition('i18n_s.type', $info['string translation']['type']);
  63. }
  64. elseif ($type == 'field' || $type == 'field_instance') {
  65. // Fields and field instances share the same textgroup. Use list of bundles
  66. // to include/exclude field_instances.
  67. $bundles = array();
  68. foreach (entity_get_info() as $entity_info) {
  69. $bundles = array_merge($bundles, array_keys($entity_info['bundles']));
  70. }
  71. $select->condition('i18n_s.objectid', $bundles, $type == 'field_instance' ? 'IN' : 'NOT IN');
  72. }
  73. $select->join('locales_source', 'ls', 'ls.lid = i18n_s.lid');
  74. $select->addField('ls', 'source');
  75. if (!empty($search_label)) {
  76. $select->condition('ls.source', "%$search_label%", 'LIKE');
  77. }
  78. foreach ($languages as $langcode) {
  79. $langcode = str_replace('-', '', $langcode);
  80. $select->leftJoin('locales_target', "lt_$langcode", "i18n_s.lid = %alias.lid AND %alias.language = '$langcode'");
  81. $select->addField("lt_$langcode", 'language', "lang_$langcode");
  82. }
  83. $select->fields("i18n_s", array('lid', 'textgroup', 'context', 'type', 'objectid'));
  84. $select->addExpression("concat(i18n_s.textgroup, ':', i18n_s.type, ':', i18n_s.objectid)", 'job_item_id');
  85. $select->orderBy('i18n_s.context');
  86. $select->groupBy('type');
  87. $select->groupBy('objectid');
  88. $select = $select->extend('PagerDefault')->limit(variable_get('tmgmt_source_list_limit', 20));
  89. return $select->execute()->fetchAll();
  90. }
  91. /**
  92. * Implements hook_form_ID_alter().
  93. *
  94. * Adds request translation capabilities into i18n translate tab.
  95. */
  96. function tmgmt_i18n_string_form_i18n_string_translate_page_overview_form_alter(&$form, &$form_state) {
  97. $object = $form['object']['#value'];
  98. // Create the id: textgroup:type:objectid.
  99. $id = $object->get_textgroup() . ':' . implode(':', $object->get_string_context());
  100. $source_language = variable_get_value('i18n_string_source_language');
  101. $existing_items = tmgmt_job_item_load_latest('i18n_string', $object->get_type(), $id, $source_language);
  102. $form['top_actions']['#type'] = 'actions';
  103. $form['top_actions']['#weight'] = -10;
  104. tmgmt_ui_add_cart_form($form['top_actions'], $form_state, 'i18n_string', $object->get_type(), $id);
  105. $form['languages']['#type'] = 'tableselect';
  106. // Append lang code so that we can use it
  107. foreach ($form['languages']['#rows'] as $lang => $row) {
  108. if (isset($existing_items[$lang])) {
  109. $states = tmgmt_job_item_states();
  110. $row['status'] = $states[$existing_items[$lang]->state];
  111. if ($existing_items[$lang]->isNeedsReview()) {
  112. $row['operations'] .= ' | ' . l(t('review'), 'admin/tmgmt/items/' . $existing_items[$lang]->tjiid, array('query' => array('destination' => $_GET['q'])));
  113. }
  114. elseif ($existing_items[$lang]->isActive()) {
  115. $row['operations'] .= ' | ' . l(t('in progress'), 'admin/tmgmt/items/' . $existing_items[$lang]->tjiid, array('query' => array('destination' => $_GET['q'])));
  116. }
  117. }
  118. $form['languages']['#options'][$id . ':' . $lang] = $row;
  119. if ($lang == $source_language || isset($existing_items[$lang])) {
  120. $form['languages'][$id . ':' . $lang] = array(
  121. '#type' => 'checkbox',
  122. '#disabled' => TRUE,
  123. );
  124. }
  125. }
  126. unset($form['languages']['#rows'], $form['languages']['#theme']);
  127. $form['actions']['request_translation'] = array(
  128. '#type' => 'submit',
  129. '#value' => t('Request translation'),
  130. '#submit' => array('tmgmt_i18n_string_translate_form_submit'),
  131. '#validate' => array('tmgmt_i18n_string_translate_form_validate'),
  132. );
  133. }
  134. /**
  135. * Validation callback for the entity translation overview form.
  136. */
  137. function tmgmt_i18n_string_translate_form_validate($form, &$form_state) {
  138. $selected = array_filter($form_state['values']['languages']);
  139. if (empty($selected)) {
  140. form_set_error('languages', t('You have to select at least one language for requesting a translation.'));
  141. }
  142. }
  143. function tmgmt_i18n_string_translate_form_submit($form, &$form_state) {
  144. $items = array_filter($form_state['values']['languages']);
  145. $type = $form_state['values']['object']->get_type();
  146. $source_lang = variable_get_value('i18n_string_source_language');
  147. $jobs = array();
  148. $target_lang_registry = array();
  149. // Loop through entities and create individual jobs for each source language.
  150. foreach ($items as $item) {
  151. $item_parts = explode(':', $item);
  152. $target_lang = array_pop($item_parts);
  153. $key = implode(':', $item_parts);
  154. // For given source lang no job exists yet.
  155. if (!isset($target_lang_registry[$target_lang])) {
  156. // Create new job.
  157. $job = tmgmt_job_create($source_lang, $target_lang, $GLOBALS['user']->uid);
  158. // Add initial job item.
  159. $job->addItem('i18n_string', $type, $key);
  160. // Add job identifier into registry
  161. $target_lang_registry[$target_lang] = $job->tjid;
  162. // Add newly created job into jobs queue.
  163. $jobs[$job->tjid] = $job;
  164. }
  165. // We have a job for given source lang, so just add new job item for the
  166. // existing job.
  167. else {
  168. $jobs[$target_lang_registry[$target_lang]]->addItem('i18n_string', $type, $key);
  169. }
  170. }
  171. tmgmt_ui_job_checkout_and_redirect($form_state, $jobs);
  172. }
  173. /**
  174. * Implements hook_i18n_object_info_alter().
  175. */
  176. function tmgmt_i18n_string_i18n_object_info_alter(&$info) {
  177. $entity_info = entity_get_info();
  178. // Add a entity key to the object info if neither load callback nor entity
  179. // keys are set and the object is an entity_type.
  180. // @todo: Add this as default in EntityDefaultI18nStringController.
  181. foreach ($info as $name => &$object) {
  182. if (!isset($object['load callback']) && !isset($object['entity']) && isset($entity_info[$name])) {
  183. $object['entity'] = $name;
  184. }
  185. }
  186. }
  187. /**
  188. * Returns the i18n wrapper object.
  189. *
  190. * I18N objects with one or two keys are supported.
  191. *
  192. * @param string $type
  193. * I18n object type.
  194. * @param object $i18n_string
  195. * Object with type and objectid properties.
  196. *
  197. * @return i18n_string_object_wrapper
  198. */
  199. function tmgmt_i18n_string_get_wrapper($type, $i18n_string) {
  200. $object_key = i18n_object_info($type, 'key');
  201. // Special handling for i18nviews.
  202. if ($type == 'views') {
  203. // The construct method needs the full view object.
  204. $view = views_get_view($i18n_string->objectid);
  205. $wrapper = i18n_get_object($type, $i18n_string->objectid, $view);
  206. return $wrapper;
  207. }
  208. // Special handling for i18n_panels.
  209. $panels_objects = array(
  210. 'pane_configuration' => 'panels_pane',
  211. 'display_configuration' => 'panels_display',
  212. );
  213. if (in_array($type, array_keys($panels_objects))) {
  214. ctools_include('export');
  215. $wrapper = FALSE;
  216. switch ($type) {
  217. case 'display_configuration':
  218. $object_array = ctools_export_load_object($panels_objects[$type], 'conditions', array('uuid' => $i18n_string->objectid));
  219. $wrapper = i18n_get_object($type, $i18n_string->objectid, $object_array[$i18n_string->objectid]);
  220. break;
  221. case 'pane_configuration':
  222. $obj = db_query("SELECT * FROM {panels_pane} WHERE uuid = :uuid", array(':uuid' => $i18n_string->objectid))->fetchObject();
  223. if ($obj) {
  224. $pane = ctools_export_unpack_object($panels_objects[$type], $obj);
  225. $translation = i18n_panels_get_i18n_translation_object($pane);
  226. $translation->uuid = $pane->uuid;
  227. $wrapper = i18n_get_object($type, $i18n_string->objectid, $translation);
  228. }
  229. break;
  230. default:
  231. break;
  232. }
  233. return $wrapper;
  234. }
  235. // Special handling if the object has two keys. Assume that they
  236. // mean type and object id.
  237. if ($type == 'field') {
  238. // Special case for fields which expect the type to be the identifier.
  239. $wrapper = i18n_get_object($type, $i18n_string->type);
  240. return $wrapper;
  241. }
  242. elseif ($type == 'field_instance') {
  243. // Special case for field instances, which use the field name as type and
  244. // bundle as object id. We don't know the entity_type, so we loop over all
  245. // entity_types to search for the bundle. This will clash if different
  246. // entity types have bundles with the same names.
  247. foreach (entity_get_info() as $entity_type => $entity_info) {
  248. if (isset($entity_info['bundles'][$i18n_string->objectid])) {
  249. list($type_key, $objectid_key) = $object_key;
  250. $wrapper = i18n_get_object($type, array(
  251. $type_key => $i18n_string->type,
  252. $objectid_key => $i18n_string->objectid
  253. ), field_info_instance($entity_type, $i18n_string->type, $i18n_string->objectid));
  254. return $wrapper;
  255. }
  256. }
  257. }
  258. elseif (count($object_key) == 2) {
  259. list($type_key, $objectid_key) = $object_key;
  260. $wrapper = i18n_get_object($type, array(
  261. $type_key => $i18n_string->type,
  262. $objectid_key => $i18n_string->objectid
  263. ));
  264. return $wrapper;
  265. }
  266. else {
  267. // Otherwise, use the object id.
  268. $wrapper = i18n_get_object($type, $i18n_string->objectid);
  269. return $wrapper;
  270. }
  271. }
  272. /**
  273. * Implements hook_tmgmt_source_suggestions()
  274. */
  275. function tmgmt_i18n_string_tmgmt_source_suggestions(array $items, TMGMTJob $job) {
  276. $suggestions = array();
  277. foreach ($items as $item) {
  278. if (($item instanceof TMGMTJobItem) && ($item->item_type == 'node')) {
  279. // Load translatable menu items related to this node.
  280. $query = db_select('menu_links', 'ml')
  281. ->condition('ml.link_path', 'node/' . $item->item_id)
  282. ->fields('ml', array('mlid'));
  283. $query->join('menu_custom', 'mc', 'ml.menu_name = mc.menu_name AND mc.i18n_mode = ' . I18N_MODE_MULTIPLE);
  284. $results = $query->execute()->fetchAllAssoc('mlid');
  285. foreach ($results as $result) {
  286. $menu_link = menu_link_load($result->mlid);
  287. // Add suggestion.
  288. $suggestions[] = array(
  289. 'job_item' => tmgmt_job_item_create('i18n_string', 'menu_link', "menu:item:{$result->mlid}"),
  290. 'reason' => t('Menu link @title', array('@title' => $menu_link['link_title'])),
  291. 'from_item' => $item->tjiid,
  292. );
  293. }
  294. }
  295. }
  296. return $suggestions;
  297. }