Lingotek Translation - Version 1.5.3

Version Description

(2022-03-01) = - ELEMENTOR: Uploading Metadata content - WP: Add 'Ready-Interim' Status - WordPress: Update the "Mailto:" link for subscription terms - ELEMENTOR: Group elementor fields in Custom Field Configuration - ELEMENTOR: Apply hooks for when elementor content is updated - ELEMENTOR: Add support for embedded content - WP: Accept the 20,000 words exceeded response - Wordpress: Transition to Ready to Download

Download this release

Release Info

Developer sowmiya2021
Plugin Icon 128x128 Lingotek Translation
Version 1.5.3
Comparing to
See all releases

Code changes from version 1.5.2 to 1.5.3

CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
  # Changelog
 
 
 
 
2
  ## [1.4.16] - 2021-07-22
3
  - Merging hotfix 1.4.16 branch
1
  # Changelog
2
+
3
+ ## [1.5.4] - 2022-04-12
4
+ - Merging release 1.5.4 branch
5
+
6
  ## [1.4.16] - 2021-07-22
7
  - Merging hotfix 1.4.16 branch
admin/actions.php CHANGED
@@ -186,6 +186,11 @@ abstract class Lingotek_Actions {
186
  'icon' => 'edit',
187
  ),
188
 
 
 
 
 
 
189
  'current' => array(
190
  'title' => __( 'Current', 'lingotek-translation' ),
191
  'icon' => 'edit',
@@ -301,6 +306,15 @@ abstract class Lingotek_Actions {
301
  esc_url( $link ),
302
  $additional
303
  );
 
 
 
 
 
 
 
 
 
304
  }
305
  return sprintf(
306
  '<a class="lingotek-color dashicons dashicons-%s dashicons-%s-lingotek" title="%s" href="%s"%s></a>',
@@ -568,6 +582,16 @@ abstract class Lingotek_Actions {
568
  )
569
  );
570
  }
 
 
 
 
 
 
 
 
 
 
571
  } else {
572
  $actions['lingotek-upload'] = $this->get_action_link(
573
  array(
@@ -596,6 +620,15 @@ abstract class Lingotek_Actions {
596
  );
597
  }
598
 
 
 
 
 
 
 
 
 
 
599
  //need to request translations?
600
  $language = $this->get_language( $document->source );
601
  $all_locales = array_flip( $this->pllm->get_languages_list( array( 'fields' => 'locale' ) ) );
@@ -862,7 +895,7 @@ abstract class Lingotek_Actions {
862
  $document = $this->lgtm->get_group( $this->type, filter_input( INPUT_POST, 'id' ) );
863
  if ( $document ) {
864
  foreach ( $document->translations as $locale => $status ) {
865
- if ( 'pending' === $status || 'ready' === $status || 'interim' === $status || 'current' === $status ) {
866
  $document->create_translation( $locale );
867
  }
868
  }
186
  'icon' => 'edit',
187
  ),
188
 
189
+ 'ready-interim' => array(
190
+ 'title' => __( 'Interim Translation Ready to be Downloaded', 'lingotek-translation' ),
191
+ 'icon' => 'edit',
192
+ ),
193
+
194
  'current' => array(
195
  'title' => __( 'Current', 'lingotek-translation' ),
196
  'icon' => 'edit',
306
  esc_url( $link ),
307
  $additional
308
  );
309
+ } elseif ( 'ready-interim' === $name) {
310
+ return sprintf(
311
+ '<a class="lingotek-ready-interim-color dashicons dashicons-%s dashicons-%s-lingotek" title="%s" href="%s"%s></a>',
312
+ self::$icons[ $name ]['icon'],
313
+ self::$icons[ $name ]['icon'],
314
+ self::$icons[ $name ]['title'],
315
+ esc_url( $link ),
316
+ $additional
317
+ );
318
  }
319
  return sprintf(
320
  '<a class="lingotek-color dashicons dashicons-%s dashicons-%s-lingotek" title="%s" href="%s"%s></a>',
582
  )
583
  );
584
  }
