Lingotek Translation - Version 1.5.0

Version Description

(2021-08-05) = * New UI - The source and translation icons have been replaced with chips. This makes it easier to view which stage the translations are in. - Added dropdown actions to the source and target chips. - Added Cancel option for specific targets in the action dropdown. *New Gone status - This will give you a visual indicator when a document or target has been archived or deleted.

Download this release

Release Info

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

Code changes from version 1.4.16 to 1.5.0

admin/action-url.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Helper class to model an action interacting with Lingotek.
5
+ *
6
+ * @since 1.5.0
7
+ */
8
+ class Lingotek_Action_Url {
9
+
10
+ /**
11
+ * The uri.
12
+ *
13
+ * @var string
14
+ */
15
+ protected $uri;
16
+
17
+ /**
18
+ * The title.
19
+ *
20
+ * @var string
21
+ */
22
+ protected $title;
23
+
24
+ public function __construct( $uri, $title ) {
25
+ $this->uri = $uri;
26
+ $this->title = $title;
27
+ }
28
+
29
+ public function getTitle() {
30
+ return $this->title;
31
+ }
32
+
33
+ public function getUri() {
34
+ return $this->uri;
35
+ }
36
+
37
+ public function render() {
38
+ return sprintf(
39
+ '
40
+ <li>
41
+ <a href="%s">%s</a>
42
+ </li>
43
+ ',
44
+ $this->getUri(),
45
+ $this->getTitle()
46
+ );
47
+ }
48
+
49
+ }
admin/actions.php CHANGED
@@ -90,48 +90,56 @@ abstract class Lingotek_Actions {
90
  'action' => __( 'Upload to Lingotek', 'lingotek-translation' ),
91
  'progress' => __( 'Uploading...', 'lingotek-translation' ),
92
  'description' => __( 'Upload this item to Lingotek TMS', 'lingotek-translation' ),
 
93
  ),
94
 
95
  'request' => array(
96
  'action' => __( 'Request translations', 'lingotek-translation' ),
97
  'progress' => __( 'Requesting translations...', 'lingotek-translation' ),
98
  'description' => __( 'Request translations of this item to Lingotek TMS', 'lingotek-translation' ),
 
99
  ),
100
 
101
  'status' => array(
102
  'action' => __( 'Update translations status', 'lingotek-translation' ),
103
  'progress' => __( 'Updating translations status...', 'lingotek-translation' ),
104
  'description' => __( 'Update translations status of this item in Lingotek TMS', 'lingotek-translation' ),
 
105
  ),
106
 
107
  'download' => array(
108
  'action' => __( 'Download translations', 'lingotek-translation' ),
109
  'progress' => __( 'Downloading translations...', 'lingotek-translation' ),
110
  'description' => __( 'Download translations of this item from Lingotek TMS', 'lingotek-translation' ),
 
111
  ),
112
 
113
  'cancel' => array(
114
  'action' => __( 'Cancel translations', 'lingotek-translation' ),
115
  'progress' => __( 'Cancelling translations...', 'lingotek-translation' ),
116
  'description' => __( 'Cancel the translations of this item from Lingotek TMS', 'lingotek-translation' ),
 
117
  ),
118
 
119
  'delete' => array(
120
  'action' => __( 'Delete translations', 'lingotek-translation' ),
121
  'progress' => __( 'Deleting translations...', 'lingotek-translation' ),
122
  'description' => __( 'Delete the translations of this item from Lingotek TMS', 'lingotek-translation' ),
 
123
  ),
124
 
125
  'cancel-translation' => array(
126
  'action' => __( 'Cancel translation', 'lingotek-translation' ),
127
  'progress' => __( 'Cancelling translation', 'lingotek-translation' ),
128
  'description' => __( 'Cancel this translation from Lingotek TMS', 'lingotek-translation' ),
 
129
  ),
130
 
131
  'delete-translation' => array(
132
  'action' => __( 'Delete translation', 'lingotek-translation' ),
133
  'progress' => __( 'Deleting translation', 'lingotek-translation' ),
134
  'description' => __( 'Delete this translation from Lingotek TMS', 'lingotek-translation' ),
 
135
  ),
136
  );
137
 
@@ -202,6 +210,16 @@ abstract class Lingotek_Actions {
202
  'title' => __( 'Translation has been cancelled', 'lingotek-translation' ),
203
  'icon' => 'warning',
204
  ),
 
 
 
 
 
 
 
 
 
 
205
  );
206
 
207
  $this->type = $type;
@@ -215,11 +233,11 @@ abstract class Lingotek_Actions {
215
  add_action( 'wp_ajax_get_user_payment_information', array( &$this, 'ajax_get_user_payment_information' ) );
216
  add_action( 'wp_ajax_get_ltk_terms_and_conditions', array( &$this, 'ajax_get_ltk_terms_and_conditions' ) );
217
 
218
- foreach ( array_keys( self::$actions ) as $action ) {
219
- if ( strpos( $action, '-translation' ) ) {
220
  continue;
221
  }
222
- add_action( 'wp_ajax_lingotek_progress_' . $this->type . '_' . $action, array( &$this, 'ajax_' . $action ) );
223
  }
224
  }
225
 
@@ -238,6 +256,31 @@ abstract class Lingotek_Actions {
238
  return Lingotek_API::PRODUCTION_URL . "/workbench/document/$document_id/locale/$locale";
239
  }
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  /**
242
  * Outputs an action icon
243
  *
@@ -296,16 +339,8 @@ abstract class Lingotek_Actions {
296
  * @param bool $confirm
297
  */
298
  public function upload_icon( $object_id, $confirm = false ) {
299
- $args = array(
300
- $this->type => $object_id,
301
- 'action' => 'lingotek-upload',
302
- 'noheader' => true,
303
- );
304
- if ( isset( $args['string'] ) ) {
305
- $args['string'] = rawurlencode( $args['string'] );
306
- }
307
- $link = wp_nonce_url( defined( 'DOING_AJAX' ) && DOING_AJAX ? add_query_arg( $args, wp_get_referer() ) : add_query_arg( $args ), 'lingotek-upload' );
308
- self::link_to_settings_if_not_connected( $link );
309
  return self::display_icon( 'upload', $link, $confirm ? self::$confirm_message : '' );
310
  }
311
 
