PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/wpml-translation-management/classes/xliff/class-wpml-tm-xliff-writer.php

https://bitbucket.org/ed47/epfl-smart-move
PHP | 396 lines | 224 code | 61 blank | 111 comment | 19 complexity | aa9054e89b4632ca5d6ffbc00c0c391f MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * @package wpml-core
  4. */
  5. class WPML_TM_Xliff_Writer extends WPML_TM_Job_Factory_User {
  6. private $xliff_version;
  7. const TAB = "\t";
  8. /**
  9. * WPML_TM_xliff constructor.
  10. *
  11. * @param WPML_Translation_Job_Factory $job_factory
  12. * @param string $xliff_version
  13. */
  14. public function __construct( $job_factory, $xliff_version = TRANSLATION_PROXY_XLIFF_VERSION ) {
  15. parent::__construct( $job_factory );
  16. $this->xliff_version = $xliff_version;
  17. }
  18. /**
  19. * Generate a XLIFF file for a given job.
  20. *
  21. * @param int $job_id
  22. *
  23. * @return resource XLIFF representation of the job
  24. */
  25. public function get_job_xliff_file( $job_id ) {
  26. return $this->generate_xliff_file( $this->generate_job_xliff( $job_id ) );
  27. }
  28. /**
  29. * Generate a XLIFF string for a given post or external type (e.g. package) job.
  30. *
  31. * @param int $job_id
  32. *
  33. * @return string XLIFF representation of the job
  34. */
  35. public function generate_job_xliff( $job_id ) {
  36. /** @var TranslationManagement $iclTranslationManagement */
  37. global $iclTranslationManagement;
  38. // don't include not-translatable and don't auto-assign
  39. $job = $iclTranslationManagement->get_translation_job( (int) $job_id, false, false, 1 );
  40. $translation_units = $this->get_job_translation_units_data( $job );
  41. $original = $job_id . '-' . md5( $job_id . $job->original_doc_id );
  42. $external_file_url = $this->get_external_url( $job );
  43. $xliff = $this->generate_xliff( $original,
  44. $job->source_language_code,
  45. $job->language_code,
  46. $translation_units, $external_file_url );
  47. return $xliff;
  48. }
  49. /**
  50. * Generate a XLIFF file for a given set of strings.
  51. *
  52. * @param array $strings
  53. * @param string $source_language
  54. * @param string $target_language
  55. *
  56. * @return resource XLIFF file
  57. */
  58. public function get_strings_xliff_file( $strings, $source_language, $target_language ) {
  59. $strings = $this->pre_populate_strings_with_translation_memory( $strings, $source_language, $target_language );
  60. return $this->generate_xliff_file(
  61. $this->generate_xliff(
  62. uniqid('string-', true),
  63. $source_language,
  64. $target_language,
  65. $this->generate_strings_translation_units_data( $strings ) )
  66. );
  67. }
  68. private function generate_xliff(
  69. $original_id,
  70. $source_language,
  71. $target_language,
  72. array $translation_units = array(),
  73. $external_file_url = null
  74. ) {
  75. $xliff = new WPML_TM_XLIFF( $this->get_xliff_version(), '1.0', 'utf-8' );
  76. $string = $xliff->setFileAttributes( array(
  77. 'original' => $original_id,
  78. 'source-language' => $source_language,
  79. 'target-language' => $target_language,
  80. 'datatype' => 'plaintext',
  81. ) )
  82. ->setReferences( array(
  83. 'external-file' => $external_file_url,
  84. ) )->setPhaseGroup( $this->get_shortcodes_data() )
  85. ->setTranslationUnits( $translation_units )
  86. ->toString();
  87. return $string;
  88. }
  89. private function get_xliff_version() {
  90. switch ( $this->xliff_version ) {
  91. case '10':
  92. return '1.0';
  93. case '11':
  94. return '1.1';
  95. case '12':
  96. default:
  97. return '1.2';
  98. }
  99. }
  100. /**
  101. * Generate translation units for a given set of strings.
  102. *
  103. * The units are the actual content to be translated
  104. * Represented as a source and a target
  105. *
  106. * @param array $strings
  107. *
  108. * @return array The translation units representation
  109. */
  110. private function generate_strings_translation_units_data( $strings ) {
  111. $translation_units = array();
  112. foreach ( $strings as $string ) {
  113. $id = 'string-' . $string->id;
  114. $translation_units[] = $this->get_translation_unit_data( $id, 'string', $string->value, $string->translation, $string->translated_from_memory );
  115. }
  116. return $translation_units;
  117. }
  118. /**
  119. * @param stdClass[] $strings
  120. * @param string $source_lang
  121. * @param string $target_lang
  122. *
  123. * @return stdClass[]
  124. */
  125. private function pre_populate_strings_with_translation_memory( $strings, $source_lang, $target_lang ) {
  126. $strings_to_translate = wp_list_pluck( $strings, 'value' );
  127. $original_translated_map = $this->get_original_translated_map_from_translation_memory( $strings_to_translate, $source_lang, $target_lang );
  128. foreach ( $strings as &$string ) {
  129. $string->translated_from_memory = false;
  130. $string->translation = $string->value;
  131. if ( array_key_exists( $string->value, $original_translated_map ) ) {
  132. $string->translation = $original_translated_map[ $string->value ];
  133. $string->translated_from_memory = true;
  134. }
  135. }
  136. return $strings;
  137. }
  138. /**
  139. * @param array $strings_to_translate
  140. * @param string $source_lang
  141. * @param string $target_lang
  142. *
  143. * @return array
  144. */
  145. private function get_original_translated_map_from_translation_memory( $strings_to_translate, $source_lang, $target_lang ) {
  146. $args = array(
  147. 'strings' => $strings_to_translate,
  148. 'source_lang' => $source_lang,
  149. 'target_lang' => $target_lang,
  150. );
  151. $strings_in_memory = apply_filters( 'wpml_st_get_translation_memory', array(), $args );
  152. if ( $strings_in_memory ) {
  153. return wp_list_pluck( $strings_in_memory, 'translation', 'original' );
  154. }
  155. return array();
  156. }
  157. /**
  158. * Generate translation units.
  159. *
  160. * The units are the actual content to be translated
  161. * Represented as a source and a target
  162. *
  163. * @param stdClass $job
  164. *
  165. * @return array The translation units data
  166. */
  167. private function get_job_translation_units_data( $job ) {
  168. $translation_units = array();
  169. /** @var array $elements */
  170. $elements = $job->elements;
  171. if ( $elements ) {
  172. $elements = $this->pre_populate_elements_with_translation_memory( $elements, $job->source_language_code, $job->language_code );
  173. foreach ( $elements as $element ) {
  174. if ( 1 === (int) $element->field_translate ) {
  175. $field_data_translated = base64_decode( $element->field_data_translated );
  176. $field_data = base64_decode( $element->field_data );
  177. if ( 0 === strpos( $element->field_type, 'field-' ) ) {
  178. $field_data_translated = apply_filters( 'wpml_tm_xliff_export_translated_cf',
  179. $field_data_translated,
  180. $element );
  181. $field_data = apply_filters( 'wpml_tm_xliff_export_original_cf',
  182. $field_data,
  183. $element );
  184. }
  185. // check for untranslated fields and copy the original if required.
  186. if ( ! null === $field_data_translated || '' === $field_data_translated ) {
  187. $field_data_translated = $this->remove_invalid_chars( $field_data );
  188. }
  189. if ( $this->is_valid_unit_content( $field_data ) ) {
  190. $translation_units[] = $this->get_translation_unit_data( $element->field_type,
  191. $element->field_type,
  192. $field_data,
  193. $field_data_translated,
  194. $element->translated_from_memory );
  195. }
  196. }
  197. }
  198. }
  199. return $translation_units;
  200. }
  201. /**
  202. * @param array $elements
  203. * @param string $source_lang
  204. * @param string $target_lang
  205. *
  206. * @return array
  207. */
  208. private function pre_populate_elements_with_translation_memory( array $elements, $source_lang, $target_lang ) {
  209. $strings_to_translate = array();
  210. foreach ( $elements as &$element ) {
  211. $element->translated_from_memory = false;
  212. if ( preg_match( '/^package-string/', $element->field_type ) ) {
  213. $strings_to_translate[ $element->tid ] = base64_decode( $element->field_data );
  214. }
  215. }
  216. $original_translated_map = $this->get_original_translated_map_from_translation_memory( $strings_to_translate, $source_lang, $target_lang );
  217. if ( $original_translated_map ) {
  218. foreach ( $elements as &$element ) {
  219. if ( array_key_exists( $element->tid, $strings_to_translate )
  220. && array_key_exists( $strings_to_translate[ $element->tid ], $original_translated_map )
  221. ) {
  222. $element->field_data_translated = base64_encode( $original_translated_map[ $strings_to_translate[ $element->tid ] ] );
  223. $element->translated_from_memory = true;
  224. }
  225. }
  226. }
  227. return $elements;
  228. }
  229. private function get_translation_unit_data( $field_id, $field_name, $field_data, $field_data_translated, $is_translated_from_memory = false ) {
  230. global $sitepress;
  231. $field_data = $this->remove_invalid_chars( $field_data );
  232. $translation_unit = array();
  233. $field_data = $this->remove_line_breaks_inside_tags( $field_data );
  234. $field_data_translated = $this->remove_line_breaks_inside_tags( $field_data_translated );
  235. if ( $sitepress->get_setting( 'xliff_newlines' ) === WPML_XLIFF_TM_NEWLINES_REPLACE ) {
  236. $field_data = $this->replace_new_line_with_tag( $field_data );
  237. $field_data_translated = $this->replace_new_line_with_tag( $field_data_translated );
  238. }
  239. $translation_unit['attributes']['resname'] = $field_name;
  240. $translation_unit['attributes']['restype'] = 'string';
  241. $translation_unit['attributes']['datatype'] = 'html';
  242. $translation_unit['attributes']['id'] = $field_id;
  243. $translation_unit['source'] = array( 'content' => $field_data );
  244. $translation_unit['target'] = array( 'content' => $field_data_translated );
  245. if ( $is_translated_from_memory ) {
  246. $translation_unit['target']['attributes'] = array(
  247. 'state' => 'needs-review-translation',
  248. 'state-qualifier' => 'tm-suggestion',
  249. );
  250. }
  251. return $translation_unit;
  252. }
  253. /**
  254. * @param string $string
  255. *
  256. * @return string
  257. */
  258. protected function replace_new_line_with_tag( $string ) {
  259. return str_replace( array( "\n", "\r" ), array( '<br class="xliff-newline" />', '' ), $string );
  260. }
  261. private function remove_line_breaks_inside_tags( $string ) {
  262. return preg_replace_callback( '/(<[^>]*>)/m', array( $this, 'remove_line_breaks_inside_tag_callback' ), $string );
  263. }
  264. /**
  265. * @param array $matches
  266. *
  267. * @return string
  268. */
  269. private function remove_line_breaks_inside_tag_callback( array $matches ) {
  270. $tag_string = preg_replace( '/([\n\r\t ]+)/', ' ', $matches[0] );
  271. $tag_string = preg_replace( '/(<[\s]+)/', '<', $tag_string );
  272. return preg_replace( '/([\s]+>)/', '>', $tag_string );
  273. }
  274. /**
  275. * @param string $string
  276. *
  277. * Remove all characters below 0x20 except for 0x09, 0x0A and 0x0D
  278. * @see https://www.w3.org/TR/xml/#charsets
  279. *
  280. * @return string
  281. */
  282. private function remove_invalid_chars( $string ) {
  283. return preg_replace( '/[\x00-\x08\x0B-\x0C\x0E-\x1F]/', '', $string );
  284. }
  285. /**
  286. * Save a xliff string to a temporary file and return the file ressource
  287. * handle
  288. *
  289. * @param string $xliff_content
  290. *
  291. * @return resource XLIFF
  292. */
  293. private function generate_xliff_file( $xliff_content ) {
  294. $file = fopen( 'php://temp', 'rb+' );
  295. fwrite( $file, $xliff_content );
  296. rewind( $file );
  297. return $file;
  298. }
  299. /**
  300. * @param $job
  301. *
  302. * @return false|null|string
  303. */
  304. private function get_external_url( $job ) {
  305. $external_file_url = null;
  306. if ( isset( $job->original_doc_id ) && 'post' === $job->element_type_prefix ) {
  307. $external_file_url = get_permalink( $job->original_doc_id );
  308. return $external_file_url;
  309. }
  310. return $external_file_url;
  311. }
  312. private function get_shortcodes() {
  313. global $shortcode_tags;
  314. $shortcodes = array();
  315. if ( $shortcode_tags ) {
  316. $shortcodes = array_keys( $shortcode_tags );
  317. }
  318. return apply_filters( 'wpml_shortcode_list', $shortcodes );
  319. }
  320. /**
  321. * @return array
  322. */
  323. private function get_shortcodes_data() {
  324. $short_codes = $this->get_shortcodes();
  325. if ( $short_codes ) {
  326. return array(
  327. 'shortcodes' => array(
  328. 'process-name' => 'Shortcodes identification',
  329. 'note' => implode( ',', $short_codes )
  330. )
  331. );
  332. }
  333. return array();
  334. }
  335. }