585
+
586
+ if ( $document->has_translation_status( 'ready-interim' ) ) {
587
+ $actions['lingotek-status'] = $this->get_action_link(
588
+ array(
589
+ 'document_id' => $document->document_id,
590
+ 'action' => 'download',
591
+ )
592
+ );
593
+ }
594
+
595
  } else {
596
  $actions['lingotek-upload'] = $this->get_action_link(
597
  array(
620
  );
621
  }
622
 
623
+ if ( $document->has_translation_status( 'ready-interim' ) ) {
624
+ $actions['lingotek-status'] = $this->get_action_link(
625
+ array(
626
+ 'document_id' => $document->document_id,
627
+ 'action' => 'download',
628
+ )
629
+ );
630
+ }
631
+
632
  //need to request translations?
633
  $language = $this->get_language( $document->source );
634
  $all_locales = array_flip( $this->pllm->get_languages_list( array( 'fields' => 'locale' ) ) );
895
  $document = $this->lgtm->get_group( $this->type, filter_input( INPUT_POST, 'id' ) );
896
  if ( $document ) {
897
  foreach ( $document->translations as $locale => $status ) {
898
+ if ( 'pending' === $status || 'ready' === $status || 'interim' === $status || 'current' === $status || 'ready-interim' === $status ) {
899
  $document->create_translation( $locale );
900
  }
901
  }
admin/chip-target.php CHANGED
@@ -52,7 +52,7 @@ class Lingotek_Chip_Target extends Lingotek_Chip_Base {
52
  /* translators: %s: The lingotek locale. */
53
  $title = sprintf( __( 'Check translation status for %s', 'lingotek-translation' ), $language->lingotek_locale );
54
  }
55
- if ( in_array( $status, array( 'ready', 'error', 'failed' ), true ) ) {
56
  $uri = $post_actions->download_translation_url( $this->id, $this->document, $language->locale );
57
  /* translators: %s: The lingotek locale. */
58
  $title = sprintf( __( 'Download translation for %s', 'lingotek-translation' ), $language->lingotek_locale );
@@ -104,7 +104,7 @@ class Lingotek_Chip_Target extends Lingotek_Chip_Base {
104
  sprintf( __( 'Cancel translation', 'lingotek-translation' ), $language->locale )
105
  );
106
  }
107
- if ( in_array( $status, array( 'ready'), true ) ) {
108
  $urls[] = new Lingotek_Action_Url(
109
  $post_actions->download_translation_url( $this->id, $this->document, $language->locale ),
110
  sprintf( __( 'Download translation', 'lingotek-translation' ), $language->locale )
@@ -128,7 +128,7 @@ class Lingotek_Chip_Target extends Lingotek_Chip_Base {
128
  );
129
  }
130
  // I'm not sure if the status is error or failed, so we consider both for now.
131
- if ( in_array( $status, array( 'pending', 'ready', 'error', 'failed', 'current', 'interim', 'intermediate', 'edited' ), true ) ) {
132
  $urls[] = new Lingotek_Action_Url(
133
  $post_actions->workbench_url( $this->id, $this->document, $language->lingotek_locale ),
134
  __( 'Open in Lingotek workbench', 'lingotek-translation' )
52
  /* translators: %s: The lingotek locale. */
53
  $title = sprintf( __( 'Check translation status for %s', 'lingotek-translation' ), $language->lingotek_locale );
54
  }
55
+ if ( in_array( $status, array( 'ready', 'error', 'failed', 'ready-interim' ), true ) ) {
56
  $uri = $post_actions->download_translation_url( $this->id, $this->document, $language->locale );
57
  /* translators: %s: The lingotek locale. */
58
  $title = sprintf( __( 'Download translation for %s', 'lingotek-translation' ), $language->lingotek_locale );
104
  sprintf( __( 'Cancel translation', 'lingotek-translation' ), $language->locale )
105
  );
106
  }
107
+ if ( in_array( $status, array( 'ready', 'ready-interim' ), true ) ) {
108
  $urls[] = new Lingotek_Action_Url(
109
  $post_actions->download_translation_url( $this->id, $this->document, $language->locale ),
110
  sprintf( __( 'Download translation', 'lingotek-translation' ), $language->locale )
128
  );
129
  }
130
  // I'm not sure if the status is error or failed, so we consider both for now.
131
+ if ( in_array( $status, array( 'pending', 'ready', 'error', 'failed', 'current', 'interim', 'intermediate', 'edited', 'ready-interim' ), true ) ) {
132
  $urls[] = new Lingotek_Action_Url(
133
  $post_actions->workbench_url( $this->id, $this->document, $language->lingotek_locale ),
134
  __( 'Open in Lingotek workbench', 'lingotek-translation' )
admin/filters-columns.php CHANGED
@@ -63,6 +63,7 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
63
  }
64
  $this->print_patch_error();
65
  $this->print_cancel_error();
 
66
  $columns ['lingotek_source'] = __( 'Lingotek source', 'lingotek-translation' );
67
  $columns ['lingotek_targets'] = __( 'Lingotek translations', 'lingotek-translation' );
68
  foreach ( $this->model->get_languages_list() as $language ) {
@@ -120,6 +121,18 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
120
  }//end if
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  private function print_patch_error() {
124
  $lingotek_log_errors = get_option( 'lingotek_log_errors' );
125
  if ( empty( $lingotek_log_errors['patch_document_error'] ) ) {
63
  }
64
  $this->print_patch_error();
65
  $this->print_cancel_error();
66
+ $this->print_word_limit_error();
67
  $columns ['lingotek_source'] = __( 'Lingotek source', 'lingotek-translation' );
68
  $columns ['lingotek_targets'] = __( 'Lingotek translations', 'lingotek-translation' );
69
  foreach ( $this->model->get_languages_list() as $language ) {
121
  }//end if
122
  }
123
 
124
+ private function print_word_limit_error() {
125
+ $lingotek_log_errors = get_option( 'lingotek_log_errors', [] );
126
+ if ( !isset( $lingotek_log_errors['word_limit_error'] ) && empty( $lingotek_log_errors['word_limit_error'] ) ) {
127
+ return;
128
+ }
129
+ printf( '<div class="notice notice-error is-dismissible"><p>%s</p></div>',
130
+ __( 'Processed word limit exceeded. Please contact your local administrator or Lingotek Client Success <a href="mailto:sales@lingotek.com">(sales@lingotek.com)</a> for assistance.', 'lingotek-translation' ),
131
+ );
132
+ unset( $lingotek_log_errors['word_limit_error'] );
133
+ update_option( 'lingotek_log_errors', $lingotek_log_errors, false );
134
+ }
135
+
136
  private function print_patch_error() {
137
  $lingotek_log_errors = get_option( 'lingotek_log_errors' );
138
  if ( empty( $lingotek_log_errors['patch_document_error'] ) ) {
css/admin.css CHANGED
@@ -56,6 +56,17 @@ a.lingotek-interim-color:hover {
56
  color: #dbdbdb;
57
  }
58
 
 
 
 
 
 
 
 
 
 
 
 
59
  .lingotek-failed-color {
60
  color: #b71c1c;
61
  }
56
  color: #dbdbdb;
57
  }
58
 
59
+ .lingotek-ready-interim-color {
60
+ color: #b0b0b0;
61
+ }
62
+ a.lingotek-ready-interim-color {
63
+ color: #b0b0b0;
64
+ }
65
+
66
+ a.lingotek-ready-interim-color:hover {
67
+ color: #dbdbdb;
68
+ }
69
+
70
  .lingotek-failed-color {
71
  color: #b71c1c;
72
  }
css/statuses.css CHANGED
@@ -171,6 +171,24 @@ a.language-icon {
171
  color: #43a047;
172
  }
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  .language-icon.target-untracked {
175
  background-color:#999999;
176
  border: 1px solid #999999;
171
  color: #43a047;
172
  }
173
 
174
+ .language-icon.target-ready-interim {
175
+ background-color: #ffffff;
176
+ border: 1px solid #2196f3;
177
+ color: #2196f3;
178
+ }
179
+
180
+ .language-icon.target-ready-interim a {
181
+ color: #2196f3;
182
+ }
183
+
184
+ .language-icon.target-ready-interim a:hover {
185
+ color: #2196f3;
186
+ }
187
+
188
+ .language-icon.target-ready-interim:hover {
189
+ color: #2196f3;
190
+ }
191
+
192
  .language-icon.target-untracked {
193
  background-color:#999999;
194
  border: 1px solid #999999;
include/api.php CHANGED
@@ -163,6 +163,10 @@ class Lingotek_API extends Lingotek_HTTP {
163
  $this->get_error_message_from_response( $response ) : 'No error message set by Lingotek';
164
  $lingotek_log_errors['patch_document_error'] = __( "Payment required. Message: $error_message", 'lingotek-translation' );
165
  update_option( 'lingotek_log_errors', $lingotek_log_errors, false );
 
 
 
 
166
  }
167
  return false;
168
  }
@@ -198,6 +202,12 @@ class Lingotek_API extends Lingotek_HTTP {
198
  $document->save();
199
  return $this->patch_document( $body->next_document_id, $unformatted_args, $wp_id );
200
  }
 
 
 
 
 
 
201
 
202
  // We will handle patch errors separately. All we want to do is return the result of the current patch request to the user if payment is required
203
  // (although in theory this will never happen, because the WP model is document count based, and it will not allow them to upload documents until
@@ -286,7 +296,7 @@ class Lingotek_API extends Lingotek_HTTP {
286
 
287
  private function update_patch_error_message( $response, $status_code, $title ) {
288
  // Do not inform user if call was successful.
289
- if ( $status_code == 202 || $status_code == 410 || $status_code == 404 ) {
290
  return;
291
  }
292
  $lingotek_log_errors = get_option( 'lingotek_log_errors', array() );
@@ -494,7 +504,7 @@ class Lingotek_API extends Lingotek_HTTP {
494
  $response = $this->get( $this->api_url . "/document/$doc_id/translation/$locale" );
495
  if ( ! is_wp_error( $response ) && 200 == wp_remote_retrieve_response_code( $response ) ) {
496
  $b = json_decode( wp_remote_retrieve_body( $response ) );
497
- $status = $b->properties->percent_complete;
498
  }
499
 
500
  $this->log_error_on_response_failure(
@@ -526,6 +536,8 @@ class Lingotek_API extends Lingotek_HTTP {
526
  $translations[ $e->properties->locale_code ] = array(
527
  'progress' => $e->properties->status,
528
  'percent_complete' => $e->properties->percent_complete,
 
 
529
  );
530
  }
531
  }
@@ -755,6 +767,12 @@ class Lingotek_API extends Lingotek_HTTP {
755
  $document->source_failed();
756
  return false;
757
  }
 
 
 
 
 
 
758
  if ( 410 === $status_code || 404 === $status_code ) {
759
  // WP hooks automatically check source status so this might not get called
760
  $polylang_targets = array_keys( $document->translations );
163
  $this->get_error_message_from_response( $response ) : 'No error message set by Lingotek';
164
  $lingotek_log_errors['patch_document_error'] = __( "Payment required. Message: $error_message", 'lingotek-translation' );
165
  update_option( 'lingotek_log_errors', $lingotek_log_errors, false );
166
+ } elseif ( $code == 429 ) {
167
+ $lingotek_log_errors = get_option( 'lingotek_log_errors', [] );
168
+ $lingotek_log_errors['word_limit_error'] = true;
169
+ update_option( 'lingotek_log_errors', $lingotek_log_errors, false );
170
  }
171
  return false;
172
  }
202
  $document->save();
203
  return $this->patch_document( $body->next_document_id, $unformatted_args, $wp_id );
204
  }
205
+ if ( $status_code == 429 ) {
206
+ $lingotek_log_errors = get_option( 'lingotek_log_errors', [] );
207
+ $lingotek_log_errors['word_limit_error'] = true;
208
+ update_option( 'lingotek_log_errors', $lingotek_log_errors, false );
209
+ return false;
210
+ }
211
 
212
  // We will handle patch errors separately. All we want to do is return the result of the current patch request to the user if payment is required
213
  // (although in theory this will never happen, because the WP model is document count based, and it will not allow them to upload documents until
296
 
297
  private function update_patch_error_message( $response, $status_code, $title ) {
298
  // Do not inform user if call was successful.
299
+ if ( $status_code == 202 || $status_code == 410 || $status_code == 404 || $status_code == 429 ) {
300
  return;
301
  }
302
  $lingotek_log_errors = get_option( 'lingotek_log_errors', array() );
504
  $response = $this->get( $this->api_url . "/document/$doc_id/translation/$locale" );
505
  if ( ! is_wp_error( $response ) && 200 == wp_remote_retrieve_response_code( $response ) ) {
506
  $b = json_decode( wp_remote_retrieve_body( $response ) );
507
+ $status = $b->properties->ready_to_download;
508
  }
509
 
510
  $this->log_error_on_response_failure(
536
  $translations[ $e->properties->locale_code ] = array(
537
  'progress' => $e->properties->status,
538
  'percent_complete' => $e->properties->percent_complete,
539
+ 'ready_to_download' => $e->properties->ready_to_download
540
+
541
  );
542
  }
543
  }
767
  $document->source_failed();
768
  return false;
769
  }
770
+ if ( 429 === $status_code ) {
771
+ $lingotek_log_errors = get_option( 'lingotek_log_errors', [] );
772
+ $lingotek_log_errors['word_limit_error'] = true;
773
+ update_option( 'lingotek_log_errors', $lingotek_log_errors, false );
774
+ return false;
775
+ }
776
  if ( 410 === $status_code || 404 === $status_code ) {
777
  // WP hooks automatically check source status so this might not get called
778
  $polylang_targets = array_keys( $document->translations );
include/callback.php CHANGED
@@ -123,7 +123,12 @@ class Lingotek_Callback {
123
  $polylang->sync = new PLL_Admin_Sync( $polylang );
124
  // Map to WordPress locale.
125
  $locale = Lingotek::map_to_wp_locale( $_GET['locale'] );
126
- $document->is_automatic_download( $locale ) ? $document->create_translation( $locale, true, $type ) : $document->translation_ready( $locale );
 
 
 
 
 
127
  }//end if
128
  }
129
 
123
  $polylang->sync = new PLL_Admin_Sync( $polylang );
124
  // Map to WordPress locale.
125
  $locale = Lingotek::map_to_wp_locale( $_GET['locale'] );
126
+ if ($type == 'download_interim_translation') {
127
+ $document->is_automatic_download( $locale ) ? $document->create_translation( $locale, true, $type ) : $document->translation_ready_interim( $locale );
128
+ }
129
+ else {
130
+ $document->is_automatic_download( $locale ) ? $document->create_translation( $locale, true, $type ) : $document->translation_ready( $locale );
131
+ }
132
  }//end if
133
  }
134
 
include/group-post.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- /*
4
  * Translations groups for posts, pages and custom post types
5
  *
6
  * @since 0.2
@@ -9,12 +9,12 @@ class Lingotek_Group_Post extends Lingotek_Group {
9
 
10
  // Preference constant used for downloaded translations.
11
  const SAME_AS_SOURCE = 'SAME_AS_SOURCE';
12
- /*
13
  * set a translation term for an object
14
  *
15
  * @since 0.2
16
  *
17
- * @param int $object_id post id
18
  * @param object $language
19
  * @param string $document_id translation term name (Lingotek document id)
20
  */
@@ -33,7 +33,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
33
  self::_create( $object_id, $document_id, $data, 'post_translations' );
34
  }
35
 
36
- /*
37
  * returns content type fields
38
  *
39
  * @since 0.2
@@ -69,7 +69,11 @@ class Lingotek_Group_Post extends Lingotek_Group {
69
  if ( is_array( $custom_fields ) && ! empty( $custom_fields ) ) {
70
  foreach ( $custom_fields as $cf => $setting ) {
71
  if ( 'translate' == $setting ) {
72
- $arr['metas'][ $cf ] = $cf;
 
 
 
 
73
  }
74
  }
75
  }
@@ -79,14 +83,14 @@ class Lingotek_Group_Post extends Lingotek_Group {
79
  return apply_filters( 'lingotek_post_content_type_fields', $arr, $post_type );
80
  }
81
 
82
- /*
83
- * returns custom fields from the wpml-config.xml file
84
- *
85
- * @since 0.2
86
- *
87
- * @param string $post_type
88
- * @return array
89
- */
90
  public static function get_custom_fields_from_wpml() {
91
  $wpml_config = PLL_WPML_Config::instance();
92
  $arr = array();
@@ -103,7 +107,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
103
  return apply_filters( 'lingotek_post_content_type_fields_from_wpml', $arr );
104
  }
105
 
106
- /*
107
  * returns meta (custom) fields from the wp-postmeta database table
108
  *
109
  * @since 0.2
@@ -111,11 +115,12 @@ class Lingotek_Group_Post extends Lingotek_Group {
111
  * @return array
112
  */
113
  public static function get_custom_fields_from_wp_postmeta( $post_ID = null ) {
114
- $all_acf_fields = array();
115
- $custom_fields = get_option( 'lingotek_custom_fields', array() );
116
- $meta_black_list = array( '_encloseme', '_edit_last', '_edit_lock', '_wp_trash_meta_status', '_wp_trash_meta_time' );
117
- $arr = array();
118
- $keys = array();
 
119
 
120
  if ( $post_ID ) {
121
  $p = get_post( $post_ID );
@@ -159,16 +164,25 @@ class Lingotek_Group_Post extends Lingotek_Group {
159
  if ( in_array( $meta['meta_key'], $meta_black_list ) || in_array( $meta['meta_key'], $keys ) ) {
160
  unset( $metadata[ $key ] );
161
  }
162
- $keys[] = $meta['meta_key'];
 
 
 
 
 
163
  }
164
  $arr = array_merge( $arr, $metadata );
165
  }
166
  $arr = array_merge( $arr, $all_acf_fields );
 
 
 
 
167
  // allow plugins to modify the fields to translate
168
  return apply_filters( 'lingotek_post_custom_fields', $arr );
169
  }
170
 
171
- /*
172
  * tests whether a meta belongs to the Advanced Custom Fields plugin
173
  *
174
  * @since 0.2
@@ -179,7 +193,36 @@ class Lingotek_Group_Post extends Lingotek_Group {
179
  return ( ( substr( $key, 0, strlen( '_' ) ) === '_' ) && ( substr( $value, 0, strlen( 'field_' ) ) === 'field_' ) ) ? true : false;
180
  }
181
 
182
- /*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  * updates meta (custom) fields values in the lingotek_custom_fields option
184
  *
185
  * @since 0.2
@@ -228,7 +271,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
228
  update_option( 'lingotek_custom_fields', $custom_fields, false );
229
  }
230
 
231
- /*
232
  * returns cached meta (custom) fields values in the lingotek_custom_fields option
233
  *
234
  * @since 0.2
@@ -255,7 +298,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
255
  return $items;
256
  }
257
 
258
- /*
259
  * returns the content to translate
260
  *
261
  * @since 0.2
@@ -275,7 +318,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
275
  // check for advanced custom fields meta expects a string
276
  // so if array, check first item
277
  $value = is_array( $value ) ? current( $value ) : $value;
278
- if ( self::is_advanced_custom_fields_meta( $meta, $value ) || in_array( $meta, $meta_black_list ) ) {
279
  continue;
280
  }
281
  // Get all metas in array format
@@ -289,7 +332,16 @@ class Lingotek_Group_Post extends Lingotek_Group {
289
  if ( isset( $fields['metas'][ $acf_empty_string ] ) && function_exists( 'acf_get_field_groups' ) && function_exists( 'acf_get_fields' ) ) {
290
  $arr['metas'][ $acf_empty_string ] = get_post_meta( $post->ID, $acf_empty_string, true );
291
  }
292
- }
 
 
 
 
 
 
 
 
 
293
  // send slug for translation only if it has been modified
294
  elseif ( 'post_name' == $key && empty( $content_types[ $post->post_type ]['fields'][ $key ] ) ) {
295
  // Default slug created by WordPress.
@@ -310,7 +362,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
310
  return $valid;
311
  }
312
 
313
- /*
314
  * requests translations to Lingotek TMS
315
  *
316
  * @since 0.1
@@ -322,7 +374,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
322
  }
323
  }
324
 
325
- /*
326
  * create a translation downloaded from Lingotek TMS
327
  *
328
  * @since 0.1
@@ -334,10 +386,10 @@ class Lingotek_Group_Post extends Lingotek_Group {
334
  // Removes content sanitization so YouTube videos, links, etc don't get removed when inserting translations
335
  remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
336
  remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
337
- $client = new Lingotek_API();
338
- $status = $client->get_translation_status( $this->document_id, $locale );
339
 
340
- if ( $status === -1 ) {
341
  return;
342
  }
343
 
@@ -376,7 +428,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
376
  self::copy_or_ignore_metas( $post->ID, $tr_id );
377
 
378
  wp_update_post( $tr_post );
379
- if ( $status !== 100 ) {
380
  $this->safe_translation_status_update( $locale, 'interim' );
381
  } else {
382
  $this->safe_translation_status_update( $locale, 'current' );
@@ -384,7 +436,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
384
  }
385
 
386
  // create new translation
387
- elseif ( ( $this->translations[ $locale ] == 'ready' || $this->translations[ $locale ] == 'pending' ) || $automatic ) {
388
  $content_type_options = get_option( 'lingotek_content_type' );
389
  if ( ! isset( $content_type_options[ $post->post_type ]['fields']['post_name'] ) ) {
390
  // Forces the creation of a new default slug if not translated by Lingotek.
@@ -410,7 +462,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
410
  $tr_lang = $this->pllm->get_language( $locale );
411
  PLL()->model->post->set_language( $tr_id, $tr_lang );
412
  $this->safe_translation_status_update( $locale, 'current', array( $tr_lang->slug => $tr_id ) );
413
- if ( $status !== 100 ) {
414
  $this->safe_translation_status_update( $locale, 'interim' );
415
  }
416
  wp_set_object_terms( $tr_id, $this->term_id, 'post_translations' );
@@ -461,7 +513,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
461
  empty( $trimmed_excerpt );
462
  }
463
 
464
- /*
465
  * copies source meta strings to translations or deletes meta if set to ignore
466
  *
467
  * @since 1.0.9
@@ -474,6 +526,14 @@ class Lingotek_Group_Post extends Lingotek_Group {
474
  foreach ( $post_custom_fields as $key => $source_meta ) {
475
  // Set to blank string to ignore if no lingotek setting has been set.
476
  $setting = isset( $custom_fields[ $key ] ) ? $custom_fields[ $key ] : '';
 
 
 
 
 
 
 
 
477
  if ( 'copy' === $setting || 'hide-copy' === $setting ) {
478
  $source_meta = current( get_post_meta( $post_id, $key ) );
479
  update_post_meta( $tr_id, $key, $source_meta );
@@ -483,7 +543,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
483
  }
484
  }
485
 
486
- /*
487
  * inserts translated meta values into translations
488
  *
489
  * @since 1.0.9
@@ -491,12 +551,19 @@ class Lingotek_Group_Post extends Lingotek_Group {
491
  protected static function copy_translated_metas( $translation_metas, $tr_id ) {
492
  if ( ! empty( $translation_metas ) ) {
493
  foreach ( $translation_metas as $key => $meta ) {
 
 
 
494
  update_post_meta( $tr_id, $key, $meta );
495
  }
 
 
 
 
496
  }
497
  }
498
 
499
- /*
500
  * checks if content should be automatically uploaded
501
  *
502
  * @since 0.2
@@ -507,7 +574,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
507
  return 'automatic' == Lingotek_Model::get_profile_option( 'upload', get_post_type( $this->source ), $this->get_source_language(), false, $this->source ) && parent::is_automatic_upload();
508
  }
509
 
510
- /*
511
  * get the the language of the source post
512
  *
513
  * @since 0.2
@@ -518,7 +585,7 @@ class Lingotek_Group_Post extends Lingotek_Group {
518
  return PLL()->model->post->get_language( $this->source );
519
  }
520
 
521
- /*
522
  * creates hash for lingotek_hash term
523
  *
524
  * @since 1.0.9
1
  <?php
2
 
3
+ /**
4
  * Translations groups for posts, pages and custom post types
5
  *
6
  * @since 0.2
9
 
10
  // Preference constant used for downloaded translations.
11
  const SAME_AS_SOURCE = 'SAME_AS_SOURCE';
12
+ /**
13
  * set a translation term for an object
14
  *
15
  * @since 0.2
16
  *
17
+ * @param int $object_id post id
18
  * @param object $language
19
  * @param string $document_id translation term name (Lingotek document id)
20
  */
33
  self::_create( $object_id, $document_id, $data, 'post_translations' );
34
  }
35
 
36
+ /**
37
  * returns content type fields
38
  *
39
  * @since 0.2
69
  if ( is_array( $custom_fields ) && ! empty( $custom_fields ) ) {
70
  foreach ( $custom_fields as $cf => $setting ) {
71
  if ( 'translate' == $setting ) {
72
+ if ( $cf === Lingotek_Metadata_Elementor::$elementor_content ) {
73
+ $arr['metas'][ Lingotek_Metadata_Elementor::$elementor_data ] = Lingotek_Metadata_Elementor::$elementor_data;
74
+ } else {
75
+ $arr['metas'][ $cf ] = $cf;
76
+ }
77
  }
78
  }
79
  }
83
  return apply_filters( 'lingotek_post_content_type_fields', $arr, $post_type );
84
  }
85
 
86
+ /**
87
+ * returns custom fields from the wpml-config.xml file
88
+ *
89
+ * @since 0.2
90
+ *
91
+ * @param string $post_type
92
+ * @return array
93
+ */
94
  public static function get_custom_fields_from_wpml() {
95
  $wpml_config = PLL_WPML_Config::instance();
96
  $arr = array();
107
  return apply_filters( 'lingotek_post_content_type_fields_from_wpml', $arr );
108
  }
109
 
110
+ /**
111
  * returns meta (custom) fields from the wp-postmeta database table
112
  *
113
  * @since 0.2
115
  * @return array
116
  */
117
  public static function get_custom_fields_from_wp_postmeta( $post_ID = null ) {
118
+ $all_acf_fields = array();
119
+ $elementor_field_group = array();
120
+ $custom_fields = get_option( 'lingotek_custom_fields', array() );
121
+ $meta_black_list = array( '_encloseme', '_edit_last', '_edit_lock', '_wp_trash_meta_status', '_wp_trash_meta_time' );
122
+ $arr = array();
123
+ $keys = array();
124
 
125
  if ( $post_ID ) {
126
  $p = get_post( $post_ID );
164
  if ( in_array( $meta['meta_key'], $meta_black_list ) || in_array( $meta['meta_key'], $keys ) ) {
165
  unset( $metadata[ $key ] );
166
  }
167
+ if ( self::is_elementor_meta( $meta['meta_key'] ) ) {
168
+ $elementor_field_group[ $meta['meta_key'] ] = $meta['meta_key'];
169
+ // Remove elementor field
170
+ unset( $metadata [ $key ] );
171
+ }
172
+ $keys[ $meta['meta_key'] ] = $meta['meta_key'];
173
  }
174
  $arr = array_merge( $arr, $metadata );
175
  }
176
  $arr = array_merge( $arr, $all_acf_fields );
177
+ if ( is_plugin_active( 'elementor/elementor.php' ) && ! empty( $elementor_field_group ) ) {
178
+ // Set Lingotek elementor field
179
+ self::group_elementor_fields( $arr, $elementor_field_group );
180
+ }
181
  // allow plugins to modify the fields to translate
182
  return apply_filters( 'lingotek_post_custom_fields', $arr );
183
  }