@@ -384,17 +419,7 @@ abstract class Lingotek_Actions {
384
  public static function translation_icon( $document, $language ) {
385
  if ( isset( $document->translations[ $language->locale ] ) ) {
386
  if ( 'ready' === $document->translations[ $language->locale ] ) {
387
- $link = wp_nonce_url(
388
- add_query_arg(
389
- array(
390
- 'document_id' => $document->document_id,
391
- 'locale' => $language->locale,
392
- 'action' => 'lingotek-download',
393
- 'noheader' => true,
394
- )
395
- ),
396
- 'lingotek-download'
397
- );
398
  self::link_to_settings_if_not_connected( $link );
399
  return self::display_icon( $document->translations[ $language->locale ], $link );
400
  } elseif ( 'not-current' === $document->translations[ $language->locale ] ) {
@@ -403,23 +428,17 @@ abstract class Lingotek_Actions {
403
  } elseif ( 'current' !== $document->translations[ $language->locale ] && $custom_icon = $document->get_custom_in_progress_icon( $language ) ) {
404
  return $custom_icon;
405
  } else {
406
- $link = self::workbench_link( $document->document_id, $language->lingotek_locale );
 
 
 
 
 
407
  self::link_to_settings_if_not_connected( $link );
408
- return self::display_icon( $document->translations[ $language->locale ], $link, ' target="_blank"' );
409
  }//end if
410
  } else {
411
- $link = wp_nonce_url(
412
- add_query_arg(
413
- array(
414
- 'document_id' => $document->document_id,
415
- 'locale' => $language->locale,
416
- 'action' => 'lingotek-request',
417
- 'noheader' => true,
418
- ),
419
- defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
420
- ),
421
- 'lingotek-request'
422
- );
423
  self::link_to_settings_if_not_connected( $link );
424
  return self::display_icon( 'request', $link );
425
  }//end if
@@ -431,17 +450,7 @@ abstract class Lingotek_Actions {
431
  * @since 1.4.3
432
  */
433
  public function failed_import_icon( $name, $object_id ) {
434
- $args = array(
435
- $this->type => $object_id,
436
- 'action' => 'lingotek-upload',
437
- 'noheader' => true,
438
- );
439
- if ( isset( $args['string'] ) ) {
440
- $args['string'] = rawurlencode( $args['string'] );
441
- }
442
- $link = wp_nonce_url( defined( 'DOING_AJAX' ) && DOING_AJAX ? add_query_arg( $args, wp_get_referer() ) : add_query_arg( $args ), 'lingotek-upload' );
443
- self::link_to_settings_if_not_connected( $link );
444
-
445
  return sprintf(
446
  '<a class="%s dashicons dashicons-%s" title="%s" href="%s"></a>',
447
  self::$icons[ $name ]['class'],
@@ -652,7 +661,7 @@ abstract class Lingotek_Actions {
652
  $actions['lingotek-cancel-translation'] = $this->get_action_link(
653
  array(
654
  'document_id' => $document->document_id,
655
- 'target_id' => $target_id,
656
  'target_locale' => $target_locale,
657
  'action' => 'cancel-translation',
658
  ),
@@ -680,14 +689,19 @@ abstract class Lingotek_Actions {
680
  * @since 0.2
681
  */
682
  protected function _add_bulk_actions( $bulk_actions ) {
683
- foreach ( self::$actions as $action => $strings ) {
684
- if ( strpos( $action, '-translation' ) ) {
685
  continue;
686
  }
687
  // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
688
- $bulk_actions[ 'bulk-lingotek-' . $action ] = __( $strings['action'], $action );
689
  if ( null !== filter_input( INPUT_GET, 'bulk-lingotek-' . $action ) ) {
690
- $text = $strings['progress'];
 
 
 
 
 
691
  }
692
  }
693
  if ( ! empty( $text ) ) {
@@ -718,13 +732,16 @@ abstract class Lingotek_Actions {
718
  'warning' => null === filter_input( INPUT_GET, 'lingotek_warning' ) ? ( null === filter_input( INPUT_GET, 'lingotek_remove' ) ? '' : __( "You are about to $action_message existing translations from your Lingotek community. Are you sure?", 'lingotek-translation' ) ) : __( 'You are about to overwrite existing translations. Are you sure?', 'lingotek-translation' ),
719
  'nonce' => wp_create_nonce( 'lingotek_progress' ),
720
  );
 
 
 
721
  if ( null !== filter_input( INPUT_GET, 'locales' ) ) {
722
  $data['locales'] = explode( ',', filter_input( INPUT_GET, 'locales' ) );
723
  }
724
  wp_localize_script( 'lingotek_progress', 'lingotek_data', $data );
725
  return;
726
- }
727
- }
728
  }
729
 
730
  /**
@@ -817,9 +834,8 @@ abstract class Lingotek_Actions {
817
  case 'lingotek-cancel-translation':
818
  check_admin_referer( 'lingotek-cancel-translation' );
819
  $target_locale = filter_input( INPUT_GET, 'target_locale' );
820
- $target_id = filter_input( INPUT_GET, 'target_id' );
821
- $language = PLL()->model->post->get_language( $target_id );
822
- $document->cancel_translation( $language, $target_id );
823
  if ( null !== filter_input( INPUT_GET, 'lingotek_redirect' ) && filter_input( INPUT_GET, 'lingotek_redirect' ) === true ) {
824
  $site_id = get_current_blog_id();
825
  wp_safe_redirect( get_site_url( $site_id, '/wp-admin/edit.php?post_type=page' ) );
@@ -917,11 +933,27 @@ abstract class Lingotek_Actions {
917
  $document->cancel();
918
  // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
919
  } elseif ( $document && $document->source != $id && $language ) {
920
- $document->cancel_translation( $language, $id );
921
  }
922
  die();
923
  }
924
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
925
  /**
926
  * Ajax call to get the price estimation of a given document.
927
  */
@@ -1001,4 +1033,184 @@ abstract class Lingotek_Actions {
1001
 
1002
  return $api_error;
1003
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  }
90
  'action' => __( 'Upload to Lingotek', 'lingotek-translation' ),
91
  'progress' => __( 'Uploading...', 'lingotek-translation' ),
92
  'description' => __( 'Upload this item to Lingotek TMS', 'lingotek-translation' ),
93
+ 'per_locale' => false,
94
  ),
95
 
96
  'request' => array(
97
  'action' => __( 'Request translations', 'lingotek-translation' ),
98
  'progress' => __( 'Requesting translations...', 'lingotek-translation' ),
99
  'description' => __( 'Request translations of this item to Lingotek TMS', 'lingotek-translation' ),
100
+ 'per_locale' => false,
101
  ),
102
 
103
  'status' => array(
104
  'action' => __( 'Update translations status', 'lingotek-translation' ),
105
  'progress' => __( 'Updating translations status...', 'lingotek-translation' ),
106
  'description' => __( 'Update translations status of this item in Lingotek TMS', 'lingotek-translation' ),
107
+ 'per_locale' => false,
108
  ),
109
 
110
  'download' => array(
111
  'action' => __( 'Download translations', 'lingotek-translation' ),
112
  'progress' => __( 'Downloading translations...', 'lingotek-translation' ),
113
  'description' => __( 'Download translations of this item from Lingotek TMS', 'lingotek-translation' ),
114
+ 'per_locale' => false,
115
  ),
116
 
117
  'cancel' => array(
118
  'action' => __( 'Cancel translations', 'lingotek-translation' ),
119
  'progress' => __( 'Cancelling translations...', 'lingotek-translation' ),
120
  'description' => __( 'Cancel the translations of this item from Lingotek TMS', 'lingotek-translation' ),
121
+ 'per_locale' => false,
122
  ),
123
 
124
  'delete' => array(
125
  'action' => __( 'Delete translations', 'lingotek-translation' ),
126
  'progress' => __( 'Deleting translations...', 'lingotek-translation' ),
127
  'description' => __( 'Delete the translations of this item from Lingotek TMS', 'lingotek-translation' ),
128
+ 'per_locale' => false,
129
  ),
130
 
131
  'cancel-translation' => array(
132
  'action' => __( 'Cancel translation', 'lingotek-translation' ),
133
  'progress' => __( 'Cancelling translation', 'lingotek-translation' ),
134
  'description' => __( 'Cancel this translation from Lingotek TMS', 'lingotek-translation' ),
135
+ 'per_locale' => true,
136
  ),
137
 
138
  'delete-translation' => array(
139
  'action' => __( 'Delete translation', 'lingotek-translation' ),
140
  'progress' => __( 'Deleting translation', 'lingotek-translation' ),
141
  'description' => __( 'Delete this translation from Lingotek TMS', 'lingotek-translation' ),
142
+ 'per_locale' => false,
143
  ),
144
  );
145
 
210
  'title' => __( 'Translation has been cancelled', 'lingotek-translation' ),
211
  'icon' => 'warning',
212
  ),
213
+
214
+ 'deleted' => array(
215
+ 'title' => __( 'Translation has been deleted', 'lingotek-translation' ),
216
+ 'icon' => 'remove',
217
+ ),
218
+
219
+ 'archived' => array(
220
+ 'title' => __( 'Document has been archived', 'lingotek-translation' ),
221
+ 'icon' => 'remove',
222
+ ),
223
  );
224
 
225
  $this->type = $type;
233
  add_action( 'wp_ajax_get_user_payment_information', array( &$this, 'ajax_get_user_payment_information' ) );
234
  add_action( 'wp_ajax_get_ltk_terms_and_conditions', array( &$this, 'ajax_get_ltk_terms_and_conditions' ) );
235
 
236
+ foreach ( self::$actions as $action => $action_data ) {
237
+ if ( strpos( $action, '-translation' ) && ! $action_data['per_locale'] ) {
238
  continue;
239
  }
240
+ add_action( 'wp_ajax_lingotek_progress_' . $this->type . '_' . $action, array( &$this, 'ajax_' . str_replace( '-', '_', $action ) ) );
241
  }
242
  }
243
 
256
  return Lingotek_API::PRODUCTION_URL . "/workbench/document/$document_id/locale/$locale";
257
  }
258
 
259
+ /**
260
+ * Generates a request target link
261
+ *
262
+ * @since 1.4.14
263
+ *
264
+ * @param string $document_id
265
+ * @param string $wp_locale WordPress locale.
266
+ * @return string Workbench link.
267
+ */
268
+ public static function request_target_link( $document_id, $wp_locale ) {
269
+ $link = wp_nonce_url(
270
+ add_query_arg(
271
+ array(
272
+ 'document_id' => $document_id,
273
+ 'locale' => $wp_locale,
274
+ 'action' => 'lingotek-request',
275
+ 'noheader' => true,
276
+ ),
277
+ defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
278
+ ),
279
+ 'lingotek-request'
280
+ );
281
+ return $link;
282
+ }
283
+
284
  /**
285
  * Outputs an action icon
286
  *
339
  * @param bool $confirm
340
  */
341
  public function upload_icon( $object_id, $confirm = false ) {
342
+ $link = $this->upload_url( $object_id );
343
+
 
 
 
 
 
 
 
 
344
  return self::display_icon( 'upload', $link, $confirm ? self::$confirm_message : '' );
345
  }
346
 
419
  public static function translation_icon( $document, $language ) {
420
  if ( isset( $document->translations[ $language->locale ] ) ) {
421
  if ( 'ready' === $document->translations[ $language->locale ] ) {
422
+ $link = self::request_target_link( $document->document_id, $language->locale );
 
 
 
 
 
 
 
 
 
 
423
  self::link_to_settings_if_not_connected( $link );
424
  return self::display_icon( $document->translations[ $language->locale ], $link );
425
  } elseif ( 'not-current' === $document->translations[ $language->locale ] ) {
428
  } elseif ( 'current' !== $document->translations[ $language->locale ] && $custom_icon = $document->get_custom_in_progress_icon( $language ) ) {
429
  return $custom_icon;
430
  } else {
431
+ $link = self::workbench_link( $document->document_id, $language->lingotek_locale );
432
+ $target_blank = ' target="_blank"';
433
+ if ( 'deleted' === $document->translations[ $language->locale ] ) {
434
+ $link = self::request_target_link( $document->document_id, $language->locale );
435
+ $target_blank = '';
436
+ }
437
  self::link_to_settings_if_not_connected( $link );
438
+ return self::display_icon( $document->translations[ $language->locale ], $link, $target_blank );
439
  }//end if
440
  } else {
441
+ $link = self::request_target_link( $document->document_id, $language->locale );
 
 
 
 
 
 
 
 
 
 
 
442
  self::link_to_settings_if_not_connected( $link );
443
  return self::display_icon( 'request', $link );
444
  }//end if
450
  * @since 1.4.3
451
  */
452
  public function failed_import_icon( $name, $object_id ) {
453
+ $link = $this->upload_url( $object_id );
 
 
 
 
 
 
 
 
 
 
454
  return sprintf(
455
  '<a class="%s dashicons dashicons-%s" title="%s" href="%s"></a>',
456
  self::$icons[ $name ]['class'],
661
  $actions['lingotek-cancel-translation'] = $this->get_action_link(
662
  array(
663
  'document_id' => $document->document_id,
664
+ 'target_id' => $id,
665
  'target_locale' => $target_locale,
666
  'action' => 'cancel-translation',
667
  ),
689
  * @since 0.2
690
  */
691
  protected function _add_bulk_actions( $bulk_actions ) {
692
+ foreach ( self::$actions as $action => $action_data ) {
693
+ if ( strpos( $action, '-translation' ) && ! $action_data['per_locale'] ) {
694
  continue;
695
  }
696
  // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
697
+ $bulk_actions[ 'bulk-lingotek-' . $action ] = __( $action_data['action'], $action );
698
  if ( null !== filter_input( INPUT_GET, 'bulk-lingotek-' . $action ) ) {
699
+ $text = $action_data['progress'];
700
+ }
701
+ if ( isset( $action_data['per_locale'] ) && true === $action_data['per_locale'] ) {
702
+ foreach ( PLL()->model->get_languages_list() as $language ) {
703
+ $bulk_actions[ 'bulk-lingotek-' . $action . ':' . $language->locale ] = __( $action_data['action'] . ' for ' . $language->lingotek_locale, 'lingotek-translation' );
704
+ }
705
  }
706
  }
707
  if ( ! empty( $text ) ) {
732
  'warning' => null === filter_input( INPUT_GET, 'lingotek_warning' ) ? ( null === filter_input( INPUT_GET, 'lingotek_remove' ) ? '' : __( "You are about to $action_message existing translations from your Lingotek community. Are you sure?", 'lingotek-translation' ) ) : __( 'You are about to overwrite existing translations. Are you sure?', 'lingotek-translation' ),
733
  'nonce' => wp_create_nonce( 'lingotek_progress' ),
734
  );
735
+ if ( null !== filter_input( INPUT_GET, 'target_locale' ) ) {
736
+ $data['target_locale'] = filter_input( INPUT_GET, 'target_locale' );
737
+ }
738
  if ( null !== filter_input( INPUT_GET, 'locales' ) ) {
739
  $data['locales'] = explode( ',', filter_input( INPUT_GET, 'locales' ) );
740
  }
741
  wp_localize_script( 'lingotek_progress', 'lingotek_data', $data );
742
  return;
743
+ }//end if
744
+ }//end foreach
745
  }
746
 
747
  /**
834
  case 'lingotek-cancel-translation':
835
  check_admin_referer( 'lingotek-cancel-translation' );
836
  $target_locale = filter_input( INPUT_GET, 'target_locale' );
837
+ $document_id = filter_input( INPUT_GET, 'document_id' );
838
+ $document->cancel_translation( $document_id, $target_locale );
 
839
  if ( null !== filter_input( INPUT_GET, 'lingotek_redirect' ) && filter_input( INPUT_GET, 'lingotek_redirect' ) === true ) {
840
  $site_id = get_current_blog_id();
841
  wp_safe_redirect( get_site_url( $site_id, '/wp-admin/edit.php?post_type=page' ) );
933
  $document->cancel();
934
  // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
935
  } elseif ( $document && $document->source != $id && $language ) {
936
+ $document->cancel_translation( $document->document_id, $language->lingotek_locale );
937
  }
938
  die();
939
  }
940
 
941
+ /**
942
+ * Ajax response cancel only one target and showing progress
943
+ *
944
+ * @since 1.5.0
945
+ */
946
+ public function ajax_cancel_translation() {
947
+ check_ajax_referer( 'lingotek_progress', '_lingotek_nonce' );
948
+ $id = filter_input( INPUT_POST, 'id' );
949
+ $target_locale = filter_input( INPUT_POST, 'target_locale' );
950
+ $document = $this->lgtm->get_group( $this->type, $id );
951
+ // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
952
+ if ( $document && $target_locale ) {
953
+ $document->cancel_translation( $document->document_id, $target_locale );
954
+ }
955
+ die();
956
+ }
957
  /**
958
  * Ajax call to get the price estimation of a given document.
959
  */
1033
 
1034
  return $api_error;
1035
  }
1036
+
1037
+ /**
1038
+ * Gets the upload url.
1039
+ *
1040
+ * @param int $object_id
1041
+ * @param string|null $locale
1042
+ *
1043
+ * @return string
1044
+ *
1045
+ * @since 1.5.0
1046
+ */
1047
+ public function upload_url( $object_id, $locale = null ) {
1048
+ $args = array(
1049
+ $this->type => $object_id,
1050
+ 'action' => 'lingotek-upload',
1051
+ 'noheader' => true,
1052
+ );
1053
+ if ( null !== $locale ) {
1054
+ $args['locale'] = $locale;
1055
+ }
1056
+ if ( isset( $args['string'] ) ) {
1057
+ $args['string'] = rawurlencode( $args['string'] );
1058
+ }
1059
+ $link = wp_nonce_url( defined( 'DOING_AJAX' ) && DOING_AJAX ? add_query_arg( $args, wp_get_referer() ) : add_query_arg( $args ), 'lingotek-upload' );
1060
+ self::link_to_settings_if_not_connected( $link );
1061
+
1062
+ return $link;
1063
+ }
1064
+
1065
+ /**
1066
+ * Gets the request translation url.
1067
+ *
1068
+ * @param int $object_id
1069
+ * @param object $document
1070
+ * @param string $ltk_locale
1071
+ *
1072
+ * @return string
1073
+ *
1074
+ * @since 1.5.0
1075
+ */
1076
+ public function request_translation_url( $object_id, $document, $ltk_locale ) {
1077
+ $link = wp_nonce_url(
1078
+ add_query_arg(
1079
+ array(
1080
+ 'document_id' => $document->document_id,
1081
+ 'locale' => $ltk_locale,
1082
+ 'action' => 'lingotek-request',
1083
+ 'noheader' => true,
1084
+ ),
1085
+ defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
1086
+ ),
1087
+ 'lingotek-request'
1088
+ );
1089
+ return $link;
1090
+ }
1091
+
1092
+ /**
1093
+ * Gets the check source status url.
1094
+ *
1095
+ * @param int $object_id
1096
+ * @param object $document
1097
+ * @param string $ltk_locale
1098
+ *
1099
+ * @return string
1100
+ *
1101
+ * @since 1.5.0
1102
+ */
1103
+ public function check_source_url( $object_id, $document, $ltk_locale ) {
1104
+ $link = wp_nonce_url(
1105
+ add_query_arg(
1106
+ array(
1107
+ 'document_id' => $document->document_id,
1108
+ 'action' => 'lingotek-status',
1109
+ 'noheader' => true,
1110
+ ),
1111
+ defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
1112
+ ),
1113
+ 'lingotek-status'
1114
+ );
1115
+ return $link;
1116
+ }
1117
+
1118
+ /**
1119
+ * Gets the check translation status url.
1120
+ *
1121
+ * @param int $object_id
1122
+ * @param object $document
1123
+ * @param string $ltk_locale
1124
+ *
1125
+ * @return string
1126
+ *
1127
+ * @since 1.5.0
1128
+ */
1129
+ public function check_translation_url( $object_id, $document, $ltk_locale ) {
1130
+ $link = wp_nonce_url(
1131
+ add_query_arg(
1132
+ array(
1133
+ 'document_id' => $document->document_id,
1134
+ 'action' => 'lingotek-status',
1135
+ 'noheader' => true,
1136
+ ),
1137
+ defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
1138
+ ),
1139
+ 'lingotek-status'
1140
+ );
1141
+ return $link;
1142
+ }
1143
+
1144
+ /**
1145
+ * Gets the cancel translation target url.
1146
+ *
1147
+ * @param int $object_id
1148
+ * @param object $document
1149
+ * @param string $ltk_locale
1150
+ *
1151
+ * @return string
1152
+ *
1153
+ * @since 1.5.0
1154
+ */
1155
+ public function cancel_translation_url( $object_id, $document, $ltk_locale ) {
1156
+ $link = wp_nonce_url(
1157
+ add_query_arg(
1158
+ array(
1159
+ 'document_id' => $document->document_id,
1160
+ 'id' => $object_id,
1161
+ 'target_locale' => $ltk_locale,
1162
+ 'action' => 'lingotek-cancel-translation',
1163
+ 'noheader' => true,
1164
+ ),
1165
+ defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
1166
+ ),
1167
+ 'lingotek-cancel-translation'
1168
+ );
1169
+
1170
+ return $link;
1171
+ }
1172
+
1173
+ /**
1174
+ * Gets the download translation url.
1175
+ *
1176
+ * @param int $object_id
1177
+ * @param object $document
1178
+ * @param string $ltk_locale
1179
+ *
1180
+ * @return string
1181
+ *
1182
+ * @since 1.5.0
1183
+ */
1184
+ public function download_translation_url( $object_id, $document, $ltk_locale ) {
1185
+ $link = wp_nonce_url(
1186
+ add_query_arg(
1187
+ array(
1188
+ 'document_id' => $document->document_id,
1189
+ 'locale' => $ltk_locale,
1190
+ 'action' => 'lingotek-download',
1191
+ 'noheader' => true,
1192
+ ),
1193
+ defined( 'DOING_AJAX' ) && DOING_AJAX ? wp_get_referer() : wp_get_referer()
1194
+ ),
1195
+ 'lingotek-download'
1196
+ );
1197
+ return $link;
1198
+ }
1199
+
1200
+ /**
1201
+ * Gets the workbench access url.
1202
+ *
1203
+ * @param int $object_id
1204
+ * @param object $document
1205
+ * @param string $ltk_locale
1206
+ *
1207
+ * @return string
1208
+ *
1209
+ * @since 1.5.0
1210
+ */
1211
+ public function workbench_url( $object_id, $document, $ltk_locale ) {
1212
+ $link = self::workbench_link( $document->document_id, $ltk_locale );
1213
+ self::link_to_settings_if_not_connected( $link );
1214
+ return $link;
1215
+ }
1216
  }
admin/admin.php CHANGED
@@ -91,10 +91,10 @@ class Lingotek_Admin {
91
  if ( $document->source !== (int) $object_id ) {
92
  $document = $lgtm->get_group( $type, $document->source );
93
  }
94
- $source_id = null !== $document->source ? $document->source : $object_id;
95
- $source_language = $terms ? pll_get_term_language( $document->source, 'locale' )
96
  : pll_get_post_language( $document->source, 'locale' );
97
- $existing_translations = ( 'term' === $type ) ? PLL()->model->term->get_translations( $source_id ) : PLL()->model->post->get_translations( $source_id );
98
 
99
  if ( count( $existing_translations ) > 1 ) {
100
  $content_metadata[ $id ]['existing_trans'] = true;
@@ -107,9 +107,12 @@ class Lingotek_Admin {
107
  $content_metadata[ $id ][ $source_language ]['status'] = $document->source === $object_id ? $document->status : $target_status;
108
  if ( is_array( $document->translations ) ) {
109
  foreach ( $document->translations as $locale => $translation_status ) {
110
- $content_metadata[ $id ][ $locale ]['status'] = $translation_status;
111
- $workbench_link = Lingotek_Actions::workbench_link( $document->document_id, $locale );
112
- $content_metadata[ $id ][ $locale ]['workbench_link'] = $workbench_link;
 
 
 
113
  }
114
  }
115
 
91
  if ( $document->source !== (int) $object_id ) {
92
  $document = $lgtm->get_group( $type, $document->source );
93
  }
94
+ $source_id = null !== $document->source ? $document->source : $object_id;
95
+ $source_language = $terms ? pll_get_term_language( $document->source, 'locale' )
96
  : pll_get_post_language( $document->source, 'locale' );
97
+ $existing_translations = ( 'term' === $type ) ? PLL()->model->term->get_translations( $source_id ) : PLL()->model->post->get_translations( $source_id );
98
 
99
  if ( count( $existing_translations ) > 1 ) {
100
  $content_metadata[ $id ]['existing_trans'] = true;
107
  $content_metadata[ $id ][ $source_language ]['status'] = $document->source === $object_id ? $document->status : $target_status;
108
  if ( is_array( $document->translations ) ) {
109
  foreach ( $document->translations as $locale => $translation_status ) {
110
+ $content_metadata[ $id ][ $locale ]['status'] = $translation_status;
111
+ // If the target was marked as deleted, there's no workbench link.
112
+ if ( 'deleted' !== $translation_status ) {
113
+ $workbench_link = Lingotek_Actions::workbench_link( $document->document_id, $locale );
114
+ $content_metadata[ $id ][ $locale ]['workbench_link'] = $workbench_link;
115
+ }
116
  }
117
  }
118
 
admin/chip-base.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Chip base class for representing a locale (source or target).
5
+ *
6
+ * @since 1.5.0
7
+ */
8
+ abstract class Lingotek_Chip_Base {
9
+
10
+ /**
11
+ * Id of the document.
12
+ *
13
+ * @var int
14
+ */
15
+ protected $id;
16
+
17
+ /**
18
+ * The document.
19
+ *
20
+ * @var object
21
+ */
22
+ protected $document;
23
+
24
+ /**
25
+ * Construct the chip.
26
+ *
27
+ * @param int $id Id of the document.
28
+ * @param object $document The document.
29
+ */
30
+ public function __construct( $id, $document ) {
31
+ $this->id = $id;
32
+ $this->document = $document;
33
+ }
34
+
35
+ /**
36
+ * Get the url for an action
37
+ *
38
+ * @param object $language
39
+ * @param string $status
40
+ *
41
+ * @return Lingotek_Action_Url|null
42
+ *
43
+ * @since 1.5.0
44
+ */
45
+ abstract public function get_action_url( $language, $status );
46
+
47
+ /**
48
+ * Get the secondary urls for an action menu.
49
+ *
50
+ * @param object $language
51
+ * @param string $status
52
+ *
53
+ * @return Lingotek_Action_Url[]
54
+ *
55
+ * @since 1.5.0
56
+ */
57
+ abstract public function get_secondary_action_urls( $language, $status );
58
+
59
+ /**
60
+ * Renders a chip with their primary action and a secondary actions menu.
61
+ *
62
+ * @param object $language
63
+ * @param string $status
64
+ *
65
+ * @return string
66
+ *
67
+ * @since 1.5.0
68
+ */
69
+ public function render( $language, $status ) {
70
+ $primary_action_url = $this->get_action_url( $language, $status );
71
+ $secondary_action_urls = $this->get_secondary_action_urls( $language, $status );
72
+
73
+ $output_html = sprintf(
74
+ '<div class="lingotek-%s-dropdown">' .
75
+ '<a href="%s" class="language-icon %s-%s" title="%s">%s</a>',
76
+ $this->type,
77
+ null !== $primary_action_url ? $primary_action_url->getUri() : '#',
78
+ $this->type,
79
+ $status,
80
+ null !== $primary_action_url ? $primary_action_url->getTitle() : '',
81
+ $language->lingotek_locale
82
+ );
83
+
84
+ $secondary_actions_code = '';
85
+ foreach ( $secondary_action_urls as $source_secondary_action ) {
86
+ $secondary_actions_code .= $source_secondary_action->render();
87
+ }
88
+
89
+ if ( count( $secondary_action_urls ) > 0 ) {
90
+ $output_html .= sprintf(
91
+ '
92
+ <button class="language-icon lingotek-%s-dropdown-toggle %s-%s"><span class="visually-hidden">Toggle Actions</span></button>
93
+ <ul class="lingotek-%s-actions">
94
+ %s
95
+ </ul>',
96
+ $this->type,
97
+ $this->type,
98
+ $status,
99
+ $this->type,
100
+ $secondary_actions_code
101
+ );
102
+ }
103
+
104
+ $output_html .= '</div>';
105
+
106
+ return $output_html;
107
+ }
108
+
109
+ public function get_canonical_url( $language ) {
110
+ if ( is_object( $this->document ) ) {
111
+ if ( post_type_exists( $this->document->type ) ) {
112
+ $id = PLL()->model->post->get( $this->document->source, $language->locale );
113
+ if ( $id ) {
114
+ return get_permalink( $id );
115
+ }
116
+ } elseif ( taxonomy_exists( $this->document->type ) ) {
117
+ $id = $this->document->pllm->get_term( $this->document->source, $language->locale );
118
+ if ( $id ) {
119
+ return get_term_link( $id, $this->document->type );
120
+ }
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+
126
+ }
admin/chip-source.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Chip class for representing a source locale and its actions.
5
+ *
6
+ * @since 1.5.0
7
+ */
8
+ class Lingotek_Chip_Source extends Lingotek_Chip_Base {
9
+
10
+ /**
11
+ * The type of the chip.
12
+ *
13
+ * @var string
14
+ */
15
+ protected $type = 'source';
16
+
17
+ /**
18
+ * {@inheritdoc}
19
+ */
20
+ public function get_action_url( $language, $status ) {
21
+ $url = null;
22
+ $uri = null;
23
+ $title = null;
24
+ $post_actions = $GLOBALS['wp_lingotek']->post_actions;
25
+ $document_id = $this->document ? $this->document->document_id : false;
26
+ if ( $document_id ) {
27
+ if ( in_array( $status, array( 'request', 'deleted', 'untracked' ), true ) ) {
28
+ $uri = $post_actions->upload_url( $this->id );
29
+ $title = __( 'Upload', 'lingotek-translation' );
30
+ }
31
+ if ( 'deleted' === $status ) {
32
+ $uri = $post_actions->upload_url( $this->id );
33
+ $title = sprintf( __( 'This document has been deleted. Reupload the source for translation.', 'lingotek-translation' ) );
34
+ }
35
+ if ( 'archived' === $status ) {
36
+ $uri = $post_actions->upload_url( $this->id );
37
+ $title = sprintf( __( 'This document has been archived. Reupload the source for translation.', 'lingotek-translation' ) );
38
+ }
39
+ if ( 'importing' === $status ) {
40
+ $uri = $post_actions->check_source_url( $this->id, $this->document, $language->locale );
41
+ $title = __( 'Check source status', 'lingotek-translation' );
42
+ }
43
+ if ( in_array( $status, array( 'current', 'edited', 'failed', 'error' ), true ) ) {
44
+ $uri = $post_actions->upload_url( $this->id );
45
+ $title = __( 'Re-Upload', 'lingotek-translation' );
46
+ }
47
+ if ( 'disabled' === $status ) {
48
+ $url = null;
49
+ }
50
+ } else {
51
+ if ( 'deleted' === $status ) {
52
+ $uri = $post_actions->upload_url( $this->id );
53
+ $title = sprintf( __( 'This document has been deleted. Reupload the source for translation.', 'lingotek-translation' ) );
54
+ } elseif ( 'archived' === $status ) {
55
+ $uri = $post_actions->upload_url( $this->id );
56
+ $title = sprintf( __( 'This document has been archived. Reupload the source for translation.', 'lingotek-translation' ) );
57
+ } else {
58
+ $uri = $post_actions->upload_url( $this->id );
59
+ $title = __( 'Upload', 'lingotek-translation' );
60
+ }
61
+ }//end if
62
+ if ( null !== $uri && null !== $title ) {
63
+ $url = new Lingotek_Action_Url( $uri, $title );
64
+ }
65
+ return $url;
66
+ }
67
+
68
+ /**
69
+ * {@inheritdoc}
70
+ */
71
+ public function get_secondary_action_urls( $language, $status ) {
72
+ $urls = array();
73
+ $post_actions = $GLOBALS['wp_lingotek']->post_actions;
74
+ $document_id = $this->document ? $this->document->document_id : false;
75
+
76
+ $canonical_url = $this->get_canonical_url( $language );
77
+ if ( $canonical_url ) {
78
+ $urls[] = new Lingotek_Action_Url(
79
+ $canonical_url,
80
+ 'source' === $this->type ? __( 'View', 'lingotek-translation' ) : __( 'View translation', 'lingotek-translation' )
81
+ );
82
+ }
83
+
84
+ if ( $document_id ) {
85
+ if ( in_array( $status, array( 'request', 'deleted', 'untracked', 'archived', 'cancelled' ), true ) ) {
86
+ $urls[] = new Lingotek_Action_Url(
87
+ $post_actions->upload_url( $this->id ),
88
+ __( 'Upload', 'lingotek-translation' )
89
+ );
90
+ }
91
+ if ( 'importing' === $status ) {
92
+ $urls[] = new Lingotek_Action_Url(
93
+ $post_actions->check_source_url( $this->id, $this->document, $language->locale ),
94
+ __( 'Check source status', 'lingotek-translation' )
95
+ );
96
+ }
97
+ if ( in_array( $status, array( 'current', 'edited', 'failed', 'error' ), true ) ) {
98
+ $urls[] = new Lingotek_Action_Url(
99
+ $post_actions->upload_url( $this->id ),
100
+ __( 'Re-Upload', 'lingotek-translation' )
101
+ );
102
+ }
103
+ if ( 'disabled' === $status ) {
104
+ $url = null;
105
+ }
106
+ } else {
107
+ $urls[] = new Lingotek_Action_Url(
108
+ $post_actions->upload_url( $this->id ),
109
+ __( 'Upload', 'lingotek-translation' )
110
+ );
111
+ }//end if
112
+ return $urls;
113
+ }
114
+
115
+ }
admin/chip-target.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Chip class for representing a target locale and its actions.
5
+ *
6
+ * @since 1.5.0
7
+ */
8
+ class Lingotek_Chip_Target extends Lingotek_Chip_Base {
9
+
10
+ /**
11
+ * The type of the chip.
12
+ *
13
+ * @var string
14
+ */
15
+ protected $type = 'target';
16
+
17
+ /**
18
+ * {@inheritdoc}
19
+ */
20
+ public function get_action_url( $language, $status ) {
21
+ $url = null;
22
+ $uri = null;
23
+ $title = null;
24
+ $post_actions = $GLOBALS['wp_lingotek']->post_actions;
25
+ $document_id = $this->document->document_id;
26
+ if ( $document_id ) {
27
+ if ( in_array( $status, array( 'request', 'untracked' ), true ) ) {
28
+ $uri = $post_actions->request_translation_url( $this->id, $this->document, $language->locale );
29
+ /* translators: %s: The lingotek locale. */
30
+ $title = sprintf( __( 'Request translation to %s', 'lingotek-translation' ), $language->lingotek_locale );
31
+ }
32
+ if ( 'deleted' === $status ) {
33
+ $uri = $post_actions->request_translation_url( $this->id, $this->document, $language->locale );
34
+ $canonical_url = $this->get_canonical_url( $language );
35
+ if ( $canonical_url ) {
36
+ $title = sprintf( __( 'This target has been deleted and the translation does exist.', 'lingotek-translation' ) );
37
+ } else {
38
+ $title = sprintf( __( 'This target has been deleted and the translation does not exist.', 'lingotek-translation' ) );
39
+ }
40
+ }
41
+ if ( 'archived' === $status ) {
42
+ $uri = $post_actions->request_translation_url( $this->id, $this->document, $language->locale );
43
+ $canonical_url = $this->get_canonical_url( $language );
44
+ if ( $canonical_url ) {
45
+ $title = sprintf( __( 'This target has been archived and the translation does exist.', 'lingotek-translation' ) );
46
+ } else {
47
+ $title = sprintf( __( 'This target has been archived and the translation does not exist.', 'lingotek-translation' ) );
48
+ }
49
+ }
50
+ if ( 'pending' === $status ) {
51
+ $uri = $post_actions->check_translation_url( $this->id, $this->document, $language->locale );
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 );
59
+ }
60
+ if ( in_array( $status, array( 'current', 'interim', 'intermediate', 'edited' ), true ) ) {
61
+ $uri = $post_actions->workbench_url( $this->id, $this->document, $language->lingotek_locale );
62
+ $title = __( 'Open in Lingotek workbench', 'lingotek-translation' );
63
+ }
64
+ if ( 'disabled' === $status ) {
65
+ $url = null;
66
+ }
67
+ }//end if
68
+ if ( null !== $uri && null !== $title ) {
69
+ $url = new Lingotek_Action_Url( $uri, $title );
70
+ }
71
+ return $url;
72
+ }
73
+
74
+ /**
75
+ * {@inheritdoc}
76
+ */
77
+ public function get_secondary_action_urls( $language, $status ) {
78
+ $urls = array();
79
+ $post_actions = $GLOBALS['wp_lingotek']->post_actions;
80
+ $document_id = $this->document->document_id;
81
+
82
+ $canonical_url = $this->get_canonical_url( $language );
83
+ if ( $canonical_url ) {
84
+ $urls[] = new Lingotek_Action_Url(
85
+ $canonical_url,
86
+ 'source' === $this->type ? __( 'View', 'lingotek-translation' ) : __( 'View translation', 'lingotek-translation' )
87
+ );
88
+ }
89
+
90
+ if ( $document_id ) {
91
+ if ( in_array( $status, array( 'request', 'deleted', 'untracked' ), true ) ) {
92
+ $urls[] = new Lingotek_Action_Url(
93
+ $post_actions->request_translation_url( $this->id, $this->document, $language->locale ),
94
+ sprintf( __( 'Request translation', 'lingotek-translation' ), $language->locale )
95
+ );
96
+ }
97
+ if ( 'pending' === $status ) {
98
+ $urls[] = new Lingotek_Action_Url(
99
+ $post_actions->check_translation_url( $this->id, $this->document, $language->locale ),
100
+ sprintf( __( 'Check translation status', 'lingotek-translation' ), $language->locale )
101
+ );
102
+ $urls[] = new Lingotek_Action_Url(
103
+ $post_actions->cancel_translation_url( $this->id, $this->document, $language->locale ),
104
+ sprintf( __( 'Cancel translation', 'lingotek-translation' ), $language->locale )
105
+ );
106
+ }
107
+ if ( in_array( $status, array( 'ready', 'error', 'failed' ), 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 )
111
+ );
112
+ $urls[] = new Lingotek_Action_Url(
113
+ $post_actions->cancel_translation_url( $this->id, $this->document, $language->locale ),
114
+ sprintf( __( 'Cancel translation', 'lingotek-translation' ), $language->locale )
115
+ );
116
+ }
117
+ // I'm not sure if the status is interim or intermediate, so we consider both for now.
118
+ if ( in_array( $status, array( 'current', 'interim', 'intermediate' ), true ) ) {
119
+ $urls[] = new Lingotek_Action_Url(
120
+ $post_actions->download_translation_url( $this->id, $this->document, $language->locale ),
121
+ sprintf( __( 'Re-Download translation', 'lingotek-translation' ), $language->locale )
122
+ );
123
+ }
124
+ // I'm not sure if the status is error or failed, so we consider both for now.
125
+ if ( in_array( $status, array( 'pending', 'ready', 'error', 'failed', 'current', 'interim', 'intermediate', 'edited' ), true ) ) {
126
+ $urls[] = new Lingotek_Action_Url(
127
+ $post_actions->workbench_url( $this->id, $this->document, $language->lingotek_locale ),
128
+ __( 'Open in Lingotek workbench', 'lingotek-translation' )
129
+ );
130
+ }
131
+ if ( 'disabled' === $status ) {
132
+ $url = null;
133
+ }
134
+ }//end if
135
+ return $urls;
136
+ }
137
+
138
+ }
admin/filters-columns.php CHANGED
@@ -26,6 +26,22 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
26
  // FIXME remove quick edit and bulk edit for now waiting for a solution to remove it only for uploaded documents.
27
  remove_filter( 'quick_edit_custom_box', array( &$this, 'quick_edit_custom_box' ), 10, 2 );
28
  remove_filter( 'bulk_edit_custom_box', array( &$this, 'quick_edit_custom_box' ), 10, 2 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  /**
@@ -47,6 +63,8 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
47
  }
48
  $this->print_patch_error();
49
  $this->print_cancel_error();
 
 
50
  foreach ( $this->model->get_languages_list() as $language ) {
51
  $columns[ 'language_' . $language->locale ] = $language->flag ? $language->flag :
52
  sprintf(
@@ -127,7 +145,7 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
127
  $inline = defined( 'DOING_AJAX' ) && $get_action === $action && ! empty( $inline_lang_choice );
128
  $lang = $inline ? $this->model->get_language( $inline_lang_choice ) : ( 'post' === $type ? PLL()->model->post->get_language( $object_id ) : PLL()->model->term->get_language( $object_id ) );
129
 
130
- if ( false === strpos( $column, 'language_' ) || ! $lang ) {
131
  if ( $custom_data ) {
132
  return $custom_data;
133
  } else {
@@ -136,6 +154,17 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
136
  }
137
 
138
  $language = $this->model->get_language( substr( $column, 9 ) );
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  // FIXME should I suppress quick edit?.
141
  // yes for uploaded posts, but I will need js as the field is built for all posts.
@@ -149,7 +178,6 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
149
  ( $language->slug === $lang->slug ? $object_id : 0 ) :
150
  'post' === $type ) ? PLL()->model->post->get( $object_id, $language ) : PLL()->model->term->get( $object_id, $language );
151
 
152
- $document = $this->lgtm->get_group( $type, $object_id );
153
  if ( isset( $document->source ) ) {
154
  $source_language = 'post' === $type ? PLL()->model->post->get_language( $document->source ) : PLL()->model->term->get_language( $document->source );
155
  $source_profile = Lingotek_Model::get_profile( $this->content_type, $source_language, $document->source );
@@ -233,23 +261,39 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
233
  * @param int $post_id post id.
234
  */
235
  public function post_column( $column, $post_id ) {
236
- if ( false === strpos( $column, 'language_' ) ) {
237
  return;
238
  }
239
  $this->content_type = get_post_type( $post_id );
240
 
241
  $allowed_html = array(
242
- 'a' => array(
243
  'href' => array(),
244
  'class' => array(),
245
  'title' => array(),
246
  'target' => array(),
247
  ),
248
- 'div' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  'title' => array(),
250
  'class' => array(),
251
  ),
252
- 'img' => array(
253
  'src' => array(),
254
  ),
255
  );
@@ -305,6 +349,7 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
305
  'class' => array(),
306
  ),
307
  'span' => array(
 
308
  'class' => array(),
309
  ),
310
  'img' => array(
@@ -365,4 +410,85 @@ class Lingotek_Filters_Columns extends PLL_Admin_Filters_Columns {
365
  }
366
  }
367
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  }
26
  // FIXME remove quick edit and bulk edit for now waiting for a solution to remove it only for uploaded documents.
27
  remove_filter( 'quick_edit_custom_box', array( &$this, 'quick_edit_custom_box' ), 10, 2 );
28
  remove_filter( 'bulk_edit_custom_box', array( &$this, 'quick_edit_custom_box' ), 10, 2 );
29
+
30
+ add_filter( 'default_hidden_columns', array( &$this, 'hide_pll_list_columns' ), 10, 2 );
31
+ }
32
+
33
+ /**
34
+ * Hide by default the polylang based columns.
35
+ *
36
+ * @param array $hidden Hidden columns.
37
+ * @param \WP_Screen $screen Screen object.
38
+ * @since 1.15.0
39
+ */
40
+ public function hide_pll_list_columns( $hidden, $screen ) {
41
+ foreach ( $this->model->get_languages_list() as $language ) {
42
+ $hidden[] = 'language_' . $language->locale;
43
+ }
44
+ return $hidden;
45
  }
46
 
47
  /**
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 ) {
69
  $columns[ 'language_' . $language->locale ] = $language->flag ? $language->flag :
70
  sprintf(
145
  $inline = defined( 'DOING_AJAX' ) && $get_action === $action && ! empty( $inline_lang_choice );
146
  $lang = $inline ? $this->model->get_language( $inline_lang_choice ) : ( 'post' === $type ? PLL()->model->post->get_language( $object_id ) : PLL()->model->term->get_language( $object_id ) );
147
 
148
+ if ( ( false === strpos( $column, 'lingotek_' ) && false === strpos( $column, 'language_' ) ) || ! $lang ) {
149
  if ( $custom_data ) {
150
  return $custom_data;
151
  } else {
154
  }
155
 
156
  $language = $this->model->get_language( substr( $column, 9 ) );
157
+ $document = $this->lgtm->get_group( $type, $object_id );
158
+
159
+ if ( 0 === strpos( $column, 'lingotek_' ) ) {
160
+ $source_language = PLL()->model->post->get_language( $object_id );
161
+ if ( 'lingotek_source' === $column ) {
162
+ return $this->render_source_status( $object_id, $source_language, $document, $document ? $document->status : 'untracked' );
163
+ }
164
+ if ( 'lingotek_targets' === $column ) {
165
+ return $this->render_target_statuses( $object_id, $source_language, $document );
166
+ }
167
+ }
168
 
169
  // FIXME should I suppress quick edit?.
170
  // yes for uploaded posts, but I will need js as the field is built for all posts.
178
  ( $language->slug === $lang->slug ? $object_id : 0 ) :
179
  'post' === $type ) ? PLL()->model->post->get( $object_id, $language ) : PLL()->model->term->get( $object_id, $language );
180
 
 
181
  if ( isset( $document->source ) ) {
182
  $source_language = 'post' === $type ? PLL()->model->post->get_language( $document->source ) : PLL()->model->term->get_language( $document->source );
183
  $source_profile = Lingotek_Model::get_profile( $this->content_type, $source_language, $document->source );
261
  * @param int $post_id post id.
262
  */
263
  public function post_column( $column, $post_id ) {
264
+ if ( false === strpos( $column, 'language_' ) && false === strpos( $column, 'lingotek_' ) ) {
265
  return;
266
  }
267
  $this->content_type = get_post_type( $post_id );
268
 
269
  $allowed_html = array(
270
+ 'a' => array(
271
  'href' => array(),
272
  'class' => array(),
273
  'title' => array(),
274
  'target' => array(),
275
  ),
276
+ 'div' => array(
277
+ 'title' => array(),
278
+ 'class' => array(),
279
+ ),
280
+ 'span' => array(
281
+ 'title' => array(),
282
+ 'class' => array(),
283
+ ),
284
+ 'button' => array(
285
+ 'title' => array(),
286
+ 'class' => array(),
287
+ ),
288
+ 'ul' => array(
289
+ 'title' => array(),
290
+ 'class' => array(),
291
+ ),
292
+ 'li' => array(
293
  'title' => array(),
294
  'class' => array(),
295
  ),
296
+ 'img' => array(
297
  'src' => array(),
298
  ),
299
  );
349
  'class' => array(),
350
  ),
351
  'span' => array(
352
+ 'title' => array(),
353
  'class' => array(),
354
  ),
355
  'img' => array(
410
  }
411
  }
412
  }
413
+
414
+ /**
415
+ * Renders the source status.
416
+ *
417
+ * @param int $id
418
+ * @param object $source_language
419
+ * @param object $document
420
+ *
421
+ * @return string
422
+ *
423
+ * @since 1.5.0
424
+ */
425
+ private function render_source_status( $id, $source_language, $document ) {
426
+ // If the source was uploaded, we want the group source document locale. If not, the document locale.
427
+ $source_language = is_object( $document ) && isset( $document->source ) ?
428
+ PLL()->model->post->get_language( $document->source ) :
429
+ $source_language;
430
+ $source_status = is_object( $document ) && $document->status ? $document->status : 'untracked';
431
+
432
+ $source_chip = new Lingotek_Chip_Source( $id, $document );
433
+ return $source_chip->render( $source_language, $source_status );
434
+ }
435
+
436
+ /**
437
+ * Render target statuses.
438
+ *
439
+ * @param int $id
440
+ * @param object $source_language
441
+ * @param object $document
442
+ *
443
+ * @return string
444
+ *
445
+ * @since 1.5.0
446
+ */
447
+ private function render_target_statuses( $id, $source_language, $document ) {
448
+ $targets_markup = '';
449
+ $targets = isset( $document->translations ) ? array_keys( $document->translations ) : array();
450
+ $targets_data = array();
451
+ // If the source was uploaded, we want the group source document locale. If not, the document locale.
452
+ $wp_source_locale = is_object( $document ) && isset( $document->source ) ?
453
+ PLL()->model->post->get_language( $document->source )->locale :
454
+ $source_language->locale;
455
+
456
+ foreach ( $targets as $wp_locale ) {
457
+ $language = PLL()->model->get_language( $wp_locale );
458
+ // If we had a translation status but the language was removed/disabled, language is false.
459
+ if ( $language ) {
460
+ $status = $document->translations[ $wp_locale ];
461
+ $targets_data[ $wp_locale ] = array(
462
+ 'id' => $id,
463
+ 'source_language' => $source_language,
464
+ 'document' => $document,
465
+ 'status' => $status,
466
+ 'language' => $language,
467
+ );
468
+ }
469
+ }
470
+
471
+ // We need to add the locales that were not requested, so they can be requested, but only when the source
472
+ // in a valid state for requesting those.
473
+ if ( is_object( $document ) && isset( $document->status ) && ! in_array( $document->status, array( 'cancelled', 'deleted', 'archived' ), true ) ) {
474
+ foreach ( PLL()->model->get_languages_list() as $pll_language ) {
475
+ $status = 'request';
476
+ if ( ! isset( $targets_data[ $pll_language->locale ] ) && $pll_language->locale !== $wp_source_locale ) {
477
+ $targets_data[ $pll_language->locale ] = array(
478
+ 'id' => $id,
479
+ 'source_language' => $source_language,
480
+ 'document' => $document,
481
+ 'status' => $status,
482
+ 'language' => $pll_language,
483
+ );
484
+ }
485
+ }
486
+ }
487
+ foreach ( $targets_data as $locale => $locale_data ) {
488
+ $target_chip = new Lingotek_Chip_Target( $locale_data['id'], $locale_data['document'] );
489
+ $targets_markup .= $target_chip->render( $locale_data['language'], $locale_data['status'] );
490
+ }
491
+ return $targets_markup;
492
+ }
493
+
494
  }
admin/post-actions.php CHANGED
@@ -157,6 +157,8 @@ class Lingotek_Post_Actions extends Lingotek_Actions {
157
  $redirect = admin_url( "edit.php?post_type=$typenow" );
158
  }
159
 
 
 
160
  switch ( $action ) {
161
  case 'bulk-lingotek-upload':
162
  $type = empty( $typenow ) ? 'media' : 'post';
@@ -198,6 +200,7 @@ class Lingotek_Post_Actions extends Lingotek_Actions {
198
  case 'bulk-lingotek-status':
199
  case 'bulk-lingotek-delete':
200
  case 'bulk-lingotek-cancel':
 
201
  if ( empty( $post_ids ) ) {
202
  $type = empty( $typenow ) ? 'media' : 'post';
203
  $filtered_get = filter_input_array( INPUT_GET );
@@ -209,11 +212,14 @@ class Lingotek_Post_Actions extends Lingotek_Actions {
209
  }
210
 
211
  empty( $typenow ) ? check_admin_referer( 'bulk-media' ) : check_admin_referer( 'bulk-posts' );
212
- if ( in_array( $action, array( 'bulk-lingotek-cancel', 'bulk-lingotek-delete' ), true ) ) {
213
  $redirect = add_query_arg( 'lingotek_remove', 1, $redirect );
214
  }
215
  $redirect = add_query_arg( $action, 1, $redirect );
216
  $redirect = add_query_arg( 'ids', implode( ',', $post_ids ), $redirect );
 
 
 
217
  break;
218
 
219
  case 'lingotek-upload':
157
  $redirect = admin_url( "edit.php?post_type=$typenow" );
158
  }
159
 
160
+ list($action, $locale) = explode( ':', $action, 2 );
161
+
162
  switch ( $action ) {
163
  case 'bulk-lingotek-upload':
164
  $type = empty( $typenow ) ? 'media' : 'post';
200
  case 'bulk-lingotek-status':
201
  case 'bulk-lingotek-delete':
202
  case 'bulk-lingotek-cancel':
203
+ case 'bulk-lingotek-cancel-translation':
204
  if ( empty( $post_ids ) ) {
205
  $type = empty( $typenow ) ? 'media' : 'post';
206
  $filtered_get = filter_input_array( INPUT_GET );
212
  }
213
 
214
  empty( $typenow ) ? check_admin_referer( 'bulk-media' ) : check_admin_referer( 'bulk-posts' );
215
+ if ( in_array( $action, array( 'bulk-lingotek-cancel', 'bulk-lingotek-delete' ), true ) && ! $locale ) {
216
  $redirect = add_query_arg( 'lingotek_remove', 1, $redirect );
217
  }
218
  $redirect = add_query_arg( $action, 1, $redirect );
219
  $redirect = add_query_arg( 'ids', implode( ',', $post_ids ), $redirect );
220
+ if ( $locale ) {
221
+ $redirect = add_query_arg( 'target_locale', $locale, $redirect );
222
+ }
223
  break;
224
 
225
  case 'lingotek-upload':
admin/tutorial/content.php CHANGED
@@ -107,7 +107,17 @@
107
  <th><?php _e( 'Translation Cancelled', 'lingotek-translation' ); ?></th>
108
  <td><?php _e( 'The translation is cancelled. The source cannot be translated into this language again.', 'lingotek-translation' ); ?></td>
109
  </tr>
110
- <tr>
 
 
 
 
 
 
 
 
 
 
111
  <td><span class="lingotek-color dashicons dashicons-no"></span></td>
112
  <th><?php _e( 'Out of Sync', 'lingotek-translation' ); ?></th>
113
  <td><?php _e( 'You have made changes to source content. The source must be sent to Lingotek again for additional translation.', 'lingotek-translation' ); ?></td>
107
  <th><?php _e( 'Translation Cancelled', 'lingotek-translation' ); ?></th>
108
  <td><?php _e( 'The translation is cancelled. The source cannot be translated into this language again.', 'lingotek-translation' ); ?></td>
109
  </tr>
110
+ <tr>
111
+ <td><span class="lingotek-color dashicons dashicons-remove"></span></td>
112
+ <th><?php _e( 'Document Archived', 'lingotek-translation' ); ?></th>
113
+ <td><?php _e( 'The document is archived. The document can be translated again.', 'lingotek-translation' ); ?></td>
114
+ </tr>
115
+ <tr>
116
+ <td><span class="lingotek-color dashicons dashicons-remove"></span></td>
117
+ <th><?php _e( 'Translation Deleted', 'lingotek-translation' ); ?></th>
118
+ <td><?php _e( 'The translation is deleted. The source can be translated into this language again.', 'lingotek-translation' ); ?></td>
119
+ </tr>
120
+ <tr>
121
  <td><span class="lingotek-color dashicons dashicons-no"></span></td>
122
  <th><?php _e( 'Out of Sync', 'lingotek-translation' ); ?></th>
123
  <td><?php _e( 'You have made changes to source content. The source must be sent to Lingotek again for additional translation.', 'lingotek-translation' ); ?></td>
css/actions.css ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .lingotek-source-dropdown-toggle,
2
+ .lingotek-target-dropdown-toggle {
3
+ display: inline-block;
4
+ height: 30px;
5
+ width: 20px;
6
+ background: url("../img/icon-collapse.svg") no-repeat center;
7
+ transform: rotateZ(180deg);
8
+ background-size: 15px 15px;
9
+ padding: 0;
10
+ padding-left: 0;
11
+ margin-bottom: 2px;
12
+ float: right;
13
+ opacity: .8;
14
+ }
15
+
16
+ .lingotek-source-dropdown-toggle:focus,
17
+ .lingotek-target-dropdown-toggle:focus {
18
+ outline: none;
19
+ }
20
+
21
+ .lingotek-source-dropdown.open .lingotek-source-dropdown-toggle,
22
+ .lingotek-target-dropdown.open .lingotek-target-dropdown-toggle {
23
+ transform: rotateZ(0);
24
+ }
25
+
26
+ .lingotek-source-dropdown.open .lingotek-source-actions,
27
+ .lingotek-target-dropdown.open .lingotek-target-actions {
28
+ display: block;
29
+ }
30
+
31
+ .lingotek-source-dropdown,
32
+ .lingotek-target-dropdown {
33
+ position: relative;
34
+ display: inline-block;
35
+ margin-right: 8px;
36
+ }
37
+
38
+ .lingotek-source-actions,
39
+ .lingotek-target-actions {
40
+ display: none;
41
+ position: absolute;
42
+ list-style-type: none;
43
+ margin: 0;
44
+ padding: 0;
45
+ background-color: #f9f9f9;
46
+ min-width: 200px;
47
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
48
+ z-index: 1;
49
+ font-size: 90%;
50
+ }
51
+
52
+ .lingotek-source-actions a,
53
+ .lingotek-target-actions a {
54
+ color: black;
55
+ padding: 12px 16px;
56
+ text-decoration: none;
57
+ display: block;
58
+ font-size: 12px;
59
+ }
60
+
61
+ .lingotek-source-actions a:hover,
62
+ .lingotek-target-actions a:hover {
63
+ background-color: #f1f1f1
64
+ }
65
+
66
+ .visually-hidden {
67
+ position: absolute !important;
68
+ overflow: hidden;
69
+ clip: rect(1px, 1px, 1px, 1px);
70
+ width: 1px;
71
+ height: 1px;
72
+ word-wrap: normal;
73
+ }
css/statuses.css ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .language-icon {
2
+ background-color:#80a49e;
3
+ border-radius: 1px;
4
+ margin-right: 0px;
5
+ padding: 4px;
6
+ color: #fff;
7
+ white-space: nowrap;
8
+ border: 1px solid #fff;
9
+ border-spacing: 0;
10
+ width: 40px;
11
+ height: 20px;
12
+ }
13
+
14
+ .language-icon.source-failed,
15
+ .language-icon.source-error,
16
+ .language-icon.target-error {
17
+ background-color: #B71C1C;
18
+ border: 1px solid #B71C1C;
19
+ color: #FFFFFF;
20
+ }
21
+
22
+ .language-icon.source-failed a,
23
+ .language-icon.source-error a,
24
+ .language-icon.target-error a {
25
+ color: #FFFFFF;
26
+ }
27
+
28
+ span.language-icon ,
29
+ a.language-icon {
30
+ margin-bottom: 2px;
31
+ display: inline-block;
32
+ text-align: center;
33
+ }
34
+
35
+ /** Content statuses v2.0 */
36
+ .language-icon.source-untracked ,
37
+ .language-icon.source-request {
38
+ background-color: #ffffff;
39
+ border: 1px solid #999999;
40
+ color: #999999;
41
+ }
42
+ .language-icon.source-untracked a ,
43
+ .language-icon.source-request a {
44
+ color: #999999;
45
+ }
46
+ .language-icon.source-untracked:hover ,
47
+ .language-icon.source-request:hover {
48
+ color: #8c8c8c;
49
+ border: 1px solid #8c8c8c;
50
+ }
51
+
52
+ .language-icon.source-edited {
53
+ background-color:#ffc107;
54
+ border: 1px solid #575757;
55
+ color: #575757;
56
+ }
57
+ .language-icon.source-edited a {
58
+ color: #575757;
59
+ }
60
+ .language-icon.source-edited:hover {
61
+ background-color: #ffb300;
62
+ }
63
+
64
+ .language-icon.source-importing {
65
+ background-color:#f37f16;
66
+ border: 1px solid #f37f16;
67
+ color: #ffffff;
68
+ }
69
+ .language-icon.source-importing a {
70
+ color: #ffffff;
71
+ }
72
+ .language-icon.source-importing:hover {
73
+ background-color: #e67209;
74
+ }
75
+
76
+ .language-icon.source-current {
77
+ background-color:#5fdc64;
78
+ border: 1px solid #5fdc64;
79
+ color: #ffffff;
80
+ }
81
+ .language-icon.source-current a {
82
+ color: #ffffff;
83
+ }
84
+ .language-icon.source-current:hover {
85
+ background-color: #43a047;
86
+ }
87
+
88
+ .language-icon.target-request {
89
+ background-color:#ffffff;
90
+ border: 1px solid #f37f16;
91
+ color: #f37f16;
92
+ }
93
+ .language-icon.target-request a {
94
+ color: #f37f16;
95
+ }
96
+ .language-icon.target-request a:hover {
97
+ color: #e67209;
98
+ }
99
+ .language-icon.target-request:hover {
100
+ color: #e67209;
101
+ border: 1px solid #e67209;
102
+ }
103
+
104
+ .language-icon.target-pending {
105
+ background-color:#f37f16;
106
+ border: 1px solid #f37f16;
107
+ color: #ffffff;
108
+ }
109
+ .language-icon.target-pending a {
110
+ color: #ffffff;
111
+ }
112
+ .language-icon.target-pending:hover {
113
+ background-color: #e67209;
114
+ }
115
+
116
+ .language-icon.target-ready {
117
+ background-color:#2196f3;
118
+ border: 1px solid #2196f3;
119
+ color: #ffffff;
120
+ }
121
+ .language-icon.target-ready a {
122
+ color: #ffffff;
123
+ }
124
+ .language-icon.target-ready:hover {
125
+ background-color: #1e88e5;
126
+ }
127
+
128
+ .language-icon.target-edited {
129
+ background-color:#ffc107;
130
+ border: 1px solid #575757;
131
+ color: #575757;
132
+ }
133
+ .language-icon.target-edited a {
134
+ color: #575757;
135
+ }
136
+ .language-icon.target-edited:hover {
137
+ background-color: #ffb300;
138
+ }
139
+
140
+ .language-icon.target-current {
141
+ background-color:#5fdc64;
142
+ border: 1px solid #5fdc64;
143
+ color: #ffffff;
144
+ }
145
+ .language-icon.target-current a {
146
+ color: #ffffff;
147
+ }
148
+ .language-icon.target-current:hover {
149
+ background-color: #43a047;
150
+ }
151
+
152
+ .language-icon.target-interim,
153
+ .language-icon.target-intermediate {
154
+ background-color: #ffffff;
155
+ border: 1px solid #4caf50;
156
+ color: #4caf50;
157
+ }
158
+
159
+ .language-icon.target-interim a,
160
+ .language-icon.target-intermediate a {
161
+ color: #4caf50;
162
+ }
163
+
164
+ .language-icon.target-interim a:hover,
165
+ .language-icon.target-intermediate a:hover {
166
+ color: #43a047;
167
+ }
168
+
169
+ .language-icon.target-interim:hover,
170
+ .language-icon.target-intermediate:hover {
171
+ color: #43a047;
172
+ }
173
+
174
+ .language-icon.target-untracked {
175
+ background-color:#999999;
176
+ border: 1px solid #999999;
177
+ color: #ffffff;
178
+ }
179
+ .language-icon.target-untracked a {
180
+ color: #ffffff;
181
+ }
182
+ .language-icon.target-untracked:hover {
183
+ background-color: #8c8c8c;
184
+ }
185
+
186
+ .language-icon.source-cancelled,
187
+ .language-icon.target-cancelled {
188
+ background-color:#ffffff;
189
+ border: 1px solid #b71c1c;
190
+ color: #b71c1c;
191
+ }
192
+ .language-icon.source-cancelled a,
193
+ .language-icon.target-cancelled a {
194
+ color: #b71c1c;
195
+ }
196
+
197
+ .language-icon.source-cancelled:hover,
198
+ .language-icon.target-cancelled:hover {
199
+ background-color:#ffffff;
200
+ color: #9e0303;
201
+ border: 1px solid #9e0303;
202
+ }
203
+
204
+ .language-icon.source-disabled,
205
+ .language-icon.target-disabled {
206
+ background-color:#ffffff;
207
+ border: 1px solid #999999;
208
+ color: #999999;
209
+ text-decoration:line-through;
210
+ }
211
+
212
+ .language-icon.source-disabled a,
213
+ .language-icon.target-disabled a {
214
+ color: #999999;
215
+ }
216
+
217
+ .language-icon.source-none,
218
+ .language-icon.target-none {
219
+ background-color:#ffffff;
220
+ border: 1px solid #999999;
221
+ color: #999999;
222
+ }
223
+
224
+ .language-icon.source-none a,
225
+ .language-icon.target-none a {
226
+ color: #999999;
227
+ }
228
+
229
+ .language-icon.source-archived,
230
+ .language-icon.source-deleted,
231
+ .language-icon.target-archived,
232
+ .language-icon.target-deleted {
233
+ background-color: #ffffff;
234
+ border: 1px solid #404040;
235
+ color: #404040;
236
+ }
237
+
238
+ .language-icon.source-archived a,
239
+ .language-icon.source-deleted a,
240
+ .language-icon.target-archived a,
241
+ .language-icon.target-deleted a {
242
+ color: #404040;
243
+ }
244
+
245
+ .fixed .column-lingotek_source {
246
+ width: 10%;
247
+ }
248
+
249
+ .fixed .column-lingotek_translations {
250
+ width: 70%;
251
+ }
252
+
253
+ .fixed .column-profile {
254
+ width: 10%;
255
+ }
here DELETED
File without changes
img/icon-collapse.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
2
+ <path class="icon" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" fill="#787878"/>
3
+ <path class="bg" d="M0 0h24v24H0z" fill="none"/>
4
+ </svg>
include/api.php CHANGED
@@ -226,26 +226,30 @@ class Lingotek_API extends Lingotek_HTTP {
226
  return $success;
227
  }//end if
228
 
229
- if ( $status_code == 410 ) {
 
230
  Lingotek_Logger::info(
231
- 'Document ID was archived, reuploading document',
232
  array(
233
  'old_document_id' => $id,
234
  'wp_id' => $wp_id,
235
  'args' => $args,
236
  )
237
  );
238
- $targets = array_keys( $document->translations );
239
- $unformatted_args['translation_locale_code'] = $targets;
240
- $this->format_args_for_upload( $unformatted_args, $wp_id );
241
- foreach ( $document->translations as $locale ) {
242
- $document->translations[ $locale ] = 'pending';
 
243
  }
 
 
244
  $document->document_id = 'disassociated_' . $document->document_id;
245
  unset( $document->desc_array['lingotek'] );
246
  $document->save();
247
  return $this->upload_document( $unformatted_args, $wp_id );
248
- }
249
  if ( $status_code == 402 ) {
250
  Lingotek_Logger::error( 'There was an error updating WordPress item', array( 'error' => $this->get_error_message_from_response( $response ) ) );
251
  $document->source_failed();
@@ -282,7 +286,7 @@ class Lingotek_API extends Lingotek_HTTP {
282
 
283
  private function update_patch_error_message( $response, $status_code, $title ) {
284
  // Do not inform user if call was successful.
285
- if ( $status_code == 202 || $status_code == 410 ) {
286
  return;
287
  }
288
  $lingotek_log_errors = get_option( 'lingotek_log_errors', array() );
@@ -716,19 +720,20 @@ class Lingotek_API extends Lingotek_HTTP {
716
  *
717
  * @since 0.1
718
  *
719
- * @param string $id document id
720
- * @param string $locale Lingotek locale
721
- * @param array $args optional arguments (only workflow_id at the moment)
722
- * @return bool true if the request succeeded
 
723
  */
724
- public function request_translation( $id, $locale, $args = array(), $wp_id = null ) {
725
- $lgtm = $GLOBALS['wp_lingotek']->model;
726
- $document = $lgtm->get_group_by_id( $id );
727
- $locale = Lingotek::map_to_lingotek_locale( $locale );
728
- $args = $unformatted_args = wp_parse_args( $args, array( 'workflow_id' => $this->get_workflow_id() ) );
729
- $args = array_merge( array( 'locale_code' => $locale ), $args );
730
- $response = $this->post( $this->api_url . '/document/' . $id . '/translation', $args );
731
- $title = isset( $args['title'] ) ? $args['title'] : $id;
732
  if ( $wp_id ) {
733
  $arr = get_option( 'lingotek_log_errors', array() );
734
  $status_code = wp_remote_retrieve_response_code( $response );
@@ -737,26 +742,37 @@ class Lingotek_API extends Lingotek_HTTP {
737
  if ( 423 === $status_code ) {
738
  $document->document_id = $body->next_document_id;
739
  $document->save();
740
- return $this->request_translation( $body->next_document_id, $locale, $unformatted_args, $wp_id );
741
  }
742
  if ( 402 === $status_code ) {
743
  Lingotek_Logger::error( 'There was an error updating WordPress item', array( 'error' => $this->get_error_message_from_response( $response ) ) );
744
  $document->source_failed();
745
  return false;
746
  }
747
- if ( 410 === $status_code ) {
748
  // WP hooks automatically check source status so this might not get called
749
- $targets = array_keys( $document->translations );
750
- $targets[] = $locale;
751
- $unformatted_args['translation_locale_code'] = $targets;
752
- $this->format_args_for_upload( $unformatted_args, $wp_id );
 
 
753
  unset( $document->desc_array['lingotek'] );
754
  $document->save();
755
- foreach ( $document->translations as $locale ) {
756
- $document->translations[ $locale ] = 'pending';
 
 
 
757
  }
758
- return $this->upload_document( $unformatted_args, $wp_id );
759
- }
 
 
 
 
 
 
760
  if ( 400 === $status_code &&
761
  ! empty( $body->messages ) &&
762
  strpos( $body->messages[0], 'already exists' ) ) {
@@ -766,17 +782,17 @@ class Lingotek_API extends Lingotek_HTTP {
766
  if ( 201 === $status_code ) {
767
  if ( isset( $arr[ $wp_id ] ) ) {
768
  unset( $arr[ $wp_id ]['wp_error'] );
769
- unset( $arr[ $wp_id ]['request_translation'][ $locale ] );
770
  if ( empty( $arr[ $wp_id ] ) ) {
771
  unset( $arr[ $wp_id ] );
772
  }
773
  }
774
  } elseif ( is_wp_error( $response ) ) {
775
  $arr[ $wp_id ]['wp_error'] = __( 'Make sure you have internet connectivity', 'lingotek-translation' );
776
- } elseif ( 400 == $status_code || 404 == $status_code ) {
777
- $arr[ $wp_id ]['request_translation'][ $locale ] = sprintf(
778
  __( 'There was an error requesting translation %1$s for WordPress item %2$s', 'lingotek-translation' ),
779
- $locale,
780
  $wp_id
781
  );
782
  }
@@ -787,8 +803,8 @@ class Lingotek_API extends Lingotek_HTTP {
787
  $response,
788
  'RequestTranslation: Error Occurred',
789
  array(
790
- 'document_id' => $id,
791
- 'locale' => $locale,
792
  'args' => $args,
793
  )
794
  );
@@ -862,18 +878,20 @@ class Lingotek_API extends Lingotek_HTTP {
862
  * cancels a translation
863
  *
864
  * @since 1.4.2
 
865
  *
866
- * @param string $id document id
867
  * @param string $locale Lingotek locale
868
  */
869
- public function cancel_translation( $id, $locale, $wp_id = null ) {
870
- $args = wp_parse_args(
871
  array(
872
  'cancelled_reason' => 'CANCELLED_BY_AUTHOR',
873
  'mark_invoiceable' => true,
874
  )
875
  );
876
- $response = $this->post( "$this->api_url/document/$id/translation/$locale/cancel", $args );
 
877
 
878
  if ( $wp_id ) {
879
  $arr = get_option( 'lingotek_log_errors' );
@@ -886,7 +904,7 @@ class Lingotek_API extends Lingotek_HTTP {
886
  $response,
887
  'CancelTranslation: Error occurred',
888
  array(
889
- 'id' => $id,
890
  'wordpress_id' => $wp_id,
891
  )
892
  );
@@ -895,7 +913,7 @@ class Lingotek_API extends Lingotek_HTTP {
895
  Lingotek_Logger::info(
896
  'Target cancelled',
897
  array(
898
- 'document_id' => $id,
899
  'wp_id' => $wp_id,
900
  )
901
  );
226
  return $success;
227
  }//end if
228
 
229
+ if ( $status_code == 410 || $status_code == 404 ) {
230
+ $document_status_string = $status_code == 410 ? 'archived' : 'not found';
231
  Lingotek_Logger::info(
232
+ 'Document ID was ' . $document_status_string . ', reuploading document',
233
  array(
234
  'old_document_id' => $id,
235
  'wp_id' => $wp_id,
236
  'args' => $args,
237
  )
238
  );
239
+ $targets = array_keys( $document->translations );
240
+ $lingotek_locales = array();
241
+ foreach ( $targets as $target ) {
242
+ // Targets stored in the document translations property are the polylang targets, so we need to get the lingotek locales instead
243
+ $document->translations[ $target ] = 'pending';
244
+ $lingotek_locales[] = Lingotek::map_to_lingotek_locale( $target );
245
  }
246
+ $unformatted_args['translation_locale_code'] = $lingotek_locales;
247
+ $this->format_args_for_upload( $unformatted_args, $wp_id );
248
  $document->document_id = 'disassociated_' . $document->document_id;
249
  unset( $document->desc_array['lingotek'] );
250
  $document->save();
251
  return $this->upload_document( $unformatted_args, $wp_id );
252
+ }//end if
253
  if ( $status_code == 402 ) {
254
  Lingotek_Logger::error( 'There was an error updating WordPress item', array( 'error' => $this->get_error_message_from_response( $response ) ) );
255
  $document->source_failed();
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() );
720
  *
721
  * @since 0.1
722
  *
723
+ * @param string &$document_id document id.
724
+ * @param string $locale Lingotek locale.
725
+ * @param array $args optional arguments (only workflow_id at the moment).
726
+ * @param object $document the translation term for the post.
727
+ * @return bool true if the request succeeded.
728
  */
729
+ public function request_translation( &$document_id, $locale, $args = array(), $wp_id = null, $document = null ) {
730
+ $lgtm = $GLOBALS['wp_lingotek']->model;
731
+ $document = isset( $document ) ? $document : $lgtm->get_group_by_id( $document_id );
732
+ $lingotek_locale = Lingotek::map_to_lingotek_locale( $locale );
733
+ $args = $unformatted_args = wp_parse_args( $args, array( 'workflow_id' => $this->get_workflow_id() ) );
734
+ $args = array_merge( array( 'locale_code' => $lingotek_locale ), $args );
735
+ $response = $this->post( $this->api_url . '/document/' . $document_id . '/translation', $args );
736
+ $title = isset( $args['title'] ) ? $args['title'] : $document_id;
737
  if ( $wp_id ) {
738
  $arr = get_option( 'lingotek_log_errors', array() );
739
  $status_code = wp_remote_retrieve_response_code( $response );
742
  if ( 423 === $status_code ) {
743
  $document->document_id = $body->next_document_id;
744
  $document->save();
745
+ return $this->request_translation( $body->next_document_id, $lingotek_locale, $unformatted_args, $wp_id );
746
  }
747
  if ( 402 === $status_code ) {
748
  Lingotek_Logger::error( 'There was an error updating WordPress item', array( 'error' => $this->get_error_message_from_response( $response ) ) );
749
  $document->source_failed();
750
  return false;
751
  }
752
+ if ( 410 === $status_code || 404 === $status_code ) {
753
  // WP hooks automatically check source status so this might not get called
754
+ $polylang_targets = array_keys( $document->translations );
755
+ if ( ! in_array( $locale, $polylang_targets ) ) {
756
+ $polylang_targets[] = $locale;
757
+ }
758
+ $params = $lgtm->reupload_build_params( $wp_id );
759
+ $unformatted_args = array_merge( $unformatted_args, $params );
760
  unset( $document->desc_array['lingotek'] );
761
  $document->save();
762
+ $lingotek_locales = array();
763
+ foreach ( $polylang_targets as $target ) {
764
+ $document->translations[ $target ] = 'pending';
765
+ // Targets stored in the document translations property are the polylang targets, so we need to get the lingotek locales instead
766
+ $lingotek_locales[] = Lingotek::map_to_lingotek_locale( $target );
767
  }
768
+ $unformatted_args['translation_locale_code'] = $lingotek_locales;
769
+ $this->format_args_for_upload( $unformatted_args, $wp_id );
770
+ $upload_response = $this->upload_document( $unformatted_args, $wp_id );
771
+ if ( $upload_response ) {
772
+ $document_id = $upload_response;
773
+ }
774
+ return $upload_response;
775
+ }//end if
776
  if ( 400 === $status_code &&
777
  ! empty( $body->messages ) &&
778
  strpos( $body->messages[0], 'already exists' ) ) {
782
  if ( 201 === $status_code ) {
783
  if ( isset( $arr[ $wp_id ] ) ) {
784
  unset( $arr[ $wp_id ]['wp_error'] );
785
+ unset( $arr[ $wp_id ]['request_translation'][ $lingotek_locale ] );
786
  if ( empty( $arr[ $wp_id ] ) ) {
787
  unset( $arr[ $wp_id ] );
788
  }
789
  }
790
  } elseif ( is_wp_error( $response ) ) {
791
  $arr[ $wp_id ]['wp_error'] = __( 'Make sure you have internet connectivity', 'lingotek-translation' );
792
+ } elseif ( 400 == $status_code ) {
793
+ $arr[ $wp_id ]['request_translation'][ $lingotek_locale ] = sprintf(
794
  __( 'There was an error requesting translation %1$s for WordPress item %2$s', 'lingotek-translation' ),
795
+ $lingotek_locale,
796
  $wp_id
797
  );
798
  }
803
  $response,
804
  'RequestTranslation: Error Occurred',
805
  array(
806
+ 'document_id' => $document_id,
807
+ 'locale' => $lingotek_locale,
808
  'args' => $args,
809
  )
810
  );
878
  * cancels a translation
879
  *
880
  * @since 1.4.2
881
+ * @since 1.5.0 Changed signature, breaking change.
882
  *
883
+ * @param string $document_id document id
884
  * @param string $locale Lingotek locale
885
  */
886
+ public function cancel_translation( $document_id, $locale, $wp_id = null ) {
887
+ $args = wp_parse_args(
888
  array(
889
  'cancelled_reason' => 'CANCELLED_BY_AUTHOR',
890
  'mark_invoiceable' => true,
891
  )
892
  );
893
+ $lingotek_locale = Lingotek::map_to_lingotek_locale( $locale );
894
+ $response = $this->post( "$this->api_url/document/$document_id/translation/$lingotek_locale/cancel", $args );
895
 
896
  if ( $wp_id ) {
897
  $arr = get_option( 'lingotek_log_errors' );
904
  $response,
905
  'CancelTranslation: Error occurred',
906
  array(
907
+ 'id' => $document_id,
908
  'wordpress_id' => $wp_id,
909
  )
910
  );
913
  Lingotek_Logger::info(
914
  'Target cancelled',
915
  array(
916
+ 'document_id' => $document_id,
917
  'wp_id' => $wp_id,
918
  )
919
  );
include/callback.php CHANGED
@@ -44,6 +44,9 @@ class Lingotek_Callback {
44
  case 'target_cancelled':
45
  $this->handleTargetCancelled( $document );
46
  break;
 
 
 
47
  case 'document_uploaded':
48
  case 'document_updated':
49
  $this->handleDocumentUploadedOrUpdated( $document );
@@ -52,8 +55,10 @@ class Lingotek_Callback {
52
  $this->handleImportFailure( $document );
53
  break;
54
  case 'document_archived':
 
 
55
  case 'document_deleted':
56
- $this->handleDocumentArchivedOrDeleted( $document );
57
  break;
58
  case 'document_cancelled';
59
  $this->handleDocumentCancelled( $document );
@@ -152,17 +157,53 @@ class Lingotek_Callback {
152
  $document->source_failed();
153
  }
154
 
155
- public function handleDocumentArchivedOrDeleted( $document ) {
156
  $callback_parameters = array(
157
- 'Document ID' => isset( $_GET['document_id'] ) ? $_GET['document_id'] : null,
158
- 'Type' => isset( $_GET['type'] ) ? $_GET['type'] : null,
 
 
 
159
  );
160
  Lingotek_Logger::info( 'Callback received', array( 'Callback Parameters' => $callback_parameters ) );
 
161
  $wp_id = isset( $document->desc_array['lingotek']['source'] ) ? $document->desc_array['lingotek']['source'] : null;
162
- unset( $document->desc_array['lingotek'] );
163
- $document->document_id = '';
 
 
 
 
 
 
 
 
164
  $document->save();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  wp_remove_object_terms( $wp_id, "lingotek_hash_{$wp_id}", 'lingotek_hash' );
 
 
 
 
166
  }
167
 
168
  public function handleDocumentCancelled( $document ) {
@@ -171,15 +212,16 @@ class Lingotek_Callback {
171
  'Type' => isset( $_GET['type'] ) ? $_GET['type'] : null,
172
  );
173
  if ( isset( $document->desc_array['lingotek']['translations'] ) && count( $document->desc_array['lingotek']['translations'] ) > 0 ) {
174
- unset( $document->desc_array['lingotek']['translations'] );
175
- $document->desc_array['lingotek']['status'] = 'cancelled';
176
- } else {
177
- unset( $document->desc_array['lingotek'] );
178
  }
179
- $wp_id = isset( $document->desc_array['lingotek']['source'] ) ? $document->desc_array['lingotek']['source'] : null;
180
- $document->status = 'cancelled';
181
- $document->save();
182
  wp_remove_object_terms( $wp_id, "lingotek_hash_{$wp_id}", 'lingotek_hash' );
 
 
 
 
183
  Lingotek_Logger::info( 'Callback Received', array( 'Callback Parameters' => $callback_parameters ) );
184
  }
185
 
@@ -198,4 +240,26 @@ class Lingotek_Callback {
198
  $document->update_translation_status( $locale, 'cancelled' );
199
  Lingotek_Logger::info( 'Callback received', array( 'Callback Parameters' => $callback_parameters ) );
200
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
44
  case 'target_cancelled':
45
  $this->handleTargetCancelled( $document );
46
  break;
47
+ case 'target_deleted':
48
+ $this->handleTargetDeleted( $document );
49
+ break;
50
  case 'document_uploaded':
51
  case 'document_updated':
52
  $this->handleDocumentUploadedOrUpdated( $document );
55
  $this->handleImportFailure( $document );
56
  break;
57
  case 'document_archived':
58
+ $this->handleDocumentArchived( $document );
59
+ break;
60
  case 'document_deleted':
61
+ $this->handleDocumentDeleted( $document );
62
  break;
63
  case 'document_cancelled';
64
  $this->handleDocumentCancelled( $document );
157
  $document->source_failed();
158
  }
159
 
160
+ public function handleDocumentArchived( $document ) {
161
  $callback_parameters = array(
162
+ // As this comes from Lingotek callback we cannot have a nonce.
163
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
164
+ 'Document ID' => isset( $_GET['document_id'] ) ? sanitize_key( $_GET['document_id'] ) : null,
165
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
166
+ 'Type' => isset( $_GET['type'] ) ? sanitize_key( $_GET['type'] ) : null,
167
  );
168
  Lingotek_Logger::info( 'Callback received', array( 'Callback Parameters' => $callback_parameters ) );
169
+ // The removal of the lingotek hash term is needed as it could prevent further uploads.
170
  $wp_id = isset( $document->desc_array['lingotek']['source'] ) ? $document->desc_array['lingotek']['source'] : null;
171
+ wp_remove_object_terms( $wp_id, "lingotek_hash_{$wp_id}", 'lingotek_hash' );
172
+
173
+ if ( isset( $document->desc_array['lingotek']['translations'] ) && count( $document->desc_array['lingotek']['translations'] ) > 0 ) {
174
+ foreach ( $document->desc_array['lingotek']['translations'] as $target_locale => $target_status ) {
175
+ $document->desc_array['lingotek']['translations'][ $target_locale ] = 'archived';
176
+ }
177
+ }
178
+ $document->desc_array['lingotek']['document_id'] = 'archived_' . $document->document_id;
179
+ $document->desc_array['lingotek']['status'] = 'archived';
180
+
181
  $document->save();
182
+ }
183
+
184
+ public function handleDocumentDeleted( $document ) {
185
+ $callback_parameters = array(
186
+ // As this comes from Lingotek callback we cannot have a nonce.
187
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
188
+ 'Document ID' => isset( $_GET['document_id'] ) ? sanitize_key( $_GET['document_id'] ) : null,
189
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
190
+ 'Type' => isset( $_GET['type'] ) ? sanitize_key( $_GET['type'] ) : null,
191
+ );
192
+ Lingotek_Logger::info( 'Callback received', array( 'Callback Parameters' => $callback_parameters ) );
193
+
194
+ if ( isset( $document->desc_array['lingotek']['translations'] ) && count( $document->desc_array['lingotek']['translations'] ) > 0 ) {
195
+ foreach ( $document->desc_array['lingotek']['translations'] as $target_locale => $target_status ) {
196
+ $document->desc_array['lingotek']['translations'][ $target_locale ] = 'deleted';
197
+ }
198
+ }
199
+
200
+ // The removal of the lingotek hash term is needed as it could prevent further uploads.
201
+ $wp_id = isset( $document->desc_array['lingotek']['source'] ) ? $document->desc_array['lingotek']['source'] : null;
202
  wp_remove_object_terms( $wp_id, "lingotek_hash_{$wp_id}", 'lingotek_hash' );
203
+
204
+ $document->desc_array['lingotek']['document_id'] = 'deleted_' . $document->document_id;
205
+ $document->desc_array['lingotek']['status'] = 'deleted';
206
+ $document->save();
207
  }
208
 
209
  public function handleDocumentCancelled( $document ) {
212
  'Type' => isset( $_GET['type'] ) ? $_GET['type'] : null,
213
  );
214
  if ( isset( $document->desc_array['lingotek']['translations'] ) && count( $document->desc_array['lingotek']['translations'] ) > 0 ) {
215
+ foreach ( $document->desc_array['lingotek']['translations'] as $target_locale => $target_status ) {
216
+ $document->desc_array['lingotek']['translations'][ $target_locale ] = 'cancelled';
217
+ }
 
218
  }
219
+ $wp_id = isset( $document->desc_array['lingotek']['source'] ) ? $document->desc_array['lingotek']['source'] : null;
 
 
220
  wp_remove_object_terms( $wp_id, "lingotek_hash_{$wp_id}", 'lingotek_hash' );
221
+
222
+ $document->desc_array['lingotek']['document_id'] = 'cancelled_' . $document->document_id;
223
+ $document->desc_array['lingotek']['status'] = 'cancelled';
224
+ $document->save();
225
  Lingotek_Logger::info( 'Callback Received', array( 'Callback Parameters' => $callback_parameters ) );
226
  }
227
 
240
  $document->update_translation_status( $locale, 'cancelled' );
241
  Lingotek_Logger::info( 'Callback received', array( 'Callback Parameters' => $callback_parameters ) );
242
  }
243
+
244
+ public function handleTargetDeleted( $document ) {
245
+ $callback_parameters = array(
246
+ // As this comes from Lingotek callback we cannot have a nonce.
247
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
248
+ 'Document ID' => isset( $_GET['document_id'] ) ? sanitize_key( $_GET['document_id'] ) : null,
249
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
250
+ 'Type' => isset( $_GET['type'] ) ? sanitize_key( $_GET['type'] ) : null,
251
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
252
+ 'Locale' => isset( $_GET['locale'] ) ? sanitize_lingotek_locale( $_GET['locale'] ) : null,
253
+ );
254
+ // Map to WordPress locale.
255
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended,
256
+ $locale = Lingotek::map_to_wp_locale( sanitize_lingotek_locale( $_GET['locale'] ) );
257
+ if ( ! isset( $document->desc_array['lingotek']['translations'][ $locale ] ) ) {
258
+ $document->desc_array['lingotek']['translations'][] = $locale;
259
+ $document->save();
260
+ }
261
+ $document->update_translation_status( $locale, 'deleted' );
262
+ Lingotek_Logger::info( 'Callback received', array( 'Callback Parameters' => $callback_parameters ) );
263
+ }
264
+
265
  }
include/group.php CHANGED
@@ -182,10 +182,11 @@ abstract class Lingotek_Group {
182
  *
183
  * @param bool $cancel whether to cancel the Lingotek document or not
184
  */
185
- public function cancel_translation( $language, $id ) {
186
  $client = new Lingotek_API();
187
- if ( $client->cancel_translation( $this->document_id, $language->lingotek_locale, $id ) ) {
188
- $this->desc_array['lingotek']['translations'][ $language->locale ] = 'cancelled';
 
189
  $this->save();
190
  } else {
191
  update_option( 'cancel_target_failed', true );
@@ -210,6 +211,7 @@ abstract class Lingotek_Group {
210
  $this->translations = isset( $this->translations ) ? array_fill_keys( array_keys( $this->translations ), 'pending' ) : null;
211
  $this->save();
212
  }
 
213
  }
214
 
215
  /**
@@ -256,7 +258,8 @@ abstract class Lingotek_Group {
256
  }
257
  $args = $workflow ? array( 'workflow_id' => $workflow ) : array();
258
 
259
- if ( ! $this->is_disabled_target( $language ) && empty( $this->translations[ $language->locale ] ) ) {
 
260
  // don't change translations to pending if the api call failed
261
  if ( $client->request_translation( $this->document_id, $language->locale, $args, $this->source ) ) {
262
  $this->status = 'current';
@@ -366,6 +369,17 @@ abstract class Lingotek_Group {
366
  $this->translations[ $wp_locale ] = 'ready';
367
  }
368
  }
 
 
 
 
 
 
 
 
 
 
 
369
 
370
  $this->save();
371
  }
182
  *
183
  * @param bool $cancel whether to cancel the Lingotek document or not
184
  */
185
+ public function cancel_translation( $id, $target_locale ) {
186
  $client = new Lingotek_API();
187
+ if ( $client->cancel_translation( $this->document_id, $target_locale ) ) {
188
+ $wp_locale = Lingotek::map_to_wp_locale( $target_locale );
189
+ $this->desc_array['lingotek']['translations'][ $wp_locale ] = 'cancelled';
190
  $this->save();
191
  } else {
192
  update_option( 'cancel_target_failed', true );
211
  $this->translations = isset( $this->translations ) ? array_fill_keys( array_keys( $this->translations ), 'pending' ) : null;
212
  $this->save();
213
  }
214
+ return $res;
215
  }
216
 
217
  /**
258
  }
259
  $args = $workflow ? array( 'workflow_id' => $workflow ) : array();
260
 
261
+ if ( ! $this->is_disabled_target( $language ) &&
262
+ ( empty( $this->translations[ $language->locale ] ) || 'deleted' === $this->translations[ $language->locale ] ) ) {
263
  // don't change translations to pending if the api call failed
264
  if ( $client->request_translation( $this->document_id, $language->locale, $args, $this->source ) ) {
265
  $this->status = 'current';
369
  $this->translations[ $wp_locale ] = 'ready';
370
  }
371
  }
372
+ // If there were any cancelled or deleted targets that we didn't update properly,
373
+ // we didn't get anything, but locally they would keep that status, let's update those here.
374
+ $pll_locale_to_lingotek_locale = array_flip( $lingotek_locale_to_pll_locale );
375
+ foreach ( $this->translations as $target_locale => $target_status ) {
376
+ if ( ! isset( $translations[ $pll_locale_to_lingotek_locale[ $target_locale ] ] ) ) {
377
+ if ( ! in_array( $this->translations[ $target_locale ], array( 'deleted', 'cancelled' ) ) ) {
378
+ // Mark is a deleted.
379
+ $this->translations[ $target_locale ] = 'deleted';
380
+ }
381
+ }
382
+ }
383
 
384
  $this->save();
385
  }
include/model.php CHANGED
@@ -391,18 +391,20 @@ class Lingotek_Model {
391
  // Returns type string or boolean
392
  $document_id = self::get_document_id( 'post', $post_id );
393
  // Slug can't be empty or duplicated, so we prefix `disassociated` to show the document id is no longer associated with this post
394
- if ( is_string( $document_id ) && strpos( $document_id, 'disassociated_' ) === 0 ) {
395
  $document_id = false;
396
  }
397
  $content = null;
398
  // If we already uploaded this doc, check if it changed and prevent the upload if it didn't.
 
399
  if ( $document_id ) {
400
  $content = Lingotek_Group_Post::get_content( $post );
401
  $hash_terms = wp_get_object_terms( $post->ID, 'lingotek_hash' );
402
  $hash_term = array_pop( $hash_terms );
403
  $new_hash = md5( $content );
404
  if ( ! empty( $hash_term ) ) {
405
- if ( $hash_term->description == $new_hash ) {
 
406
  return;
407
  }
408
  }
@@ -414,7 +416,6 @@ class Lingotek_Model {
414
  $document->pre_upload_to_lingotek( $post_id, $post->post_type, $language, 'post' );
415
  }
416
 
417
- $client = new Lingotek_API();
418
  $external_url = get_page_link( $post_id );
419
  if ( ! $content ) {
420
  $content = Lingotek_Group_Post::get_content( $post );
@@ -431,12 +432,12 @@ class Lingotek_Model {
431
  $hash_terms = wp_set_post_terms( $post->ID, 'lingotek_hash_' . $post->ID, 'lingotek_hash' );
432
  $hash_term_id = array_pop( $hash_terms );
433
  } else {
434
- $hash_term_id = $hash_term->id;
435
  }
436
  wp_update_term( $hash_term_id, 'lingotek_hash', array( 'description' => $new_hash ) );
437
  }
438
  return $response;
439
- } elseif ( ! Lingotek_Group::$creating_translation && ! self::$copying_post && ( ! $document || $document->status === 'cancelled' ) ) {
440
  $document_id = $client->upload_document( $params, $post->ID );
441
  if ( $document_id ) {
442
  Lingotek_Group_Post::create( $post->ID, $language, $document_id );
@@ -675,7 +676,7 @@ class Lingotek_Model {
675
  $client->cancel_document( $document->document_id, $object_id );
676
  } else {
677
  $lang = PLL()->model->term->get_language( $object_id );
678
- PLL()->model->term->cancel_translation( $object_id );
679
  PLL()->model->term->delete_translation( $object_id );
680
  $client->cancel_translation( $document->document_id, $lang->lingotek_locale, $object_id );
681
  }
@@ -1028,6 +1029,36 @@ class Lingotek_Model {
1028
  return $params;
1029
  }
1030
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  private function get_filter_ids( $type, $language, $wp_id ) {
1032
  $filter_ids = array();
1033
  if ( self::get_profile_option( 'primary_filter_id', $type, $language, false, $wp_id ) ) {
@@ -1038,4 +1069,18 @@ class Lingotek_Model {
1038
  }
1039
  return $filter_ids;
1040
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041
  }
391
  // Returns type string or boolean
392
  $document_id = self::get_document_id( 'post', $post_id );
393
  // Slug can't be empty or duplicated, so we prefix `disassociated` to show the document id is no longer associated with this post
394
+ if ( is_string( $document_id ) && $this->is_disassociated_deleted_cancelled_or_archived( $document_id ) ) {
395
  $document_id = false;
396
  }
397
  $content = null;
398
  // If we already uploaded this doc, check if it changed and prevent the upload if it didn't.
399
+ $client = new Lingotek_API();
400
  if ( $document_id ) {
401
  $content = Lingotek_Group_Post::get_content( $post );
402
  $hash_terms = wp_get_object_terms( $post->ID, 'lingotek_hash' );
403
  $hash_term = array_pop( $hash_terms );
404
  $new_hash = md5( $content );
405
  if ( ! empty( $hash_term ) ) {
406
+ // If the document hasn't changed and it exists in TMS, don't upload
407
+ if ( $hash_term->description == $new_hash && $client->get_document_status( $document_id ) ) {
408
  return;
409
  }
410
  }
416
  $document->pre_upload_to_lingotek( $post_id, $post->post_type, $language, 'post' );
417
  }
418
 
 
419
  $external_url = get_page_link( $post_id );
420
  if ( ! $content ) {
421
  $content = Lingotek_Group_Post::get_content( $post );
432
  $hash_terms = wp_set_post_terms( $post->ID, 'lingotek_hash_' . $post->ID, 'lingotek_hash' );
433
  $hash_term_id = array_pop( $hash_terms );
434
  } else {
435
+ $hash_term_id = $hash_term->term_id;
436
  }
437
  wp_update_term( $hash_term_id, 'lingotek_hash', array( 'description' => $new_hash ) );
438
  }
439
  return $response;
440
+ } elseif ( ! Lingotek_Group::$creating_translation && ! self::$copying_post && ( ! $document || in_array( $document->status, array( 'deleted', 'disassociated', 'archived', 'cancelled', null ), true ) ) ) {
441
  $document_id = $client->upload_document( $params, $post->ID );
442
  if ( $document_id ) {
443
  Lingotek_Group_Post::create( $post->ID, $language, $document_id );
676
  $client->cancel_document( $document->document_id, $object_id );
677
  } else {
678
  $lang = PLL()->model->term->get_language( $object_id );
679
+ PLL()->model->term->cancel_translation( $document->document_id, $lang->lingotek_locale );
680
  PLL()->model->term->delete_translation( $object_id );
681
  $client->cancel_translation( $document->document_id, $lang->lingotek_locale, $object_id );
682
  }
1029
  return $params;
1030
  }
1031
 
1032
+ /**
1033
+ * Builds the array of parameters for reuploads by getting only the post id.
1034
+ * Primarily for when handling 410 and 404 response codes
1035
+ * Return empty array if profile is disabled, or post/language are empty
1036
+ *
1037
+ * @since 1.5.0
1038
+ *
1039
+ * @param integer $post_id
1040
+ * @return array
1041
+ */
1042
+ public function reupload_build_params( $post_id ) {
1043
+ $post = get_post( $post_id );
1044
+ $language = PLL()->model->post->get_language( $post_id );
1045
+ $profile = self::get_profile( $post->post_type, $language, $post_id );
1046
+ if ( 'disabled' === $profile['profile'] || empty( $post ) || empty( $language ) ) {
1047
+ Lingotek_Logger::info(
1048
+ 'Document cannot be uploaded',
1049
+ array(
1050
+ 'profile' => $profile['profile'],
1051
+ 'post is empty' => empty( $post ),
1052
+ 'language is empty' => empty( $language ),
1053
+ )
1054
+ );
1055
+ return array();
1056
+ }
1057
+ $external_url = get_page_link( $post_id );
1058
+ $content = Lingotek_Group_Post::get_content( $post );
1059
+ return $this->build_params( $external_url, $post->post_title, $post->post_type, $content, $language, $profile, $post_id );
1060
+ }
1061
+
1062
  private function get_filter_ids( $type, $language, $wp_id ) {
1063
  $filter_ids = array();
1064
  if ( self::get_profile_option( 'primary_filter_id', $type, $language, false, $wp_id ) ) {
1069
  }
1070
  return $filter_ids;
1071
  }
1072
+
1073
+ /**
1074
+ * Checks if a document id is disassociated, cancelled or archived.
1075
+ *
1076
+ * @param string $document_id
1077
+ *
1078
+ * @return bool
1079
+ */
1080
+ protected function is_disassociated_deleted_cancelled_or_archived( $document_id ) {
1081
+ return strpos( $document_id, 'disassociated_' ) === 0 ||
1082
+ strpos( $document_id, 'deleted_' ) === 0 ||
1083
+ strpos( $document_id, 'cancelled_' ) === 0 ||
1084
+ strpos( $document_id, 'archived_' ) === 0;
1085
+ }
1086
  }
js/actions.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @file
3
+ * Lingotek target actions JS code for lingotek target actions button.
4
+ */
5
+ jQuery( document ).ready(
6
+ function($) {
7
+ 'use strict';
8
+ var $actionsElement = $( document ).find( '.lingotek-target-dropdown,.lingotek-source-dropdown' );
9
+ // Attach event handlers to toggle button.
10
+ $actionsElement.each(
11
+ function () {
12
+ var $this = $( this );
13
+ var $toggle = $this.find( '.lingotek-target-dropdown-toggle,.lingotek-source-dropdown-toggle' );
14
+ $toggle.on(
15
+ 'click',
16
+ function (e) {
17
+ e.preventDefault();
18
+ $this.toggleClass( 'open' );
19
+ }
20
+ );
21
+ $this.on(
22
+ 'focusout',
23
+ function (e) {
24
+ setTimeout(
25
+ function () {
26
+ if ($this.has( document.activeElement ).length == 0) {
27
+ // The focus left the action button group, hide actions.
28
+ $this.removeClass( 'open' );
29
+ }
30
+ },
31
+ 1
32
+ );
33
+ }
34
+ );
35
+ }
36
+ );
37
+ }
38
+ );
js/progress.js CHANGED
@@ -10,6 +10,9 @@ jQuery(document).ready(function($) {
10
  if (lingotek_data.locales){
11
  data.locales = JSON.stringify(lingotek_data.locales);
12
  }
 
 
 
13
  $.post(ajaxurl, data , function(response) {
14
  $("#lingotek-progressbar").progressbar({
15
  value: ++i / lingotek_data.ids.length * 100
10
  if (lingotek_data.locales){
11
  data.locales = JSON.stringify(lingotek_data.locales);
12
  }
13
+ if (lingotek_data.target_locale){
14
+ data.target_locale = lingotek_data.target_locale;
15
+ }
16
  $.post(ajaxurl, data , function(response) {
17
  $("#lingotek-progressbar").progressbar({
18
  value: ++i / lingotek_data.ids.length * 100
js/updater.js CHANGED
@@ -2,7 +2,7 @@ jQuery(document).ready(function($) {
2
  $('.edit-php a.submitdelete, .post-php a.submitdelete').click( function( event ) {
3
  if( ! confirm( 'Content that is put into the trash will be cancelled in your Lingotek community. Are you sure you want to proceed?' ) ) {
4
  event.preventDefault();
5
- }
6
  });
7
  var current_ids = {};
8
  var post_data = {"check_ids" : current_ids};
@@ -108,6 +108,14 @@ 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 'cancelled':
112
  if (locale === data[key]['source']){
113
  updateUploadBulkLink(tr, data, key, 'upload' , 'Upload cancelled item to Lingotek TMS', 'Upload to Lingotek');
2
  $('.edit-php a.submitdelete, .post-php a.submitdelete').click( function( event ) {
3
  if( ! confirm( 'Content that is put into the trash will be cancelled in your Lingotek community. Are you sure you want to proceed?' ) ) {
4
  event.preventDefault();
5
+ }
6
  });
7
  var current_ids = {};
8
  var post_data = {"check_ids" : current_ids};
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');
114
+ updateIndicator(td, data, key, locale, 'deleted', 'Upload deleted item', 'remove');
115
+ } else {
116
+ updateIndicator(td, data, key, locale, 'deleted', 'Target is deleted', 'remove');
117
+ }
118
+ break;
119
  case 'cancelled':
120
  if (locale === data[key]['source']){
121
  updateUploadBulkLink(tr, data, key, 'upload' , 'Upload cancelled item to Lingotek TMS', 'Upload to Lingotek');
lingotek.php CHANGED
@@ -2,13 +2,15 @@
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.4.16
6
  Author: Lingotek and Frédéric Demarle
7
  Author uri: http://lingotek.com
8
  Description: Lingotek offers convenient cloud-based localization and translation.
9
  Text Domain: lingotek-translation
10
  Domain Path: /languages
11
  GitHub Plugin URI: https://github.com/lingotek/lingotek-translation
 
 
12
  */
13
 
14
  // don't access directly.
@@ -17,7 +19,7 @@ if ( ! function_exists( 'add_action' ) ) {
17
  }
18
 
19
  // Plugin version (should match above meta).
20
- define( 'LINGOTEK_VERSION', '1.4.16' );
21
  define( 'LINGOTEK_MIN_PLL_VERSION', '1.8' );
22
  // Plugin name as known by WordPress.
23
  define( 'LINGOTEK_BASENAME', plugin_basename( __FILE__ ) );
@@ -311,24 +313,61 @@ class Lingotek {
311
  }
312
 
313
  add_action( 'plugins_loaded', array( &$this, 'lingotek_plugin_migration' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  }
315
 
316
  public function lingotek_plugin_migration() {
317
- $updatesHaveRun = false;
318
  if ( $this->needs_to_run_updates( '1.4.11' ) ) {
319
  // Add the updates introduced in 1.4.11.
320
  $this->update_1_4_11_for_removing_custom_fields_from_postTypes_settings();
321
  $this->update_1_4_11_for_removing_custom_fields_autoload();
322
  $this->update_1_4_11_for_removing_community_resources_autoload();
323
- $updatesHaveRun = true;
324
  }
325
  if ( $this->needs_to_run_updates( '1.4.13' ) ) {
326
  // Add the updates introduced in 1.4.13.
327
  $this->update_1_4_13_for_disabling_autoload_for_all_wpoptions();
328
- $updatesHaveRun = true;
329
  }
330
  // If any update happened, inform the user and update the version.
331
- if ( $updatesHaveRun ) {
332
  update_option( 'lingotek_plugin_version', LINGOTEK_VERSION, false );
333
  add_action(
334
  'admin_notices',
@@ -347,18 +386,18 @@ class Lingotek {
347
  * It will check that the installed version is newer than the db one, even if we
348
  * had skipped any version, and ensures we don't run any update if we already did.
349
  *
350
- * @param $versionUpdates
351
  * The version where these updates were introduced.
352
  * @return bool
353
  * true if needs to run, false otherwise
354
  *
355
  * @since 1.4.11
356
  */
357
- public function needs_to_run_updates( $versionUpdates ) {
358
  $db_version = get_option( 'lingotek_plugin_version' );
359
  return ( $db_version !== false &&
360
- version_compare( LINGOTEK_VERSION, $versionUpdates, '>=' ) &&
361
- version_compare( $versionUpdates, $db_version, '>' ) );
362
  }
363
 
364
  /**
@@ -368,9 +407,9 @@ class Lingotek {
368
  * @since 1.4.11
369
  */
370
  public function update_1_4_11_for_removing_custom_fields_from_postTypes_settings() {
371
- $settings = get_option( 'lingotek_content_type' );
372
- foreach ( $settings as $postType => &$postTypeSettings ) {
373
- unset( $postTypeSettings['fields']['metas'] );
374
  }
375
  update_option( 'lingotek_content_type', $settings, false );
376
  }
@@ -697,6 +736,10 @@ class Lingotek {
697
  }
698
 
699
  wp_enqueue_style( 'lingotek_admin', LINGOTEK_URL . '/css/admin.css', array(), LINGOTEK_VERSION );
 
 
 
 
700
  }
701
 
702
  /**
@@ -971,4 +1014,5 @@ class Lingotek {
971
  }
972
  }
973
 
 
974
  $GLOBALS['wp_lingotek'] = Lingotek::get_instance();
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.0
6
  Author: Lingotek and Frédéric Demarle
7
  Author uri: http://lingotek.com
8
  Description: Lingotek offers convenient cloud-based localization and translation.
9
  Text Domain: lingotek-translation
10
  Domain Path: /languages
11
  GitHub Plugin URI: https://github.com/lingotek/lingotek-translation
12
+ *
13
+ * @package Lingotek
14
  */
15
 
16
  // don't access directly.
19
  }
20
 
21
  // Plugin version (should match above meta).
22
+ define( 'LINGOTEK_VERSION', '1.5.0' );
23
  define( 'LINGOTEK_MIN_PLL_VERSION', '1.8' );
24
  // Plugin name as known by WordPress.
25
  define( 'LINGOTEK_BASENAME', plugin_basename( __FILE__ ) );
313
  }
314
 
315
  add_action( 'plugins_loaded', array( &$this, 'lingotek_plugin_migration' ) );
316
+
317
+ /**
318
+ * We temporarely inform of the new UI. We might want to remove this after some releases.
319
+ *
320
+ * @since 1.15.0
321
+ */
322
+ add_action( 'init', array( &$this, 'lingotek_new_ui' ), 10, 2 );
323
+ }
324
+
325
+ /**
326
+ * Show the bubble announcing the new UI.
327
+ *
328
+ * @since 1.15.0
329
+ */
330
+ public function lingotek_new_ui() {
331
+ $content = __( 'Lingotek new UI is ready! If you still need the Polylang based columns, customize those in "Screen Options"', 'lingotek-translation' );
332
+
333
+ $buttons = array(
334
+ array(
335
+ 'label' => __( 'Close' ),
336
+ ),
337
+ );
338
+
339
+ $args = array(
340
+ 'pointer' => 'lingotek-new-ui',
341
+ 'id' => 'lingotek_targets',
342
+ 'position' => array(
343
+ 'edge' => 'top',
344
+ 'align' => 'left',
345
+ ),
346
+ 'width' => 380,
347
+ 'title' => __( 'Lingotek New UI', 'lingotek-translation' ),
348
+ 'content' => $content,
349
+ 'buttons' => $buttons,
350
+ );
351
+
352
+ new Lingotek_Pointer( $args );
353
  }
354
 
355
  public function lingotek_plugin_migration() {
356
+ $updates_have_run = false;
357
  if ( $this->needs_to_run_updates( '1.4.11' ) ) {
358
  // Add the updates introduced in 1.4.11.
359
  $this->update_1_4_11_for_removing_custom_fields_from_postTypes_settings();
360
  $this->update_1_4_11_for_removing_custom_fields_autoload();
361
  $this->update_1_4_11_for_removing_community_resources_autoload();
362
+ $updates_have_run = true;
363
  }
364
  if ( $this->needs_to_run_updates( '1.4.13' ) ) {
365
  // Add the updates introduced in 1.4.13.
366
  $this->update_1_4_13_for_disabling_autoload_for_all_wpoptions();
367
+ $updates_have_run = true;
368
  }
369
  // If any update happened, inform the user and update the version.
370
+ if ( $updates_have_run ) {
371
  update_option( 'lingotek_plugin_version', LINGOTEK_VERSION, false );
372
  add_action(
373
  'admin_notices',
386
  * It will check that the installed version is newer than the db one, even if we
387
  * had skipped any version, and ensures we don't run any update if we already did.
388
  *
389
+ * @param $version_updates
390
  * The version where these updates were introduced.
391
  * @return bool
392
  * true if needs to run, false otherwise
393
  *
394
  * @since 1.4.11
395
  */
396
+ public function needs_to_run_updates( $version_updates ) {
397
  $db_version = get_option( 'lingotek_plugin_version' );
398
  return ( $db_version !== false &&
399
+ version_compare( LINGOTEK_VERSION, $version_updates, '>=' ) &&
400
+ version_compare( $version_updates, $db_version, '>' ) );
401
  }
402
 
403
  /**
407
  * @since 1.4.11
408
  */
409
  public function update_1_4_11_for_removing_custom_fields_from_postTypes_settings() {
410
+ $settings = get_option( 'lingotek_content_type', array() );
411
+ foreach ( $settings as $enabled_post_type => &$enabled_post_type_settings ) {
412
+ unset( $enabled_post_type_settings['fields']['metas'] );
413
  }
414
  update_option( 'lingotek_content_type', $settings, false );
415
  }
736
  }
737
 
738
  wp_enqueue_style( 'lingotek_admin', LINGOTEK_URL . '/css/admin.css', array(), LINGOTEK_VERSION );
739
+ wp_enqueue_style( 'lingotek_statuses', LINGOTEK_URL . '/css/statuses.css', array(), LINGOTEK_VERSION );
740
+
741
+ wp_enqueue_script( 'lingotek_actions', LINGOTEK_URL . '/js/actions.js', array( 'jquery' ), LINGOTEK_VERSION, true );
742
+ wp_enqueue_style( 'lingotek_actions', LINGOTEK_URL . '/css/actions.css', array(), LINGOTEK_VERSION );
743
  }
744
 
745
  /**
1014
  }
1015
  }
1016
 
1017
+ require_once 'sanitization.php';
1018
  $GLOBALS['wp_lingotek'] = Lingotek::get_instance();
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.4.16
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -122,6 +122,14 @@ For more, visit the [Lingotek documentation site](https://lingotek.atlassian.net
122
  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.
123
 
124
  == Changelog ==
 
 
 
 
 
 
 
 
125
  = 1.4.16 (2021-07-22) =
126
  * Fixed small whitespace bug
127
 
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.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
122
  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.
123
 
124
  == Changelog ==
125
+ = 1.5.0 (2021-08-05) =
126
+ * New UI
127
+ - The source and translation icons have been replaced with chips. This makes it easier to view which stage the translations are in.
128
+ - Added dropdown actions to the source and target chips.
129
+ - Added Cancel option for specific targets in the action dropdown.
130
+ *New Gone status
131
+ - This will give you a visual indicator when a document or target has been archived or deleted.
132
+
133
  = 1.4.16 (2021-07-22) =
134
  * Fixed small whitespace bug
135
 
sanitization.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Collection of functions that needs to be available everywhere in Lingotek Translation plugin.
4
+ *
5
+ * @package Lingotek
6
+ */
7
+
8
+ /**
9
+ * Sanitizes a Lingotek locale.
10
+ *
11
+ * Lower and uppercase alphanumeric characters, dashes, and underscores are allowed.
12
+ *
13
+ * @since 1.4.14
14
+ *
15
+ * @param string $locale String locale.
16
+ * @return string Sanitized locale.
17
+ */
18
+ // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
19
+ function sanitize_lingotek_locale( $locale ) {
20
+ $raw_locale = $locale;
21
+ $locale = preg_replace( '/[^a-zA-Z0-9_\-]/', '', $locale );
22
+
23
+ /**
24
+ * Filters a sanitized key string.
25
+ *
26
+ * @since 1.4.14
27
+ *
28
+ * @param string $key Sanitized key.
29
+ * @param string $raw_key The key prior to sanitization.
30
+ */
31
+ // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
32
+ return apply_filters( 'sanitize_lingotek_locale', $locale, $raw_locale );
33
+ }
tests/test-action-url.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class ActionUrlTest
4
+ *
5
+ * @package Lingotek_Translation
6
+ */
7
+
8
+ /**
9
+ * Sample test case.
10
+ */
11
+ class ActionUrlTest extends WP_UnitTestCase {
12
+
13
+ /**
14
+ * Tests the rendering of an action url.
15
+ *
16
+ * @test
17
+ * @dataProvider actionsDataProvider
18
+ *
19
+ * @param string $url The url.
20
+ * @param string $title The title.
21
+ * @param string $expected The expected html.
22
+ */
23
+ public function SecondaryActionIsRendered( $url, $title, $expected ) {
24
+ $action = new Lingotek_Action_Url( $url, $title );
25
+ $this->assertSame( $url, $action->getUri() );
26
+ $this->assertSame( $title, $action->getTitle() );
27
+ $this->assertXmlStringEqualsXmlString( $expected, $action->render() );
28
+ }
29
+
30
+ /**
31
+ * Data provider for actions.
32
+ *
33
+ * @see ::SecondaryActionIsRendered
34
+ */
35
+ public function actionsDataProvider() {
36
+ yield 'secondary action' => array(
37
+ 'https://www.lingotek.com',
38
+ 'Lingotek website',
39
+ '<li><a href="https://www.lingotek.com">Lingotek website</a></li>',
40
+ );
41
+ }
42
+
43
+ }