184
 
185
+ /**
186
  * tests whether a meta belongs to the Advanced Custom Fields plugin
187
  *
188
  * @since 0.2
193
  return ( ( substr( $key, 0, strlen( '_' ) ) === '_' ) && ( substr( $value, 0, strlen( 'field_' ) ) === 'field_' ) ) ? true : false;
194
  }
195
 
196
+ /**
197
+ * tests whether a meta belongs to the Elementor plugin
198
+ *
199
+ * @since 1.5.1
200
+ *
201
+ * @return bool
202
+ */
203
+ protected static function is_elementor_meta( $key ) {
204
+ return ( is_string( $key ) && 0 === strpos( $key, '_elementor' ) ) ? true : false;
205
+ }
206
+
207
+ /**
208
+ * Checks if certain elementor fields exist, then groups all elementor metadata keys under one meta key
209
+ *
210
+ * @since 1.5.3
211
+ *
212
+ * @param array $metas The array of meta keys that users can translate.
213
+ * @param array $elementor_field_group The array of elementor metadata found in the posts
214
+ */
215
+ public static function group_elementor_fields( &$metas, $elementor_field_group ) {
216
+ // Create Lingotek elementor meta field if posts exist with elementor content
217
+ if ( in_array( Lingotek_Metadata_Elementor::$elementor_data, $elementor_field_group ) && in_array( Lingotek_Metadata_Elementor::$elementor_edit_mode, $elementor_field_group ) ) {
218
+ $metas[] = array(
219
+ 'meta_key' => Lingotek_Metadata_Elementor::$elementor_content,
220
+ 'meta_value' => $elementor_field_group,
221
+ );
222
+ }
223
+ }
224
+
225
+ /**
226
  * updates meta (custom) fields values in the lingotek_custom_fields option
227
  *
228
  * @since 0.2
271
  update_option( 'lingotek_custom_fields', $custom_fields, false );
272
  }
273
 
274
+ /**
275
  * returns cached meta (custom) fields values in the lingotek_custom_fields option
276
  *
277
  * @since 0.2
298
  return $items;
299
  }
300
 
301
+ /**
302
  * returns the content to translate
303
  *
304
  * @since 0.2
318
  // check for advanced custom fields meta expects a string
319
  // so if array, check first item
320
  $value = is_array( $value ) ? current( $value ) : $value;
321
+ if ( self::is_advanced_custom_fields_meta( $meta, $value ) || self::is_elementor_meta( $meta ) || in_array( $meta, $meta_black_list ) ) {
322
  continue;
323
  }
324
  // Get all metas in array format
332
  if ( isset( $fields['metas'][ $acf_empty_string ] ) && function_exists( 'acf_get_field_groups' ) && function_exists( 'acf_get_fields' ) ) {
333
  $arr['metas'][ $acf_empty_string ] = get_post_meta( $post->ID, $acf_empty_string, true );
334
  }
335
+
336
+ // Check if elementor data exists in post
337
+ if ( isset( $fields['metas'][ Lingotek_Metadata_Elementor::$elementor_data ] ) && 'builder' === get_post_meta( $post->ID, Lingotek_Metadata_Elementor::$elementor_edit_mode, true ) ) {
338
+ $arr['metas'][ Lingotek_Metadata_Elementor::$elementor_data ] = Lingotek_Metadata_Elementor::get_post_metadata_content( $post->ID );
339
+ // We need to keep the original meta string with the translation
340
+ $arr['metas'][ Lingotek_Metadata_Elementor::$elementor_data . '_original' ] = get_post_meta( $post->ID, Lingotek_Metadata_Elementor::$elementor_data, true );
341
+ // Since the elementor data overwrites post_content this prevents duplicating the content sent up for translation.
342
+ $arr['post']['post_content'] = '';
343
+ }
344
+ }//end if
345
  // send slug for translation only if it has been modified
346
  elseif ( 'post_name' == $key && empty( $content_types[ $post->post_type ]['fields'][ $key ] ) ) {
347
  // Default slug created by WordPress.
362
  return $valid;
363
  }
364
 
365
+ /**
366
  * requests translations to Lingotek TMS
367
  *
368
  * @since 0.1
374
  }
375
  }
376
 
377
+ /**
378
  * create a translation downloaded from Lingotek TMS
379
  *
380
  * @since 0.1
386
  // Removes content sanitization so YouTube videos, links, etc don't get removed when inserting translations
387
  remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
388
  remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
389
+ $client = new Lingotek_API();
390
+ $ready_to_download = $client->get_translation_status( $this->document_id, $locale );
391
 
392
+ if ( $ready_to_download === -1 ) {
393
  return;
394
  }
395
 
428
  self::copy_or_ignore_metas( $post->ID, $tr_id );
429
 
430
  wp_update_post( $tr_post );
431
+ if ( ! $ready_to_download ) {
432
  $this->safe_translation_status_update( $locale, 'interim' );
433
  } else {
434
  $this->safe_translation_status_update( $locale, 'current' );
436
  }
437
 
438
  // create new translation
439
+ elseif ( ( $this->translations[ $locale ] == 'ready' || $this->translations[ $locale ] == 'pending' ) || $automatic || $this->translations[ $locale ] == 'ready-interim' ) {
440
  $content_type_options = get_option( 'lingotek_content_type' );
441
  if ( ! isset( $content_type_options[ $post->post_type ]['fields']['post_name'] ) ) {
442
  // Forces the creation of a new default slug if not translated by Lingotek.
462
  $tr_lang = $this->pllm->get_language( $locale );
463
  PLL()->model->post->set_language( $tr_id, $tr_lang );
464
  $this->safe_translation_status_update( $locale, 'current', array( $tr_lang->slug => $tr_id ) );
465
+ if ( ! $ready_to_download ) {
466
  $this->safe_translation_status_update( $locale, 'interim' );
467
  }
468
  wp_set_object_terms( $tr_id, $this->term_id, 'post_translations' );
513
  empty( $trimmed_excerpt );
514
  }
515
 
516
+ /**
517
  * copies source meta strings to translations or deletes meta if set to ignore
518
  *
519
  * @since 1.0.9
526
  foreach ( $post_custom_fields as $key => $source_meta ) {
527
  // Set to blank string to ignore if no lingotek setting has been set.
528
  $setting = isset( $custom_fields[ $key ] ) ? $custom_fields[ $key ] : '';
529
+ if ( self::is_elementor_meta( $key ) ) {
530
+ // Use custom lingotek elementor field
531
+ $setting = isset( $custom_fields [ Lingotek_Metadata_Elementor::$elementor_content ] ) ? $custom_fields[ Lingotek_Metadata_Elementor::$elementor_content ] : '';
532
+ // If the setting is translate but key is not elementor data, set to copy
533
+ if ( $setting === 'translate' && $key !== Lingotek_Metadata_Elementor::$elementor_data ) {
534
+ $setting = 'copy';
535
+ }
536
+ }
537
  if ( 'copy' === $setting || 'hide-copy' === $setting ) {
538
  $source_meta = current( get_post_meta( $post_id, $key ) );
539
  update_post_meta( $tr_id, $key, $source_meta );
543
  }
544
  }
545
 
546
+ /**
547
  * inserts translated meta values into translations
548
  *
549
  * @since 1.0.9
551
  protected static function copy_translated_metas( $translation_metas, $tr_id ) {
552
  if ( ! empty( $translation_metas ) ) {
553
  foreach ( $translation_metas as $key => $meta ) {
554
+ if ( $key === Lingotek_Metadata_Elementor::$elementor_data || $key === Lingotek_Metadata_Elementor::$elementor_data . '_original' ) {
555
+ continue;
556
+ }
557
  update_post_meta( $tr_id, $key, $meta );
558
  }
559
+
560
+ if ( isset( $translation_metas[ Lingotek_Metadata_Elementor::$elementor_data ], $translation_metas[ Lingotek_Metadata_Elementor::$elementor_data . '_original' ] ) ) {
561
+ Lingotek_Metadata_Elementor::set_post_metadata_content( $tr_id, $translation_metas[ Lingotek_Metadata_Elementor::$elementor_data . '_original' ], $translation_metas[ Lingotek_Metadata_Elementor::$elementor_data ] );
562
+ }
563
  }
564
  }
565
 
566
+ /**
567
  * checks if content should be automatically uploaded
568
  *
569
  * @since 0.2
574
  return 'automatic' == Lingotek_Model::get_profile_option( 'upload', get_post_type( $this->source ), $this->get_source_language(), false, $this->source ) && parent::is_automatic_upload();
575
  }
576
 
577
+ /**
578
  * get the the language of the source post
579
  *
580
  * @since 0.2
585
  return PLL()->model->post->get_language( $this->source );
586
  }
587
 
588
+ /**
589
  * creates hash for lingotek_hash term
590
  *
591
  * @since 1.0.9
include/group.php CHANGED
@@ -362,15 +362,13 @@ abstract class Lingotek_Group {
362
  }
363
  $wp_locale = $lingotek_locale_to_pll_locale[ $lingotek_locale ];
364
 
365
- if ( $locale_status['percent_complete'] < 100 && $this->translations[ $wp_locale ] !== 'interim' ) {
366
- if ( strtolower( $locale_status['progress'] ) === 'in_progress' ) {
367
- $this->translations[ $wp_locale ] = 'ready';
368
- } else {
369
- $this->translations[ $wp_locale ] = 'pending';
370
- }
371
- } elseif ( $this->translations[ $wp_locale ] === 'interim' && $locale_status['percent_complete'] === 100 ) {
372
  $this->translations[ $wp_locale ] = 'ready';
373
- } elseif ( ( ! isset( $this->translations[ $wp_locale ] ) ) || ( $this->translations[ $wp_locale ] !== 'current' ) && $this->translations[ $wp_locale ] !== 'interim' ) {
 
 
374
  $this->translations[ $wp_locale ] = 'ready';
375
  }
376
  }
@@ -399,6 +397,16 @@ abstract class Lingotek_Group {
399
  $this->safe_translation_status_update( $locale, 'ready' );
400
  }
401
 
 
 
 
 
 
 
 
 
 
 
402
  /**
403
  * attempts to create all translations from an object
404
  *
362
  }
363
  $wp_locale = $lingotek_locale_to_pll_locale[ $lingotek_locale ];
364
 
365
+ if ( !$locale_status['ready_to_download'] && ! in_array( $this->translations[ $wp_locale ], array( 'interim', 'ready-interim' ) ) ) {
366
+ $this->translations[ $wp_locale ] = 'pending';
367
+ } elseif ( in_array( $this->translations[ $wp_locale ], array( 'interim', 'ready-interim' ) ) && $locale_status['ready_to_download'] ) {
 
 
 
 
368
  $this->translations[ $wp_locale ] = 'ready';
369
+ } elseif ( in_array( $this->translations[ $wp_locale ], array( 'interim', 'ready-interim' ) ) && !$locale_status['ready_to_download'] ) {
370
+ $this->translations[ $wp_locale ] = $this->translations[ $wp_locale ];
371
+ } elseif ( ( ! isset( $this->translations[ $wp_locale ] ) ) || ( $this->translations[ $wp_locale ] !== 'current' ) && ! in_array( $this->translations[ $wp_locale ], array( 'interim', 'ready-interim' ) ) ) {
372
  $this->translations[ $wp_locale ] = 'ready';
373
  }
374
  }
397
  $this->safe_translation_status_update( $locale, 'ready' );
398
  }
399
 
400
+ /**
401
+ * sets translation status to interim
402
+ *
403
+ * @since 0.1
404
+ * @uses Lingotek_Group::safe_translation_status_update() as the status can be automatically set by the TMS callback
405
+ */
406
+ public function translation_ready_interim( $locale ) {
407
+ $this->safe_translation_status_update( $locale, 'ready-interim' );
408
+ }
409
+
410
  /**
411
  * attempts to create all translations from an object
412
  *
include/metadata-elementor.php ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Special processing for Elementor metadata content
4
+ *
5
+ * @since 1.5.3
6
+ */
7
+ class Lingotek_Metadata_Elementor {
8
+ /**
9
+ * Reusable variable of elementor data meta key
10
+ *
11
+ * @var string
12
+ */
13
+ public static $elementor_data = '_elementor_data';
14
+
15
+ /**
16
+ * Reusable variable of elementor edit mode meta key. Determines if the elementor data should be applied to the post.
17
+ *
18
+ * @var string
19
+ */
20
+ public static $elementor_edit_mode = '_elementor_edit_mode';
21
+
22
+ /**
23
+ * Reusable variable of Lingotek custom field for Elementor field group
24
+ *
25
+ * @var string
26
+ */
27
+ public static $elementor_content = 'elementor_content';
28
+
29
+ /**
30
+ * Widgets that contain translatable content.
31
+ *
32
+ * @var array
33
+ */
34
+ protected static $text_widgets = array(
35
+ 'text-editor',
36
+ 'heading',
37
+ 'button',
38
+ 'call-to-action',
39
+ 'form',
40
+ 'text-path',
41
+ 'blockquote',
42
+ 'forms',
43
+ 'animated-headline',
44
+ 'price-list',
45
+ 'price-table',
46
+ 'table-of-contents',
47
+ 'lottie',
48
+ 'theme-site-title',
49
+ );
50
+
51
+ /**
52
+ * Widgets that embedded content that need to be handled differently, i.e. images
53
+ * The values are the translatable fields in the settings of each widget type.
54
+ *
55
+ * @var array
56
+ */
57
+ public static $embedded_widgets = array(
58
+ 'image' => array(
59
+ 'caption',
60
+ ),
61
+ 'image-box' => array(
62
+ 'title_text',
63
+ 'description_text',
64
+ ),
65
+ 'hotspot' => array(
66
+ 'hotspot',
67
+ 'hotspot_label',
68
+ 'hotspot_tooltip_content',
69
+ ),
70
+ 'testimonial-carousel' => array(
71
+ 'slides',
72
+ 'content',
73
+ 'name',
74
+ 'title',
75
+ ),
76
+ 'blockquote' => array(
77
+ 'blockquote_content',
78
+ 'tweet_button_label',
79
+ 'author_name',
80
+ ),
81
+ 'posts' => array(
82
+ 'classic_meta_separator',
83
+ 'classic_read_more_text',
84
+ 'cards_meta_separator',
85
+ 'cards_read_more_text',
86
+ 'full_content_meta_separator',
87
+ 'pagination_prev_label',
88
+ 'pagination_next_label',
89
+ 'text',
90
+ 'load_more_no_posts_custom_message',
91
+ ),
92
+ 'gallery' => array(
93
+ 'gallery_title',
94
+ 'show_all_galleries_label',
95
+ ),
96
+ );
97
+
98
+ /**
99
+ * Get the translatable content from the _elementor_data
100
+ *
101
+ * @since 1.5.3
102
+ *
103
+ * @param int $post_id The source post id.
104
+ * @return array list of translatable content, where the indexes are the element ids, and the values are the element content
105
+ */
106
+ public static function get_post_metadata_content( $post_id ) {
107
+ $content_list = array();
108
+ $meta_json_string = get_post_meta( $post_id, self::$elementor_data, true );
109
+ $meta_array = json_decode( $meta_json_string, true );
110
+ self::get_elements( $meta_array, $content_list );
111
+ return $content_list;
112
+ }
113
+
114
+ /**
115
+ * Sets the translated content into the copied _elementor_data json string
116
+ *
117
+ * @since 1.5.3
118
+ *
119
+ * @param int $post_id The translated post id.
120
+ * @param string $element_json The original json string.
121
+ * @param array $translation_list The list of translated content.
122
+ * @return boolean True if the content in the original json string is successfully replaced with the translated content, and the meta field for the translated post is successfully updated, false otherwise.
123
+ */
124
+ public static function set_post_metadata_content( $post_id, $element_json, $translation_list ) {
125
+ // We need to set the translations in the meta string from the original post
126
+ $meta_array = json_decode( $element_json, true );
127
+ if ( ! $meta_array && ! $translation_list ) {
128
+ // We either don't have the original json string or there is no translated content.
129
+ return false;
130
+ }
131
+ self::set_translations( $meta_array, $translation_list );
132
+ $updated_meta_json_string = wp_slash( wp_json_encode( $meta_array ) );
133
+ return update_post_meta( $post_id, self::$elementor_data, $updated_meta_json_string );
134
+ }
135
+
136
+ /**
137
+ * Parse through the list of elements and pull translatable elements into a list
138
+ *
139
+ * @since 1.5.3
140
+ *
141
+ * @param array $element_list List of elementor elements.
142
+ * @param array $content_list List of translatable elements.
143
+ */
144
+ public static function get_elements( $element_list, &$content_list ) {
145
+ foreach ( $element_list as $element ) {
146
+ $element_in_array = isset( $element['id'], $content_list[ $element['id'] ] ) ? true : false;
147
+ $element_settings = isset( $element['settings'] ) && ! empty( $element['settings'] ) ? $element['settings'] : false;
148
+ $element_widget_type = isset( $element['widgetType'] ) ? $element['widgetType'] : false;
149
+
150
+ // Check if element has already been stored, and if the widget has translatable text.
151
+ $widget_editor_type = in_array( $element_widget_type, self::$text_widgets, true );
152
+ $embedded_widget = isset( self::$embedded_widgets[ $element_widget_type ] ) ? true : false;
153
+ if ( ! $element_in_array && $widget_editor_type && $element_settings ) {
154
+ $content_list[ $element['id'] ] = $element_settings;
155
+ } elseif ( ! $element_in_array && $embedded_widget && $element_settings ) {
156
+ $translatable_fields = isset( self::$embedded_widgets[ $element_widget_type ] ) ? self::$embedded_widgets[ $element_widget_type ] : array();
157
+ $content_list[ $element['id'] ] = self::get_translatable_fields( $translatable_fields, $element_settings );
158
+ }
159
+ $sub_elements = isset( $element['elements'] ) && ! empty( $element['elements'] ) ? $element['elements'] : false;
160
+ if ( $sub_elements ) {
161
+ // Check embedded elements for more translatable content.
162
+ self::get_elements( $sub_elements, $content_list );
163
+ }
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Sets the translated content in the elementor elements in for the translated post
169
+ *
170
+ * @since 1.5.3
171
+ *
172
+ * @param array $element_list List of elementor elements.
173
+ * @param array $translation_list List of translated content.
174
+ */
175
+ public static function set_translations( &$element_list, $translation_list ) {
176
+ foreach ( $element_list as $key => $element ) {
177
+ $translated_element = isset( $element['id'], $translation_list[ $element['id'] ] ) ? $translation_list[ $element['id'] ] : false;
178
+ $element_settings = isset( $element['settings'] ) && ! empty( $element['settings'] ) ? $element['settings'] : false;
179
+ $element_widget_type = isset( $element['widgetType'] ) ? $element['widgetType'] : false;
180
+ $widget_editor_type = in_array( $element_widget_type, self::$text_widgets, true );
181
+ $embedded_widget = isset( self::$embedded_widgets[ $element_widget_type ] ) ? true : false;
182
+ // Check that element translation exists, has a valid editor type, and has a settings key.
183
+ if ( $translated_element && $widget_editor_type && $element_settings ) {
184
+ $element['settings'] = $translated_element;
185
+ } elseif ( $translated_element && $element_settings && $embedded_widget ) {
186
+ $translatable_fields = isset( self::$embedded_widgets[ $element_widget_type ] ) ? self::$embedded_widgets[ $element_widget_type ] : array();
187
+ $element['settings'] = self::set_translatable_fields( $translatable_fields, $element_settings, $translated_element );
188
+ }
189
+ $sub_elements = isset( $element['elements'] ) && ! empty( $element['elements'] ) ? $element['elements'] : false;
190
+ if ( $sub_elements ) {
191
+ // Check if any embedded elements have translations and update them.
192
+ self::set_translations( $sub_elements, $translation_list );
193
+ $element['elements'] = $sub_elements;
194
+ }
195
+ // Save element to list of elements, along with any changes.
196
+ $element_list[ $key ] = $element;
197
+ }//end foreach
198
+ }
199
+
200
+ /**
201
+ * Gets the translatable fields from embedded content.
202
+ *
203
+ * @since 1.5.3
204
+ *
205
+ * @param array $translatable_fields Array containing the fields that we want to translate.
206
+ * @param array $settings Array containing the current widget settings.
207
+ * @return array
208
+ */
209
+ public static function get_translatable_fields( $translatable_fields, $settings ) {
210
+ $temp_settings = array();
211
+ // we only want to translate specific fields, anything not translatable has its key prepended with `_`
212
+ foreach ( $settings as $setting => $value ) {
213
+ // if an image exists within a widget setting, we only want to translate the alt text.
214
+ if ( 'image' === $setting && is_array( $value ) ) {
215
+ $temp_settings[ $setting ] = self::get_translatable_fields( array( 'alt' ), $value );
216
+ } elseif ( in_array( $setting, $translatable_fields, true ) ) {
217
+ // If setting is a translatable field then we save it as is.
218
+ if ( is_array( $value ) && ! self::has_string_keys( $value ) ) {
219
+ // If the field is a JSON array and not a JSON object, we go through each item to check that it is translatable.
220
+ foreach ( $value as $value_key => $value_settings ) {
221
+ $value[ $value_key ] = self::get_translatable_fields( $translatable_fields, $value_settings );
222
+ }
223
+ }
224
+ $temp_settings[ $setting ] = $value;
225
+ } else {
226
+ // Non translatable fields are prepended with `_` to mark them as non translatable.
227
+ // If the setting is not translatable, but if it is an array, we check if it's a JSON object, or a JSON array.
228
+ if ( is_array( $value ) ) {
229
+ $temp_value = array();
230
+ $has_string_keys = self::has_string_keys( $value );
231
+ foreach ( $value as $value_key => $value_settings ) {
232
+ // If it's a JSON object then we check if any of its fields are translatable.
233
+ if ( $has_string_keys ) {
234
+ if ( in_array( $value_key, $translatable_fields, true ) ) {
235
+ $temp_value[ $value_key ] = $value_settings;
236
+ } else {
237
+ $temp_value[ '_' . $value_key ] = $value_settings;
238
+ }
239
+ } else {
240
+ // Else we check each item in the array for translatable fields.
241
+ $temp_value[ $value_key ] = self::get_translatable_fields( $translatable_fields, $value_settings );
242
+ }
243
+ }
244
+ // We save any changes made to the field value.
245
+ $value = $temp_value;
246
+ }
247
+ $temp_settings[ '_' . $setting ] = $value;
248
+ }//end if
249
+ }//end foreach
250
+ return $temp_settings;
251
+ }
252
+
253
+ /**
254
+ * Sets the translations back in the correct translatable fields
255
+ *
256
+ * @since 1.5.3
257
+ *
258
+ * @param array $translatable_fields Array of translatable fields for current element type.
259
+ * @param array $settings Array of the current element settings.
260
+ * @param array $translation Translated version of the current element settings.
261
+ * @return array Return the current element settings with the correct translations properly inserted.
262
+ */
263
+ public static function set_translatable_fields( $translatable_fields, $settings, $translation ) {
264
+ foreach ( $translation as $key => $value ) {
265
+ // Images are a special case since we only want to translate the alt text, and this is shorter than adding 'alt'
266
+ // and 'image' to all the content that potentially uses images in their widget
267
+ if ( 'image' === $key && is_array( $value ) ) {
268
+ $settings[ $key ] = self::set_translatable_fields( array( 'alt' ), $settings[ $key ], $value );
269
+ continue;
270
+ }
271
+ // If the key is a string and not a translatable field, we assume that an underscore was added to mark it as non-translatable, so it's removed.
272
+ if ( ! in_array( $key, $translatable_fields, true ) && is_string( $key ) ) {
273
+ $temp_key = substr( $key, 1 );
274
+ } else {
275
+ $temp_key = $key;
276
+ }
277
+ // If the current translated value is an array then we have to update each of its
278
+ // fields individually incase not all of them are translatable.
279
+ if ( is_array( $value ) ) {
280
+ $value = self::set_translatable_fields( $translatable_fields, $settings[ $temp_key ], $value );
281
+ }
282
+ // Replace current setting value with translated value.
283
+ $settings[ $temp_key ] = $value;
284
+ }//end foreach
285
+ return $settings;
286
+ }
287
+
288
+ /**
289
+ * Returns true if the array has all string keys, else returns false
290
+ *
291
+ * @since 1.5.3
292
+ *
293
+ * @param array $array Array to test.
294
+ * @return boolean
295
+ */
296
+ public static function has_string_keys( array $array ) {
297
+ return count( array_filter( array_keys( $array ), 'is_string' ) ) > 0;
298
+ }
299
+ }
js/updater.js CHANGED
@@ -108,6 +108,10 @@ jQuery(document).ready(function($) {
108
  updateGenericBulkLink(tr, data, key, 'download' , 'Download translations of this item from Lingotek TMS', 'Download translations');
109
  updateIndicator(td, data, key, locale, 'download', 'Ready to download', 'download');
110
  break;
 
 
 
 
111
  case 'deleted':
112
  if (locale === data[key]['source']){
113
  updateUploadBulkLink(tr, data, key, 'upload' , 'Upload deleted item to Lingotek TMS', 'Upload to Lingotek');
@@ -224,6 +228,24 @@ jQuery(document).ready(function($) {
224
  $(td).prepend(request_link);
225
  }
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  function updateGenericBulkLink(tr, data, key, action, title, text){
228
  var row_actions = $(tr).find('.row-actions');
229
  if($(row_actions).find('.lingotek-' + action).length === 0){
108
  updateGenericBulkLink(tr, data, key, 'download' , 'Download translations of this item from Lingotek TMS', 'Download translations');
109
  updateIndicator(td, data, key, locale, 'download', 'Ready to download', 'download');
110
  break;
111
+ case 'ready-interim':
112
+ updateGenericBulkLink(tr, data, key, 'status' , 'Update translations status of this item in Lingotek TMS', 'Update translations status ');
113
+ updateReadyInterimIcon(td, data, key, locale);
114
+ break;
115
  case 'deleted':
116
  if (locale === data[key]['source']){
117
  updateUploadBulkLink(tr, data, key, 'upload' , 'Upload deleted item to Lingotek TMS', 'Upload to Lingotek');
228
  $(td).prepend(request_link);
229
  }
230
 
231
+ function updateReadyInterimIcon(td, data, key, locale) {
232
+ $(td).find('.lingotek-color').remove();
233
+ $(td).find('.lingotek-ready-interim-color').remove();
234
+ if ($(td).find('dashicons-no').length > 0) {
235
+ $(td).find('.dashicons-no').remove();
236
+ } else if ($(td).find('.dashicons-clock').length > 0) {
237
+ $(td).find('.dashicons-clock').remove();
238
+ }
239
+ $(td).find('.lingotek-professional-icon').remove();
240
+ var icon = 'edit';
241
+ $(td).find('.lingotek-ready-interim-color').remove();
242
+ var request_link = $('<a></a>').attr('href', data[key][locale]['workbench_link'])
243
+ .attr('title', 'Interim Translation Ready to be Downloaded')
244
+ .attr('target','_blank')
245
+ .addClass('lingotek-ready-interim-color dashicons dashicons-' + icon + ' dashicons-' + icon + '-lingotek');
246
+ $(td).prepend(request_link);
247
+ }
248
+
249
  function updateGenericBulkLink(tr, data, key, action, title, text){
250
  var row_actions = $(tr).find('.row-actions');
251
  if($(row_actions).find('.lingotek-' + action).length === 0){
lingotek.php CHANGED
@@ -2,7 +2,7 @@
2
  /**
3
  Plugin name: Lingotek Translation
4
  Plugin URI: http://lingotek.com/wordpress#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wplingotektranslationplugin
5
- Version: 1.5.2
6
  Author: Lingotek and Frédéric Demarle
7
  Author uri: http://lingotek.com
8
  Description: Lingotek offers convenient cloud-based localization and translation.
@@ -19,7 +19,7 @@ if ( ! function_exists( 'add_action' ) ) {
19
  }
20
 
21
  // Plugin version (should match above meta).
22
- define( 'LINGOTEK_VERSION', '1.5.2' );
23
  define( 'LINGOTEK_MIN_PLL_VERSION', '1.8' );
24
  // Plugin name as known by WordPress.
25
  define( 'LINGOTEK_BASENAME', plugin_basename( __FILE__ ) );
2
  /**
3
  Plugin name: Lingotek Translation
4
  Plugin URI: http://lingotek.com/wordpress#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wplingotektranslationplugin
5
+ Version: 1.5.3
6
  Author: Lingotek and Frédéric Demarle
7
  Author uri: http://lingotek.com
8
  Description: Lingotek offers convenient cloud-based localization and translation.
19
  }
20
 
21
  // Plugin version (should match above meta).
22
+ define( 'LINGOTEK_VERSION', '1.5.3' );
23
  define( 'LINGOTEK_MIN_PLL_VERSION', '1.8' );
24
  // Plugin name as known by WordPress.
25
  define( 'LINGOTEK_BASENAME', plugin_basename( __FILE__ ) );
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://lingotek.com/
4
  Tags: automation, bilingual, international, language, Lingotek, localization, multilanguage, multilingual, translate, translation
5
  Requires at least: 3.8
6
  Tested up to: 5.8
7
- Stable tag: 1.5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -123,12 +123,23 @@ For more, visit the [Lingotek documentation site](https://lingotek.atlassian.net
123
  5. The Lingotek Translation plugin provides the ability to Copy, Translate, and Ignore each specific custom field. Our plugin supports Wordpress custom fields and advanced custom fields.
124
 
125
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
126
  = 1.5.2 (2021-11-05) =
127
  - Removed the cancel action from the target dropdown
128
  - Fixed duplicated message "Your Lingotek account has been successfully connected."
129
  - Unable to add language-only locale if same language code already exists
130
  - Fixed Cancel translation for specific targets from bulk action displays an irrelevant message
131
-
132
  = 1.5.1 (2021-08-19) =
133
  - Compatibility issues with PolyLang 3.1
134
  - Add check for the Document Status
4
  Tags: automation, bilingual, international, language, Lingotek, localization, multilanguage, multilingual, translate, translation
5
  Requires at least: 3.8
6
  Tested up to: 5.8
7
+ Stable tag: 1.5.3
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
123
  5. The Lingotek Translation plugin provides the ability to Copy, Translate, and Ignore each specific custom field. Our plugin supports Wordpress custom fields and advanced custom fields.
124
 
125
  == Changelog ==
126
+
127
+ = 1.5.3 (2022-03-01) =
128
+ - ELEMENTOR: Uploading Metadata content
129
+ - WP: Add 'Ready-Interim' Status
130
+ - WordPress: Update the "Mailto:" link for subscription terms
131
+ - ELEMENTOR: Group elementor fields in Custom Field Configuration
132
+ - ELEMENTOR: Apply hooks for when elementor content is updated
133
+ - ELEMENTOR: Add support for embedded content
134
+ - WP: Accept the 20,000 words exceeded response
135
+ - Wordpress: Transition to Ready to Download
136
+
137
  = 1.5.2 (2021-11-05) =
138
  - Removed the cancel action from the target dropdown
139
  - Fixed duplicated message "Your Lingotek account has been successfully connected."
140
  - Unable to add language-only locale if same language code already exists
141
  - Fixed Cancel translation for specific targets from bulk action displays an irrelevant message
142
+
143
  = 1.5.1 (2021-08-19) =
144
  - Compatibility issues with PolyLang 3.1
145
  - Add check for the Document Status