Polylang - Version 2.6

Version Description

(2019-06-26) =

  • Pro: Remove all languages files. All translations are now maintained on TranslationsPress
  • Pro: Move the languages metabox to a block editor plugin
  • Pro: Better management of user capabilities when synchronizing posts
  • Pro: Separate REST requests from the frontend
  • Pro: Copy the post slug when duplicating a post
  • Pro: Duplicate ACF term metas when terms are automatically duplicated when creating a new post translation
  • Pro: Fix hierarchy lost when duplicating terms
  • Pro: Fix page shared slugs with special characters
  • Pro: Fix synchronized posts sharing their slug when the language is set from the content
  • Pro: Fix PHP warning with ACF Pro 5.8.1
  • Pro: Fix ACF clone fields not translated in repeaters
  • Better management of user capablities when synchronizing taxonomies terms and custom fields
  • Extend string translations search to translated strings #207
  • Update plugin updater to 1.6.18
  • Honor the filter pll_flag when performing the flag validation when creating a new language
  • Modify the title and the label for the language switcher menu items #307
  • Add support for international domain names
  • Add a title to the link icon used to add a translation #325
  • Add a notice when a static front page is not translated in a language
  • Add support for custom term fields in wpml-config.xml
  • Add filter pll_admin_languages_filter for the list of items the admin bar language filter
  • Add compatibility with WP Offload Media Lite. Props Daniel Berkman
  • Yoast SEO: Add post type archive url in all languages to the sitemap
  • Fix www. not redirected to not www. for the home page in multiple domains #311
  • Fix cropped images not being synchronized
  • Fix auto added page to menus when the page is created with the block editor
  • Fix embed of translated static front page #318
  • Fix a possible infinite redirect if the static front page is not translated
  • Fix incorrect behavior of action 'wpml_register_single_string' when updating the string source
  • Fix fatal error with Jetpack when no languages has been defined yet #330
  • Fix a conflict with Laravel Valet. Props @chesio. #250
  • Fix a conflict with Thesis.
  • Fix a conflict with Pods in the block editor. Props Jory Hogeveen. #369
  • Fix fatal error with Twenty Fourteen introduced in version 2.5.4. #374
Download this release

Release Info

Developer Chouby
Plugin Icon 128x128 Polylang
Version 2.6
Comparing to
See all releases

Code changes from version 2.5.4 to 2.6

Files changed (110) hide show
  1. admin/admin-base.php +77 -37
  2. admin/admin-block-editor.php +14 -4
  3. admin/admin-classic-editor.php +91 -61
  4. admin/admin-filters-columns.php +53 -24
  5. admin/admin-filters-media.php +18 -16
  6. admin/admin-filters-post.php +6 -6
  7. admin/admin-filters-term.php +108 -70
  8. admin/admin-filters.php +24 -19
  9. admin/admin-links.php +69 -30
  10. admin/admin-model.php +33 -24
  11. admin/admin-nav-menu.php +9 -76
  12. admin/admin-notices.php +5 -4
  13. admin/admin-static-pages.php +43 -2
  14. admin/admin-strings.php +8 -8
  15. admin/admin.php +70 -1
  16. admin/view-translations-media.php +8 -10
  17. admin/view-translations-post.php +10 -10
  18. admin/view-translations-term.php +8 -8
  19. css/admin.css +39 -0
  20. css/admin.min.css +1 -1
  21. frontend/choose-lang-content.php +1 -1
  22. frontend/choose-lang-url.php +10 -8
  23. frontend/choose-lang.php +20 -13
  24. frontend/frontend-auto-translate.php +1 -1
  25. frontend/frontend-filters-links.php +8 -7
  26. frontend/frontend-filters-search.php +3 -3
  27. frontend/frontend-filters.php +3 -3
  28. frontend/frontend-links.php +3 -3
  29. frontend/frontend-nav-menu.php +13 -16
  30. frontend/frontend-static-pages.php +11 -6
  31. frontend/frontend.php +36 -39
  32. include/api.php +5 -4
  33. include/base.php +40 -8
  34. include/class-polylang.php +76 -30
  35. include/crud-posts.php +4 -4
  36. include/crud-terms.php +1 -1
  37. include/filters.php +1 -1
  38. include/functions.php +68 -0
  39. include/language.php +33 -13
  40. include/links-abstract-domain.php +5 -1
  41. include/links-default.php +1 -1
  42. include/links-directory.php +5 -6
  43. include/links-domain.php +11 -3
  44. include/links-model.php +1 -1
  45. include/links-subdomain.php +1 -1
  46. include/links.php +0 -29
  47. include/model.php +26 -18
  48. include/nav-menu.php +67 -0
  49. include/olt-manager.php +1 -1
  50. include/pointer.php +2 -2
  51. include/query.php +1 -1
  52. include/rest-request.php +73 -0
  53. include/static-pages.php +27 -6
  54. include/switcher.php +4 -4
  55. include/translated-object.php +45 -7
  56. include/translated-post.php +91 -1
  57. include/translated-term.php +7 -4
  58. include/walker-dropdown.php +2 -2
  59. include/widget-languages.php +16 -19
  60. install/install-base.php +1 -1
  61. install/install.php +1 -1
  62. install/plugin-updater.php +89 -17
  63. install/t15s.php +169 -0
  64. install/upgrade.php +11 -11
  65. js/block-editor.js +93 -1
  66. js/block-editor.min.js +1 -1
  67. js/classic-editor.js +25 -0
  68. js/classic-editor.min.js +1 -1
  69. js/media.js +0 -6
  70. js/media.min.js +0 -1
  71. js/post.js +75 -5
  72. js/post.min.js +1 -1
  73. lingotek/lingotek.php +9 -15
  74. modules/plugins/as3cf.php +64 -0
  75. modules/plugins/cache-compat.php +2 -2
  76. modules/plugins/featured-content.php +19 -16
  77. modules/plugins/jetpack.php +2 -2
  78. modules/plugins/plugins-compat.php +27 -14
  79. modules/plugins/wp-import.php +4 -4
  80. modules/plugins/wpseo.php +45 -35
  81. modules/share-slug/settings-share-slug.php +2 -2
  82. modules/sync/admin-sync.php +17 -9
  83. modules/sync/settings-sync.php +1 -1
  84. modules/sync/sync-metas.php +44 -14
  85. modules/sync/sync-post-metas.php +4 -2
  86. modules/sync/sync-tax.php +48 -18
  87. modules/sync/sync.php +69 -18
  88. modules/wpml/settings-wpml.php +2 -2
  89. modules/wpml/wpml-api.php +1 -1
  90. modules/wpml/wpml-compat.php +19 -4
  91. modules/wpml/wpml-config.php +25 -1
  92. modules/wpml/wpml-legacy-api.php +6 -5
  93. polylang.php +13 -14
  94. readme.txt +40 -3
  95. settings/flags.php +2 -2
  96. settings/languages.php +1 -1
  97. settings/settings-browser.php +2 -2
  98. settings/settings-cpt.php +5 -5
  99. settings/settings-licenses.php +15 -9
  100. settings/settings-media.php +1 -1
  101. settings/settings-module.php +7 -7
  102. settings/settings-tools.php +2 -2
  103. settings/settings-url.php +17 -16
  104. settings/settings.php +50 -21
  105. settings/table-languages.php +2 -2
  106. settings/table-settings.php +5 -5
  107. settings/table-string.php +86 -30
  108. settings/view-about.php +1 -12
  109. settings/view-tab-lang.php +8 -8
  110. uninstall.php +15 -3
admin/admin-base.php CHANGED
@@ -31,9 +31,11 @@ class PLL_Admin_Base extends PLL_Base {
31
 
32
  add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) );
33
 
34
- // Lingotek
35
- if ( ! defined( 'POLYLANG_PRO' ) && ( ! defined( 'PLL_LINGOTEK_AD' ) || PLL_LINGOTEK_AD ) ) {
36
- require_once POLYLANG_DIR . '/lingotek/lingotek.php';
 
 
37
  }
38
  }
39
 
@@ -44,6 +46,8 @@ class PLL_Admin_Base extends PLL_Base {
44
  * @since 1.2
45
  */
46
  public function init() {
 
 
47
  if ( ! $this->model->get_languages_list() ) {
48
  return;
49
  }
@@ -60,6 +64,12 @@ class PLL_Admin_Base extends PLL_Base {
60
 
61
  // Adds the languages in admin bar
62
  add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); // 100 determines the position
 
 
 
 
 
 
63
  }
64
 
65
  /**
@@ -118,19 +128,21 @@ class PLL_Admin_Base extends PLL_Base {
118
  // 3 => 1 if loaded in footer
119
  // FIXME: check if I can load more scripts in footer
120
  $scripts = array(
121
- 'post' => array( array( 'edit' ), array( 'jquery', 'wp-ajax-response' ), 0, 1 ),
122
- 'media' => array( array( 'upload' ), array( 'jquery' ), 0, 1 ),
123
  'term' => array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), 0, 1 ),
124
  'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), 0, 0 ),
125
  'widgets' => array( array( 'widgets' ), array( 'jquery' ), 0, 0 ),
126
  );
127
 
128
  if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
129
- $scripts['classic-editor'] = array( array( 'post', 'media', 'async-upload' ), array( 'jquery', 'wp-ajax-response', 'post', 'jquery-ui-autocomplete' ), 0, 1 );
 
 
 
130
 
131
- // Block editor in WP 5.0+
132
- if ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) {
133
- $scripts['block-editor'] = array( array( 'post' ), array( 'wp-api-fetch' ), 0, 1 );
134
  }
135
  }
136
 
@@ -164,6 +176,22 @@ class PLL_Admin_Base extends PLL_Base {
164
  * @since 2.4.0
165
  */
166
  public function localize_scripts() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
168
  wp_localize_script(
169
  'pll_widgets',
@@ -202,7 +230,7 @@ class PLL_Admin_Base extends PLL_Base {
202
  }
203
 
204
  $str = http_build_query( $params );
205
- $arr = json_encode( $params );
206
  ?>
207
  <script type="text/javascript">
208
  if (typeof jQuery != 'undefined') {
@@ -210,23 +238,23 @@ class PLL_Admin_Base extends PLL_Base {
210
  $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
211
  if ( -1 != options.url.indexOf( ajaxurl ) || -1 != ajaxurl.indexOf( options.url ) ) {
212
  if ( 'undefined' === typeof options.data ) {
213
- options.data = ( 'get' === options.type.toLowerCase() ) ? '<?php echo $str; ?>' : <?php echo $arr; ?>;
214
  } else {
215
  if ( 'string' === typeof options.data ) {
216
  if ( '' === options.data && 'get' === options.type.toLowerCase() ) {
217
- options.url = options.url+'&<?php echo $str; ?>';
218
  } else {
219
  try {
220
  var o = $.parseJSON(options.data);
221
- o = $.extend(o, <?php echo $arr; ?>);
222
  options.data = JSON.stringify(o);
223
  }
224
  catch(e) {
225
- options.data = '<?php echo $str; ?>&'+options.data;
226
  }
227
  }
228
  } else {
229
- options.data = $.extend(options.data, <?php echo $arr; ?>);
230
  }
231
  }
232
  }
@@ -246,31 +274,31 @@ class PLL_Admin_Base extends PLL_Base {
246
  $this->curlang = $this->filter_lang;
247
 
248
  // Edit Post
249
- if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) {
250
  $this->curlang = $lang;
251
- } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && is_numeric( $_GET['post'] ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) {
252
  $this->curlang = $lang;
253
- } elseif ( 'post-new.php' === $GLOBALS['pagenow'] && ( empty( $_GET['post_type'] ) || $this->model->is_translated_post_type( $_GET['post_type'] ) ) ) {
254
- $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( $_GET['new_lang'] );
255
  }
256
 
257
  // Edit Term
258
  // FIXME 'edit-tags.php' for backward compatibility with WP < 4.5
259
- elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) {
260
  $this->curlang = $lang;
261
- } elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) {
262
  $this->curlang = $lang;
263
- } elseif ( 'edit-tags.php' === $GLOBALS['pagenow'] && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( $_GET['taxonomy'] ) ) {
264
- if ( ! empty( $_GET['new_lang'] ) ) {
265
- $this->curlang = $this->model->get_language( $_GET['new_lang'] );
266
  } elseif ( empty( $this->curlang ) ) {
267
  $this->curlang = $this->pref_lang;
268
  }
269
  }
270
 
271
  // Ajax
272
- if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) {
273
- $this->curlang = $this->model->get_language( $_REQUEST['lang'] );
274
  }
275
  }
276
 
@@ -282,8 +310,9 @@ class PLL_Admin_Base extends PLL_Base {
282
  public function init_user() {
283
  // Language for admin language filter: may be empty
284
  // $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter
285
- if ( ! wp_doing_ajax() && ! empty( $_GET['lang'] ) && ! is_numeric( $_GET['lang'] ) && current_user_can( 'edit_user', $user_id = get_current_user_id() ) ) {
286
- update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( $_GET['lang'] ) ) ? $lang->slug : '' );
 
287
  }
288
 
289
  $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
@@ -356,16 +385,27 @@ class PLL_Admin_Base extends PLL_Base {
356
  esc_html( $selected->name )
357
  );
358
 
359
- $wp_admin_bar->add_menu(
360
- array(
361
- 'id' => 'languages',
362
- 'title' => $selected->flag . $title,
363
- 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ),
364
- 'meta' => array( 'title' => __( 'Filters content by language', 'polylang' ) ),
365
- )
366
- );
 
 
 
 
 
 
 
 
 
 
 
367
 
368
- foreach ( array_merge( array( $all_item ), $this->model->get_languages_list() ) as $lang ) {
369
  if ( $selected->slug === $lang->slug ) {
370
  continue;
371
  }
31
 
32
  add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) );
33
 
34
+ if ( defined( 'POLYLANG_PRO' ) ) {
35
+ new PLL_Pro();
36
+ } elseif ( ! defined( 'PLL_LINGOTEK_AD' ) || PLL_LINGOTEK_AD ) {
37
+ require_once POLYLANG_DIR . '/lingotek/lingotek.php'; // Lingotek
38
+ add_action( 'wp_loaded', array( new PLL_Lingotek(), 'init' ) );
39
  }
40
  }
41
 
46
  * @since 1.2
47
  */
48
  public function init() {
49
+ parent::init();
50
+
51
  if ( ! $this->model->get_languages_list() ) {
52
  return;
53
  }
64
 
65
  // Adds the languages in admin bar
66
  add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); // 100 determines the position
67
+
68
+ // Translate slugs, only for pretty permalinks
69
+ if ( get_option( 'permalink_structure' ) && class_exists( 'PLL_Translate_Slugs' ) ) {
70
+ $slugs_model = new PLL_Translate_Slugs_Model( $this );
71
+ $this->translate_slugs = new PLL_Translate_Slugs( $slugs_model, $this->curlang );
72
+ }
73
  }
74
 
75
  /**
128
  // 3 => 1 if loaded in footer
129
  // FIXME: check if I can load more scripts in footer
130
  $scripts = array(
131
+ 'post' => array( array( 'edit', 'upload' ), array( 'jquery', 'wp-ajax-response' ), 0, 1 ),
 
132
  'term' => array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), 0, 1 ),
133
  'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), 0, 0 ),
134
  'widgets' => array( array( 'widgets' ), array( 'jquery' ), 0, 0 ),
135
  );
136
 
137
  if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
138
+ // Classic editor.
139
+ if ( ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) {
140
+ $scripts['classic-editor'] = array( array( 'post', 'media', 'async-upload' ), array( 'jquery', 'wp-ajax-response', 'post' ), 0, 1 );
141
+ }
142
 
143
+ // Block editor with legacy metabox in WP 5.0+.
144
+ if ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin() ) {
145
+ $scripts['block-editor'] = array( array( 'post' ), array( 'jquery', 'wp-ajax-response', 'wp-api-fetch' ), 0, 1 );
146
  }
147
  }
148
 
176
  * @since 2.4.0
177
  */
178
  public function localize_scripts() {
179
+ if ( wp_script_is( 'pll_classic-editor', 'enqueued' ) ) {
180
+ wp_localize_script(
181
+ 'pll_classic-editor',
182
+ 'confirm_text',
183
+ __( 'You are about to overwrite an existing translation. Are you sure you want to proceed?', 'polylang' )
184
+ );
185
+ }
186
+
187
+ if ( wp_script_is( 'pll_block-editor', 'enqueued' ) ) {
188
+ wp_localize_script(
189
+ 'pll_block-editor',
190
+ 'confirm_text',
191
+ __( 'You are about to overwrite an existing translation. Are you sure you want to proceed?', 'polylang' )
192
+ );
193
+ }
194
+
195
  if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
196
  wp_localize_script(
197
  'pll_widgets',
230
  }
231
 
232
  $str = http_build_query( $params );
233
+ $arr = wp_json_encode( $params );
234
  ?>
235
  <script type="text/javascript">
236
  if (typeof jQuery != 'undefined') {
238
  $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
239
  if ( -1 != options.url.indexOf( ajaxurl ) || -1 != ajaxurl.indexOf( options.url ) ) {
240
  if ( 'undefined' === typeof options.data ) {
241
+ options.data = ( 'get' === options.type.toLowerCase() ) ? '<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>' : <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?>;
242
  } else {
243
  if ( 'string' === typeof options.data ) {
244
  if ( '' === options.data && 'get' === options.type.toLowerCase() ) {
245
+ options.url = options.url+'&<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>';
246
  } else {
247
  try {
248
  var o = $.parseJSON(options.data);
249
+ o = $.extend(o, <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?>);
250
  options.data = JSON.stringify(o);
251
  }
252
  catch(e) {
253
+ options.data = '<?php echo $str; // phpcs:ignore WordPress.Security.EscapeOutput ?>&'+options.data;
254
  }
255
  }
256
  } else {
257
+ options.data = $.extend(options.data, <?php echo $arr; // phpcs:ignore WordPress.Security.EscapeOutput ?>);
258
  }
259
  }
260
  }
274
  $this->curlang = $this->filter_lang;
275
 
276
  // Edit Post
277
+ if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
278
  $this->curlang = $lang;
279
+ } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
280
  $this->curlang = $lang;
281
+ } elseif ( 'post-new.php' === $GLOBALS['pagenow'] && ( empty( $_GET['post_type'] ) || $this->model->is_translated_post_type( sanitize_key( $_GET['post_type'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
282
+ $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
283
  }
284
 
285
  // Edit Term
286
  // FIXME 'edit-tags.php' for backward compatibility with WP < 4.5
287
+ elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
288
  $this->curlang = $lang;
289
+ } elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
290
  $this->curlang = $lang;
291
+ } elseif ( 'edit-tags.php' === $GLOBALS['pagenow'] && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
292
+ if ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
293
+ $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
294
  } elseif ( empty( $this->curlang ) ) {
295
  $this->curlang = $this->pref_lang;
296
  }
297
  }
298
 
299
  // Ajax
300
+ if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
301
+ $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
302
  }
303
  }
304
 
310
  public function init_user() {
311
  // Language for admin language filter: may be empty
312
  // $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter
313
+ // We intentionally don't use a nonce to update the language filter
314
+ if ( ! wp_doing_ajax() && ! empty( $_GET['lang'] ) && ! is_numeric( sanitize_key( $_GET['lang'] ) ) && current_user_can( 'edit_user', $user_id = get_current_user_id() ) ) { // phpcs:ignore WordPress.Security.NonceVerification
315
+ update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( sanitize_key( $_GET['lang'] ) ) ) ? $lang->slug : '' ); // phpcs:ignore WordPress.Security.NonceVerification
316
  }
317
 
318
  $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) );
385
  esc_html( $selected->name )
386
  );
387
 
388
+ /**
389
+ * Filters the admin languages filter submenu items
390
+ *
391
+ * @since 2.6
392
+ *
393
+ * @param array $items The admin languages filter submenu items.
394
+ */
395
+ $items = apply_filters( 'pll_admin_languages_filter', array_merge( array( $all_item ), $this->model->get_languages_list() ) );
396
+
397
+ if ( ! empty( $items ) ) {
398
+ $wp_admin_bar->add_menu(
399
+ array(
400
+ 'id' => 'languages',
401
+ 'title' => $selected->flag . $title,
402
+ 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ),
403
+ 'meta' => array( 'title' => __( 'Filters content by language', 'polylang' ) ),
404
+ )
405
+ );
406
+ }
407
 
408
+ foreach ( $items as $lang ) {
409
  if ( $selected->slug === $lang->slug ) {
410
  continue;
411
  }
admin/admin-block-editor.php CHANGED
@@ -6,6 +6,7 @@
6
  * @since 2.5
7
  */
8
  class PLL_Admin_Block_Editor {
 
9
 
10
  /**
11
  * Constructor: setups filters and actions
@@ -15,6 +16,9 @@ class PLL_Admin_Block_Editor {
15
  * @param object $polylang
16
  */
17
  public function __construct( &$polylang ) {
 
 
 
18
  add_filter( 'block_editor_preload_paths', array( $this, 'preload_paths' ), 10, 2 );
19
  }
20
 
@@ -30,11 +34,17 @@ class PLL_Admin_Block_Editor {
30
  * @return array
31
  */
32
  public function preload_paths( $preload_paths, $post ) {
33
- $lang = pll_get_post_language( $post->ID );
 
 
 
 
 
34
 
35
- foreach ( $preload_paths as $k => $path ) {
36
- if ( is_string( $path ) && '/' !== $path ) {
37
- $preload_paths[ $k ] = $path . "&lang={$lang}";
 
38
  }
39
  }
40
 
6
  * @since 2.5
7
  */
8
  class PLL_Admin_Block_Editor {
9
+ public $model;
10
 
11
  /**
12
  * Constructor: setups filters and actions
16
  * @param object $polylang
17
  */
18
  public function __construct( &$polylang ) {
19
+ $this->model = &$polylang->model;
20
+ $this->pref_lang = &$polylang->pref_lang;
21
+
22
  add_filter( 'block_editor_preload_paths', array( $this, 'preload_paths' ), 10, 2 );
23
  }
24
 
34
  * @return array
35
  */
36
  public function preload_paths( $preload_paths, $post ) {
37
+ if ( $this->model->is_translated_post_type( $post->post_type ) ) {
38
+ $lang = $this->model->post->get_language( $post->ID );
39
+
40
+ if ( ! $lang ) {
41
+ $lang = $this->pref_lang;
42
+ }
43
 
44
+ foreach ( $preload_paths as $k => $path ) {
45
+ if ( is_string( $path ) && '/' !== $path ) {
46
+ $preload_paths[ $k ] = $path . "&lang={$lang->slug}";
47
+ }
48
  }
49
  }
50
 
admin/admin-classic-editor.php CHANGED
@@ -30,6 +30,9 @@ class PLL_Admin_Classic_Editor {
30
 
31
  // Filters the pages by language in the parent dropdown list in the page attributes metabox
32
  add_filter( 'page_attributes_dropdown_pages_args', array( $this, 'page_attributes_dropdown_pages_args' ), 10, 2 );
 
 
 
33
  }
34
 
35
  /**
@@ -42,7 +45,17 @@ class PLL_Admin_Classic_Editor {
42
  */
43
  public function add_meta_boxes( $post_type, $post ) {
44
  if ( $this->model->is_translated_post_type( $post_type ) ) {
45
- add_meta_box( 'ml_box', __( 'Languages', 'polylang' ), array( $this, 'post_language' ), $post_type, 'side', 'high' );
 
 
 
 
 
 
 
 
 
 
46
  }
47
  }
48
 
@@ -56,12 +69,26 @@ class PLL_Admin_Classic_Editor {
56
  $post_id = $post_ID;
57
  $post_type = get_post_type( $post_ID );
58
 
 
 
59
  $lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg :
60
- ( isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) :
61
  $this->pref_lang );
62
 
63
  $dropdown = new PLL_Walker_Dropdown();
64
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  wp_nonce_field( 'pll_language', '_pll_nonce' );
66
 
67
  // NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js )
@@ -70,17 +97,9 @@ class PLL_Admin_Classic_Editor {
70
  <label class="screen-reader-text" for="%2$s">%1$s</label>
71
  <div id="select-%3$s-language">%4$s</div>',
72
  esc_html__( 'Language', 'polylang' ),
73
- $id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', $post_ID ) : 'post_lang_choice',
74
- 'attachment' === $post_type ? 'media' : 'post',
75
- $dropdown->walk(
76
- $this->model->get_languages_list(),
77
- array(
78
- 'name' => $id,
79
- 'class' => 'post_lang_choice tags-input',
80
- 'selected' => $lang ? $lang->slug : '',
81
- 'flag' => true,
82
- )
83
- )
84
  );
85
 
86
  /**
@@ -105,11 +124,20 @@ class PLL_Admin_Classic_Editor {
105
  public function post_lang_choice() {
106
  check_ajax_referer( 'pll_language', '_pll_nonce' );
107
 
 
 
 
 
108
  global $post_ID; // Obliged to use the global variable for wp_popular_terms_checklist
109
  $post_id = $post_ID = (int) $_POST['post_id'];
110
- $lang = $this->model->get_language( $_POST['lang'] );
 
 
 
 
 
 
111
 
112
- $post_type = $_POST['post_type'];
113
  $post_type_object = get_post_type_object( $post_type );
114
  if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
115
  wp_die( -1 );
@@ -132,7 +160,7 @@ class PLL_Admin_Classic_Editor {
132
  // Categories
133
  if ( isset( $_POST['taxonomies'] ) ) {
134
  // Not set for pages
135
- foreach ( $_POST['taxonomies'] as $taxname ) {
136
  $taxonomy = get_taxonomy( $taxname );
137
 
138
  ob_start();
@@ -172,7 +200,7 @@ class PLL_Admin_Classic_Editor {
172
  'exclude_tree' => $post->ID,
173
  'selected' => $post->post_parent,
174
  'name' => 'parent_id',
175
- 'show_option_none' => __( '(no parent)' ),
176
  'sort_column' => 'menu_order, post_title',
177
  'echo' => 0,
178
  );
@@ -180,7 +208,7 @@ class PLL_Admin_Classic_Editor {
180
  /** This filter is documented in wp-admin/includes/meta-boxes.php */
181
  $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // Since WP 3.3
182
 
183
- $x->Add( array( 'what' => 'pages', 'data' => wp_dropdown_pages( $dropdown_args ) ) );
184
  }
185
 
186
  // Flag
@@ -200,53 +228,32 @@ class PLL_Admin_Classic_Editor {
200
  public function ajax_posts_not_translated() {
201
  check_ajax_referer( 'pll_language', '_pll_nonce' );
202
 
203
- if ( ! post_type_exists( $_GET['post_type'] ) ) {
204
- die( 0 );
205
  }
206
 
207
- $post_language = $this->model->get_language( $_GET['post_language'] );
208
- $translation_language = $this->model->get_language( $_GET['translation_language'] );
209
-
210
- // Don't order by title: see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
211
- $args = array(
212
- 's' => wp_unslash( $_GET['term'] ),
213
- 'suppress_filters' => 0, // To make the post_fields filter work
214
- 'lang' => 0, // Avoid admin language filter
215
- 'numberposts' => 20, // Limit to 20 posts
216
- 'post_status' => 'any',
217
- 'post_type' => $_GET['post_type'],
218
- 'tax_query' => array(
219
- array(
220
- 'taxonomy' => 'language',
221
- 'field' => 'term_taxonomy_id', // WP 3.5+
222
- 'terms' => $translation_language->term_taxonomy_id,
223
- ),
224
- ),
225
- );
226
 
227
- /**
228
- * Filter the query args when auto suggesting untranslated posts in the Languages metabox
229
- * This should help plugins to fix some edge cases
230
- *
231
- * @see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
232
- *
233
- * @since 1.7
234
- *
235
- * @param array $args WP_Query arguments
236
- */
237
- $args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
238
- $posts = get_posts( $args );
239
 
240
  $return = array();
241
 
242
- foreach ( $posts as $key => $post ) {
243
- if ( ! $this->model->post->get_translation( $post->ID, $post_language ) ) {
244
- $return[] = array(
245
- 'id' => $post->ID,
246
- 'value' => $post->post_title,
247
- 'link' => $this->links->edit_post_translation_link( $post->ID ),
248
- );
249
- }
 
250
  }
251
 
252
  // Add current translation in list
@@ -262,7 +269,7 @@ class PLL_Admin_Classic_Editor {
262
  );
263
  }
264
 
265
- wp_die( json_encode( $return ) );
266
  }
267
 
268
  /**
@@ -275,11 +282,34 @@ class PLL_Admin_Classic_Editor {
275
  * @return array Modified arguments
276
  */
277
  public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) {
278
- $dropdown_args['lang'] = isset( $_POST['lang'] ) ? $this->model->get_language( $_POST['lang'] ) : $this->model->post->get_language( $post->ID ); // ajax or not ?
279
  if ( ! $dropdown_args['lang'] ) {
280
  $dropdown_args['lang'] = $this->pref_lang;
281
  }
282
 
283
  return $dropdown_args;
284
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  }
30
 
31
  // Filters the pages by language in the parent dropdown list in the page attributes metabox
32
  add_filter( 'page_attributes_dropdown_pages_args', array( $this, 'page_attributes_dropdown_pages_args' ), 10, 2 );
33
+
34
+ // Notice
35
+ add_action( 'edit_form_top', array( $this, 'edit_form_top' ) );
36
  }
37
 
38
  /**
45
  */
46
  public function add_meta_boxes( $post_type, $post ) {
47
  if ( $this->model->is_translated_post_type( $post_type ) ) {
48
+ add_meta_box(
49
+ 'ml_box',
50
+ __( 'Languages', 'polylang' ),
51
+ array( $this, 'post_language' ),
52
+ $post_type,
53
+ 'side',
54
+ 'high',
55
+ array(
56
+ '__back_compat_meta_box' => pll_use_block_editor_plugin(),
57
+ )
58
+ );
59
  }
60
  }
61
 
69
  $post_id = $post_ID;
70
  $post_type = get_post_type( $post_ID );
71
 
72
+ $from_post_id = isset( $_GET['from_post'] ) ? (int) $_GET['from_post'] : 0; // phpcs:ignore WordPress.Security.NonceVerification
73
+
74
  $lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg :
75
+ ( isset( $_GET['new_lang'] ) ? $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification
76
  $this->pref_lang );
77
 
78
  $dropdown = new PLL_Walker_Dropdown();
79
 
80
+ $id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', (int) $post_ID ) : 'post_lang_choice';
81
+
82
+ $dropdown_html = $dropdown->walk(
83
+ $this->model->get_languages_list(),
84
+ array(
85
+ 'name' => $id,
86
+ 'class' => 'post_lang_choice tags-input',
87
+ 'selected' => $lang ? $lang->slug : '',
88
+ 'flag' => true,
89
+ )
90
+ );
91
+
92
  wp_nonce_field( 'pll_language', '_pll_nonce' );
93
 
94
  // NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js )
97
  <label class="screen-reader-text" for="%2$s">%1$s</label>
98
  <div id="select-%3$s-language">%4$s</div>',
99
  esc_html__( 'Language', 'polylang' ),
100
+ esc_attr( $id ),
101
+ ( 'attachment' === $post_type ? 'media' : 'post' ),
102
+ $dropdown_html // phpcs:ignore WordPress.Security.EscapeOutput
 
 
 
 
 
 
 
 
103
  );
104
 
105
  /**
124
  public function post_lang_choice() {
125
  check_ajax_referer( 'pll_language', '_pll_nonce' );
126
 
127
+ if ( ! isset( $_POST['post_id'], $_POST['lang'], $_POST['post_type'] ) ) {
128
+ wp_die( 0 );
129
+ }
130
+
131
  global $post_ID; // Obliged to use the global variable for wp_popular_terms_checklist
132
  $post_id = $post_ID = (int) $_POST['post_id'];
133
+ $lang = $this->model->get_language( sanitize_key( $_POST['lang'] ) );
134
+
135
+ $post_type = sanitize_key( $_POST['post_type'] );
136
+
137
+ if ( ! post_type_exists( $post_type ) ) {
138
+ wp_die( 0 );
139
+ }
140
 
 
141
  $post_type_object = get_post_type_object( $post_type );
142
  if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
143
  wp_die( -1 );
160
  // Categories
161
  if ( isset( $_POST['taxonomies'] ) ) {
162
  // Not set for pages
163
+ foreach ( array_map( 'sanitize_key', $_POST['taxonomies'] ) as $taxname ) {
164
  $taxonomy = get_taxonomy( $taxname );
165
 
166
  ob_start();
200
  'exclude_tree' => $post->ID,
201
  'selected' => $post->post_parent,
202
  'name' => 'parent_id',
203
+ 'show_option_none' => __( '(no parent)', 'polylang' ),
204
  'sort_column' => 'menu_order, post_title',
205
  'echo' => 0,
206
  );
208
  /** This filter is documented in wp-admin/includes/meta-boxes.php */
209
  $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // Since WP 3.3
210
 
211
+ $x->Add( array( 'what' => 'pages', 'data' => wp_dropdown_pages( $dropdown_args ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput
212
  }
213
 
214
  // Flag
228
  public function ajax_posts_not_translated() {
229
  check_ajax_referer( 'pll_language', '_pll_nonce' );
230
 
231
+ if ( ! isset( $_GET['post_type'], $_GET['post_language'], $_GET['translation_language'], $_GET['term'], $_GET['pll_post_id'] ) ) {
232
+ wp_die( 0 );
233
  }
234
 
235
+ $post_type = sanitize_key( $_GET['post_type'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
+ if ( ! post_type_exists( $post_type ) ) {
238
+ wp_die( 0 );
239
+ }
240
+
241
+ $term = wp_unslash( $_GET['term'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
242
+
243
+ $post_language = $this->model->get_language( sanitize_key( $_GET['post_language'] ) );
244
+ $translation_language = $this->model->get_language( sanitize_key( $_GET['translation_language'] ) );
 
 
 
 
245
 
246
  $return = array();
247
 
248
+ $untranslated_posts = $this->model->post->get_untranslated( $post_type, $post_language, $translation_language, $term );
249
+
250
+ // format output
251
+ foreach ( $untranslated_posts as $post ) {
252
+ $return[] = array(
253
+ 'id' => $post->ID,
254
+ 'value' => $post->post_title,
255
+ 'link' => $this->links->edit_post_translation_link( $post->ID ),
256
+ );
257
  }
258
 
259
  // Add current translation in list
269
  );
270
  }
271
 
272
+ wp_die( wp_json_encode( $return ) );
273
  }
274
 
275
  /**
282
  * @return array Modified arguments
283
  */
284
  public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) {
285
+ $dropdown_args['lang'] = isset( $_POST['lang'] ) ? $this->model->get_language( sanitize_key( $_POST['lang'] ) ) : $this->model->post->get_language( $post->ID ); // phpcs:ignore WordPress.Security.NonceVerification
286
  if ( ! $dropdown_args['lang'] ) {
287
  $dropdown_args['lang'] = $this->pref_lang;
288
  }
289
 
290
  return $dropdown_args;
291
  }
292
+
293
+ /**
294
+ * Displays a notice if the user has not sufficient rights to overwrite synchronized taxonomies and metas
295
+ *
296
+ * @since 2.6
297
+ *
298
+ * @param object $post Post currently being edited
299
+ */
300
+ public function edit_form_top( $post ) {
301
+ if ( ! $this->model->post->current_user_can_synchronize( $post->ID ) ) {
302
+ ?>
303
+ <div class="pll-notice notice notice-warning">
304
+ <p>
305
+ <?php
306
+ esc_html_e( 'Some taxonomies or metadata may be synchronized with existing translations that you are not allowed to modify.', 'polylang' );
307
+ echo ' ';
308
+ esc_html_e( 'If you attempt to modify them anyway, your changes will not be saved.', 'polylang' );
309
+ ?>
310
+ </p>
311
+ </div>
312
+ <?php
313
+ }
314
+ }
315
  }
admin/admin-filters-columns.php CHANGED
@@ -108,8 +108,8 @@ class PLL_Admin_Filters_Columns {
108
  * @param int $post_id
109
  */
110
  public function post_column( $column, $post_id ) {
111
- $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save' === $_REQUEST['action'];
112
- $lang = $inline ? $this->model->get_language( $_POST['inline_lang_choice'] ) : $this->model->post->get_language( $post_id );
113
 
114
  if ( false === strpos( $column, 'language_' ) || ! $lang ) {
115
  return;
@@ -155,7 +155,7 @@ class PLL_Admin_Filters_Columns {
155
  }
156
  // Link to add a new translation
157
  else {
158
- echo $this->links->new_post_translation_link( $post_id, $language );
159
  }
160
  }
161
 
@@ -173,7 +173,7 @@ class PLL_Admin_Filters_Columns {
173
 
174
  $elements = $this->model->get_languages_list();
175
  if ( current_filter() == 'bulk_edit_custom_box' ) {
176
- array_unshift( $elements, (object) array( 'slug' => -1, 'name' => __( '&mdash; No Change &mdash;' ) ) );
177
  }
178
 
179
  $dropdown = new PLL_Walker_Dropdown();
@@ -188,7 +188,7 @@ class PLL_Admin_Filters_Columns {
188
  </div>
189
  </fieldset>',
190
  esc_html__( 'Language', 'polylang' ),
191
- $dropdown->walk( $elements, array( 'name' => 'inline_lang_choice', 'id' => '' ) )
192
  );
193
  }
194
  return $column;
@@ -216,13 +216,26 @@ class PLL_Admin_Filters_Columns {
216
  * @param int $term_id
217
  */
218
  public function term_column( $out, $column, $term_id ) {
219
- $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save-tax' === $_REQUEST['action'];
220
- if ( false === strpos( $column, 'language_' ) || ! ( $lang = $inline ? $this->model->get_language( $_POST['inline_lang_choice'] ) : $this->model->term->get_language( $term_id ) ) ) {
221
  return $out;
222
  }
223
 
224
- $post_type = isset( $GLOBALS['post_type'] ) ? $GLOBALS['post_type'] : $_REQUEST['post_type']; // 2nd case for quick edit
225
- $taxonomy = isset( $GLOBALS['taxonomy'] ) ? $GLOBALS['taxonomy'] : $_REQUEST['taxonomy'];
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
  if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
228
  return $out;
@@ -282,21 +295,29 @@ class PLL_Admin_Filters_Columns {
282
  * @since 1.7
283
  */
284
  public function ajax_update_post_rows() {
285
- global $wp_list_table;
286
 
287
- if ( ! post_type_exists( $post_type = $_POST['post_type'] ) || ! $this->model->is_translated_post_type( $post_type ) ) {
288
- die( 0 );
289
  }
290
 
291
- check_ajax_referer( 'inlineeditnonce', '_pll_nonce' );
 
 
 
 
 
 
 
292
 
293
  $x = new WP_Ajax_Response();
294
- $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
295
 
296
- $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // collect old translations
297
- $translations = array_merge( $translations, array( $_POST['post_id'] ) ); // add current post
298
  $translations = array_map( 'intval', $translations );
299
 
 
 
300
  foreach ( $translations as $post_id ) {
301
  $level = is_post_type_hierarchical( $post_type ) ? count( get_ancestors( $post_id, $post_type ) ) : 0;
302
  if ( $post = get_post( $post_id ) ) {
@@ -316,22 +337,30 @@ class PLL_Admin_Filters_Columns {
316
  * @since 1.7
317
  */
318
  public function ajax_update_term_rows() {
319
- global $wp_list_table;
320
 
321
- if ( ! taxonomy_exists( $taxonomy = $_POST['taxonomy'] ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
322
- die( 0 );
323
  }
324
 
325
- check_ajax_referer( 'pll_language', '_pll_nonce' );
 
 
 
 
 
 
 
326
 
327
  $x = new WP_Ajax_Response();
328
- $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
329
 
330
- $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // collect old translations
331
- $translations = array_merge( $translations, $this->model->term->get_translations( (int) $_POST['term_id'] ) ); // add current translations
332
- $translations = array_unique( $translations ); // remove duplicates
333
  $translations = array_map( 'intval', $translations );
334
 
 
 
 
335
  foreach ( $translations as $term_id ) {
336
  $level = is_taxonomy_hierarchical( $taxonomy ) ? count( get_ancestors( $term_id, $taxonomy ) ) : 0;
337
  if ( $tag = get_term( $term_id, $taxonomy ) ) {
108
  * @param int $post_id
109
  */
110
  public function post_column( $column, $post_id ) {
111
+ $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save' === $_REQUEST['action']; // phpcs:ignore WordPress.Security.NonceVerification
112
+ $lang = $inline ? $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) : $this->model->post->get_language( $post_id ); // phpcs:ignore WordPress.Security.NonceVerification
113
 
114
  if ( false === strpos( $column, 'language_' ) || ! $lang ) {
115
  return;
155
  }
156
  // Link to add a new translation
157
  else {
158
+ echo $this->links->new_post_translation_link( $post_id, $language ); // phpcs:ignore WordPress.Security.EscapeOutput
159
  }
160
  }
161
 
173
 
174
  $elements = $this->model->get_languages_list();
175
  if ( current_filter() == 'bulk_edit_custom_box' ) {
176
+ array_unshift( $elements, (object) array( 'slug' => -1, 'name' => __( '&mdash; No Change &mdash;', 'polylang' ) ) );
177
  }
178
 
179
  $dropdown = new PLL_Walker_Dropdown();
188
  </div>
189
  </fieldset>',
190
  esc_html__( 'Language', 'polylang' ),
191
+ $dropdown->walk( $elements, array( 'name' => 'inline_lang_choice', 'id' => '' ) ) // phpcs:ignore WordPress.Security.EscapeOutput
192
  );
193
  }
194
  return $column;
216
  * @param int $term_id
217
  */
218
  public function term_column( $out, $column, $term_id ) {
219
+ $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save-tax' === $_REQUEST['action']; // phpcs:ignore WordPress.Security.NonceVerification
220
+ if ( false === strpos( $column, 'language_' ) || ! ( $lang = $inline ? $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) : $this->model->term->get_language( $term_id ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
221
  return $out;
222
  }
223
 
224
+ if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
225
+ $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification
226
+ }
227
+
228
+ if ( isset( $GLOBALS['post_type'] ) ) {
229
+ $post_type = $GLOBALS['post_type'];
230
+ }
231
+
232
+ if ( isset( $_REQUEST['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
233
+ $taxonomy = sanitize_key( $_REQUEST['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification
234
+ }
235
+
236
+ if ( isset( $GLOBALS['taxonomy'] ) ) {
237
+ $taxonomy = $GLOBALS['taxonomy'];
238
+ }
239
 
240
  if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
241
  return $out;
295
  * @since 1.7
296
  */
297
  public function ajax_update_post_rows() {
298
+ check_ajax_referer( 'inlineeditnonce', '_pll_nonce' );
299
 
300
+ if ( ! isset( $_POST['post_type'], $_POST['post_id'], $_POST['screen'] ) ) {
301
+ wp_die( 0 );
302
  }
303
 
304
+ $post_type = sanitize_key( $_POST['post_type'] );
305
+
306
+ if ( ! post_type_exists( $post_type ) || ! $this->model->is_translated_post_type( $post_type ) ) {
307
+ wp_die( 0 );
308
+ }
309
+
310
+ global $wp_list_table;
311
+ $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => sanitize_key( $_POST['screen'] ) ) );
312
 
313
  $x = new WP_Ajax_Response();
 
314
 
315
+ // Collect old translations
316
+ $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
317
  $translations = array_map( 'intval', $translations );
318
 
319
+ $translations = array_merge( $translations, array( (int) $_POST['post_id'] ) ); // Add current post
320
+
321
  foreach ( $translations as $post_id ) {
322
  $level = is_post_type_hierarchical( $post_type ) ? count( get_ancestors( $post_id, $post_type ) ) : 0;
323
  if ( $post = get_post( $post_id ) ) {
337
  * @since 1.7
338
  */
339
  public function ajax_update_term_rows() {
340
+ check_ajax_referer( 'pll_language', '_pll_nonce' );
341
 
342
+ if ( ! isset( $_POST['taxonomy'], $_POST['term_id'], $_POST['screen'] ) ) {
343
+ wp_die( 0 );
344
  }
345
 
346
+ $taxonomy = sanitize_key( $_POST['taxonomy'] );
347
+
348
+ if ( ! taxonomy_exists( $taxonomy ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
349
+ wp_die( 0 );
350
+ }
351
+
352
+ global $wp_list_table;
353
+ $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => sanitize_key( $_POST['screen'] ) ) );
354
 
355
  $x = new WP_Ajax_Response();
 
356
 
357
+ // Collect old translations
358
+ $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
 
359
  $translations = array_map( 'intval', $translations );
360
 
361
+ $translations = array_merge( $translations, $this->model->term->get_translations( (int) $_POST['term_id'] ) ); // Add current translations
362
+ $translations = array_unique( $translations ); // Remove duplicates
363
+
364
  foreach ( $translations as $term_id ) {
365
  $level = is_taxonomy_hierarchical( $taxonomy ) ? count( get_ancestors( $term_id, $taxonomy ) ) : 0;
366
  if ( $tag = get_term( $term_id, $taxonomy ) ) {
admin/admin-filters-media.php CHANGED
@@ -28,7 +28,7 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
28
  add_filter( 'attachment_fields_to_save', array( $this, 'save_media' ), 10, 2 );
29
 
30
  // Creates a media translation
31
- if ( isset( $_GET['action'], $_GET['new_lang'], $_GET['from_media'] ) && 'translate_media' === $_GET['action'] ) {
32
  add_action( 'admin_init', array( $this, 'translate_media' ) );
33
  }
34
  }
@@ -74,21 +74,23 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
74
  * @since 0.9
75
  */
76
  public function translate_media() {
77
- // Security check
78
- check_admin_referer( 'translate_media' );
79
- $post_id = (int) $_GET['from_media'];
80
-
81
- // Bails if the translations already exists
82
- // See https://wordpress.org/support/topic/edit-translation-in-media-attachments?#post-7322303
83
- // Or if the source media does not exist
84
- if ( $this->model->post->get_translation( $post_id, $_GET['new_lang'] ) || ! get_post( $post_id ) ) {
85
- wp_safe_redirect( wp_get_referer() );
 
 
 
 
 
 
86
  exit;
87
  }
88
-
89
- $tr_id = $this->posts->create_media_translation( $post_id, $_GET['new_lang'] );
90
- wp_safe_redirect( admin_url( sprintf( 'post.php?post=%d&action=edit', $tr_id ) ) ); // WP 3.5+
91
- exit;
92
  }
93
 
94
  /**
@@ -108,8 +110,8 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
108
  $this->model->post->set_language( $post['ID'], $attachment['language'] );
109
  }
110
 
111
- if ( isset( $_POST['media_tr_lang'] ) ) {
112
- $this->save_translations( $post['ID'], $_POST['media_tr_lang'] );
113
  }
114
 
115
  return $post;
28
  add_filter( 'attachment_fields_to_save', array( $this, 'save_media' ), 10, 2 );
29
 
30
  // Creates a media translation
31
+ if ( isset( $_GET['action'], $_GET['new_lang'], $_GET['from_media'] ) && 'translate_media' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
32
  add_action( 'admin_init', array( $this, 'translate_media' ) );
33
  }
34
  }
74
  * @since 0.9
75
  */
76
  public function translate_media() {
77
+ if ( isset( $_GET['from_media'], $_GET['new_lang'] ) ) {
78
+ // Security check
79
+ check_admin_referer( 'translate_media' );
80
+ $post_id = (int) $_GET['from_media'];
81
+
82
+ // Bails if the translations already exists
83
+ // See https://wordpress.org/support/topic/edit-translation-in-media-attachments?#post-7322303
84
+ // Or if the source media does not exist
85
+ if ( $this->model->post->get_translation( $post_id, sanitize_key( $_GET['new_lang'] ) ) || ! get_post( $post_id ) ) {
86
+ wp_safe_redirect( wp_get_referer() );
87
+ exit;
88
+ }
89
+
90
+ $tr_id = $this->posts->create_media_translation( $post_id, sanitize_key( $_GET['new_lang'] ) );
91
+ wp_safe_redirect( admin_url( sprintf( 'post.php?post=%d&action=edit', $tr_id ) ) ); // WP 3.5+
92
  exit;
93
  }
 
 
 
 
94
  }
95
 
96
  /**
110
  $this->model->post->set_language( $post['ID'], $attachment['language'] );
111
  }
112
 
113
+ if ( isset( $_POST['media_tr_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
114
+ $this->save_translations( $post['ID'], array_map( 'absint', $_POST['media_tr_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
115
  }
116
 
117
  return $post;
admin/admin-filters-post.php CHANGED
@@ -104,17 +104,17 @@ class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
104
  * @since 2.3
105
  */
106
  public function edit_post() {
107
- if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $post_id = (int) $_POST['post_ID'] ) {
108
  check_admin_referer( 'pll_language', '_pll_nonce' );
109
 
110
  $post = get_post( $post_id );
111
  $post_type_object = get_post_type_object( $post->post_type );
112
 
113
  if ( current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
114
- $this->model->post->set_language( $post_id, $this->model->get_language( $_POST['post_lang_choice'] ) );
115
 
116
  if ( isset( $_POST['post_tr_lang'] ) ) {
117
- $this->save_translations( $post_id, $_POST['post_tr_lang'] );
118
  }
119
  }
120
  }
@@ -160,10 +160,10 @@ class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
160
  * @since 2.3
161
  */
162
  public function bulk_edit_posts() {
163
- if ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) && -1 !== $_GET['inline_lang_choice'] ) {
164
  check_admin_referer( 'bulk-posts' );
165
 
166
- if ( $lang = $this->model->get_language( $_GET['inline_lang_choice'] ) ) {
167
  $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
168
  foreach ( $post_ids as $post_id ) {
169
  $this->inline_save_language( $post_id, $lang );
@@ -182,7 +182,7 @@ class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
182
 
183
  if ( isset( $_POST['post_ID'], $_POST['inline_lang_choice'] ) ) {
184
  $post_id = (int) $_POST['post_ID'];
185
- $lang = $this->model->get_language( $_POST['inline_lang_choice'] );
186
  if ( $post_id && $lang ) {
187
  $this->inline_save_language( $post_id, $lang );
188
  }
104
  * @since 2.3
105
  */
106
  public function edit_post() {
107
+ if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $post_id = (int) $_POST['post_ID'] ) { // phpcs:ignore WordPress.Security.NonceVerification
108
  check_admin_referer( 'pll_language', '_pll_nonce' );
109
 
110
  $post = get_post( $post_id );
111
  $post_type_object = get_post_type_object( $post->post_type );
112
 
113
  if ( current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
114
+ $this->model->post->set_language( $post_id, $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ) );
115
 
116
  if ( isset( $_POST['post_tr_lang'] ) ) {
117
+ $this->save_translations( $post_id, array_map( 'absint', $_POST['post_tr_lang'] ) );
118
  }
119
  }
120
  }
160
  * @since 2.3
161
  */
162
  public function bulk_edit_posts() {
163
+ if ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'], $_REQUEST['post'] ) && -1 !== $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification
164
  check_admin_referer( 'bulk-posts' );
165
 
166
+ if ( $lang = $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ) ) {
167
  $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
168
  foreach ( $post_ids as $post_id ) {
169
  $this->inline_save_language( $post_id, $lang );
182
 
183
  if ( isset( $_POST['post_ID'], $_POST['inline_lang_choice'] ) ) {
184
  $post_id = (int) $_POST['post_ID'];
185
+ $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) );
186
  if ( $post_id && $lang ) {
187
  $this->inline_save_language( $post_id, $lang );
188
  }
admin/admin-filters-term.php CHANGED
@@ -55,16 +55,38 @@ class PLL_Admin_Filters_Term {
55
  * @since 0.1
56
  */
57
  public function add_term_form() {
58
- $taxonomy = $_GET['taxonomy'];
59
- $post_type = isset( $GLOBALS['post_type'] ) ? $GLOBALS['post_type'] : $_REQUEST['post_type'];
 
 
 
 
 
 
 
 
 
60
 
61
- if ( ! taxonomy_exists( $taxonomy ) || ! post_type_exists( $post_type ) ) {
62
  return;
63
  }
64
 
65
- $lang = isset( $_GET['new_lang'] ) ? $this->model->get_language( $_GET['new_lang'] ) : $this->pref_lang;
 
 
 
66
  $dropdown = new PLL_Walker_Dropdown();
67
 
 
 
 
 
 
 
 
 
 
 
68
  wp_nonce_field( 'pll_language', '_pll_nonce' );
69
 
70
  printf(
@@ -74,20 +96,12 @@ class PLL_Admin_Filters_Term {
74
  <p>%s</p>
75
  </div>',
76
  esc_html__( 'Language', 'polylang' ),
77
- $dropdown->walk(
78
- $this->model->get_languages_list(),
79
- array(
80
- 'name' => 'term_lang_choice',
81
- 'value' => 'term_id',
82
- 'selected' => $lang ? $lang->term_id : '',
83
- 'flag' => true,
84
- )
85
- ),
86
  esc_html__( 'Sets the language', 'polylang' )
87
  );
88
 
89
- if ( ! empty( $_GET['from_tag'] ) ) {
90
- printf( '<input type="hidden" name="from_tag" value="%d" />', (int) $_GET['from_tag'] );
91
  }
92
 
93
  // Adds translation fields
@@ -106,7 +120,13 @@ class PLL_Admin_Filters_Term {
106
  * @param object $tag
107
  */
108
  public function edit_term_form( $tag ) {
109
- $post_type = isset( $GLOBALS['post_type'] ) ? $GLOBALS['post_type'] : $_REQUEST['post_type'];
 
 
 
 
 
 
110
 
111
  if ( ! post_type_exists( $post_type ) ) {
112
  return;
@@ -118,15 +138,27 @@ class PLL_Admin_Filters_Term {
118
  $lang = $this->model->term->get_language( $term_id );
119
  $lang = empty( $lang ) ? $this->pref_lang : $lang;
120
 
121
- $dropdown = new PLL_Walker_Dropdown();
122
-
123
  // Disable the language dropdown and the translations input fields for default categories to prevent removal
124
  $disabled = in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) );
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  printf(
127
  '<tr class="form-field">
128
  <th scope="row">
129
- %s
130
  <label for="term_lang_choice">%s</label>
131
  </th>
132
  <td id="select-edit-term-language">
@@ -134,18 +166,8 @@ class PLL_Admin_Filters_Term {
134
  <p class="description">%s</p>
135
  </td>
136
  </tr>',
137
- wp_nonce_field( 'pll_language', '_pll_nonce', true, false ),
138
  esc_html__( 'Language', 'polylang' ),
139
- $dropdown->walk(
140
- $this->model->get_languages_list(),
141
- array(
142
- 'name' => 'term_lang_choice',
143
- 'value' => 'term_id',
144
- 'selected' => $lang ? $lang->term_id : '',
145
- 'disabled' => $disabled,
146
- 'flag' => true,
147
- )
148
- ),
149
  esc_html__( 'Sets the language', 'polylang' )
150
  );
151
 
@@ -165,10 +187,15 @@ class PLL_Admin_Filters_Term {
165
  * @return string modified html
166
  */
167
  public function wp_dropdown_cats( $output ) {
168
- if ( isset( $_GET['taxonomy'], $_GET['from_tag'], $_GET['new_lang'] ) && taxonomy_exists( $_GET['taxonomy'] ) ) {
169
- $term = get_term( (int) $_GET['from_tag'], $_GET['taxonomy'] );
 
 
 
 
 
170
  if ( $term && $id = $term->parent ) {
171
- $lang = $this->model->get_language( $_GET['new_lang'] );
172
  if ( $parent = $this->model->term->get_translation( $id, $lang ) ) {
173
  return str_replace( '"' . $parent . '"', '"' . $parent . '" selected="selected"', $output );
174
  }
@@ -185,7 +212,7 @@ class PLL_Admin_Filters_Term {
185
  * @param int $post_id
186
  */
187
  public function pre_post_update( $post_id ) {
188
- if ( isset( $_GET['bulk_edit'] ) ) {
189
  $this->post_id = $post_id;
190
  }
191
  }
@@ -205,14 +232,13 @@ class PLL_Admin_Filters_Term {
205
 
206
  // Edit tags
207
  if ( isset( $_POST['term_lang_choice'] ) ) {
208
- if ( 'add-' . $taxonomy == $_POST['action'] ) {
209
- check_ajax_referer( $_POST['action'], '_ajax_nonce-add-' . $taxonomy ); // category metabox
210
- }
211
- else {
212
- check_admin_referer( 'pll_language', '_pll_nonce' ); // edit tags or tags metabox
213
  }
214
 
215
- $this->model->term->set_language( $term_id, $this->model->get_language( $_POST['term_lang_choice'] ) );
216
  }
217
 
218
  // *Post* bulk edit, in case a new term is created
@@ -252,7 +278,7 @@ class PLL_Admin_Filters_Term {
252
  }
253
 
254
  else {
255
- $this->model->term->set_language( $term_id, $this->model->get_language( $_GET['inline_lang_choice'] ) );
256
  }
257
  }
258
 
@@ -264,7 +290,7 @@ class PLL_Admin_Filters_Term {
264
  );
265
 
266
  $old_lang = $this->model->term->get_language( $term_id ); // Stores the old language
267
- $lang = $this->model->get_language( $_POST['inline_lang_choice'] ); // New language
268
  $translations = $this->model->term->get_translations( $term_id );
269
 
270
  // Checks if the new language already exists in the translation group
@@ -285,7 +311,7 @@ class PLL_Admin_Filters_Term {
285
  // Edit post
286
  elseif ( isset( $_POST['post_lang_choice'] ) ) { // FIXME should be useless now
287
  check_admin_referer( 'pll_language', '_pll_nonce' );
288
- $this->model->term->set_language( $term_id, $this->model->get_language( $_POST['post_lang_choice'] ) );
289
  }
290
  }
291
 
@@ -301,10 +327,14 @@ class PLL_Admin_Filters_Term {
301
  // Security check as 'wp_update_term' can be called from outside WP admin
302
  check_admin_referer( 'pll_language', '_pll_nonce' );
303
 
 
 
304
  // Save translations after checking the translated term is in the right language ( as well as cast id to int )
305
- foreach ( $_POST['term_tr_lang'] as $lang => $tr_id ) {
306
- $tr_lang = $this->model->term->get_language( (int) $tr_id );
307
- $translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? (int) $tr_id : 0;
 
 
308
  }
309
 
310
  $this->model->term->save_translations( $term_id, $translations );
@@ -332,10 +362,10 @@ class PLL_Admin_Filters_Term {
332
  // As 'wp_update_term' can be called from outside WP admin
333
  // 2nd test for creating tags when creating / editing a post
334
  $tax = get_taxonomy( $taxonomy );
335
- if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) {
336
  $this->save_language( $term_id, $taxonomy );
337
 
338
- if ( isset( $_POST['term_tr_lang'] ) ) {
339
  $translations = $this->save_translations( $term_id );
340
  }
341
  }
@@ -367,21 +397,21 @@ class PLL_Admin_Filters_Term {
367
 
368
  // If the term already exists in another language
369
  if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
370
- if ( isset( $_POST['term_lang_choice'] ) ) {
371
- $slug = $name . '-' . $this->model->get_language( $_POST['term_lang_choice'] )->slug;
372
  }
373
 
374
- elseif ( isset( $_POST['inline_lang_choice'] ) ) {
375
- $slug = $name . '-' . $this->model->get_language( $_POST['inline_lang_choice'] )->slug;
376
  }
377
 
378
  // *Post* bulk edit, in case a new term is created
379
- elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
380
  // Bulk edit does not modify the language
381
- if ( -1 == $_GET['inline_lang_choice'] ) {
382
  $slug = $name . '-' . $this->model->post->get_language( $this->post_id )->slug;
383
  } else {
384
- $slug = $name . '-' . $this->model->get_language( $_GET['inline_lang_choice'] )->slug;
385
  }
386
  }
387
  }
@@ -397,13 +427,17 @@ class PLL_Admin_Filters_Term {
397
  public function term_lang_choice() {
398
  check_ajax_referer( 'pll_language', '_pll_nonce' );
399
 
400
- $lang = $this->model->get_language( $_POST['lang'] );
 
 
 
 
401
  $term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : null;
402
- $taxonomy = $_POST['taxonomy'];
403
- $post_type = $_POST['post_type'];
404
 
405
  if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
406
- die( 0 );
407
  }
408
 
409
  ob_start();
@@ -423,7 +457,7 @@ class PLL_Admin_Filters_Term {
423
  'name' => 'parent',
424
  'orderby' => 'name',
425
  'hierarchical' => true,
426
- 'show_option_none' => __( 'None' ),
427
  'echo' => 0,
428
  );
429
  $x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) );
@@ -460,16 +494,20 @@ class PLL_Admin_Filters_Term {
460
  public function ajax_terms_not_translated() {
461
  check_ajax_referer( 'pll_language', '_pll_nonce' );
462
 
463
- $s = wp_unslash( $_GET['term'] );
464
- $post_type = $_GET['post_type'];
465
- $taxonomy = $_GET['taxonomy'];
 
 
 
 
466
 
467
  if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
468
- die( 0 );
469
  }
470
 
471
- $term_language = $this->model->get_language( $_GET['term_language'] );
472
- $translation_language = $this->model->get_language( $_GET['translation_language'] );
473
 
474
  $return = array();
475
 
@@ -487,8 +525,8 @@ class PLL_Admin_Filters_Term {
487
  }
488
 
489
  // Add current translation in list
490
- // Not in add term for as term_id is not set
491
- if ( 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) {
492
  $term = get_term( $term_id, $taxonomy );
493
  array_unshift(
494
  $return,
@@ -500,7 +538,7 @@ class PLL_Admin_Filters_Term {
500
  );
501
  }
502
 
503
- wp_die( json_encode( $return ) );
504
  }
505
 
506
  /**
@@ -577,7 +615,7 @@ class PLL_Admin_Filters_Term {
577
  $translations[ $key ] = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id );
578
 
579
  // Hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
580
- if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) {
581
  $_POST['term_tr_lang'][ $key ] = $translations[ $key ];
582
  }
583
  }
55
  * @since 0.1
56
  */
57
  public function add_term_form() {
58
+ if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
59
+ $taxonomy = sanitize_key( $_GET['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification
60
+ }
61
+
62
+ if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
63
+ $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification
64
+ }
65
+
66
+ if ( isset( $GLOBALS['post_type'] ) ) {
67
+ $post_type = $GLOBALS['post_type'];
68
+ }
69
 
70
+ if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) || ! post_type_exists( $post_type ) ) {
71
  return;
72
  }
73
 
74
+ $from_term_id = isset( $_GET['from_tag'] ) ? (int) $_GET['from_tag'] : 0; // phpcs:ignore WordPress.Security.NonceVerification
75
+
76
+ $lang = isset( $_GET['new_lang'] ) ? $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) : $this->pref_lang; // phpcs:ignore WordPress.Security.NonceVerification
77
+
78
  $dropdown = new PLL_Walker_Dropdown();
79
 
80
+ $dropdown_html = $dropdown->walk(
81
+ $this->model->get_languages_list(),
82
+ array(
83
+ 'name' => 'term_lang_choice',
84
+ 'value' => 'term_id',
85
+ 'selected' => $lang ? $lang->term_id : '',
86
+ 'flag' => true,
87
+ )
88
+ );
89
+
90
  wp_nonce_field( 'pll_language', '_pll_nonce' );
91
 
92
  printf(
96
  <p>%s</p>
97
  </div>',
98
  esc_html__( 'Language', 'polylang' ),
99
+ $dropdown_html, // phpcs:ignore
 
 
 
 
 
 
 
 
100
  esc_html__( 'Sets the language', 'polylang' )
101
  );
102
 
103
+ if ( ! empty( $from_term_id ) ) {
104
+ printf( '<input type="hidden" name="from_tag" value="%d" />', (int) $from_term_id );
105
  }
106
 
107
  // Adds translation fields
120
  * @param object $tag
121
  */
122
  public function edit_term_form( $tag ) {
123
+ if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
124
+ $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification
125
+ }
126
+
127
+ if ( isset( $GLOBALS['post_type'] ) ) {
128
+ $post_type = $GLOBALS['post_type'];
129
+ }
130
 
131
  if ( ! post_type_exists( $post_type ) ) {
132
  return;
138
  $lang = $this->model->term->get_language( $term_id );
139
  $lang = empty( $lang ) ? $this->pref_lang : $lang;
140
 
 
 
141
  // Disable the language dropdown and the translations input fields for default categories to prevent removal
142
  $disabled = in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) );
143
 
144
+ $dropdown = new PLL_Walker_Dropdown();
145
+
146
+ $dropdown_html = $dropdown->walk(
147
+ $this->model->get_languages_list(),
148
+ array(
149
+ 'name' => 'term_lang_choice',
150
+ 'value' => 'term_id',
151
+ 'selected' => $lang ? $lang->term_id : '',
152
+ 'disabled' => $disabled,
153
+ 'flag' => true,
154
+ )
155
+ );
156
+
157
+ wp_nonce_field( 'pll_language', '_pll_nonce' );
158
+
159
  printf(
160
  '<tr class="form-field">
161
  <th scope="row">
 
162
  <label for="term_lang_choice">%s</label>
163
  </th>
164
  <td id="select-edit-term-language">
166
  <p class="description">%s</p>
167
  </td>
168
  </tr>',
 
169
  esc_html__( 'Language', 'polylang' ),
170
+ $dropdown_html, // phpcs:ignore
 
 
 
 
 
 
 
 
 
171
  esc_html__( 'Sets the language', 'polylang' )
172
  );
173
 
187
  * @return string modified html
188
  */
189
  public function wp_dropdown_cats( $output ) {
190
+ if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
191
+ $taxonomy = sanitize_key( $_GET['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification
192
+ }
193
+
194
+ if ( isset( $taxonomy, $_GET['from_tag'], $_GET['new_lang'] ) && taxonomy_exists( $taxonomy ) ) { // phpcs:ignore WordPress.Security.NonceVerification
195
+ $term = get_term( (int) $_GET['from_tag'], $taxonomy ); // phpcs:ignore WordPress.Security.NonceVerification
196
+
197
  if ( $term && $id = $term->parent ) {
198
+ $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
199
  if ( $parent = $this->model->term->get_translation( $id, $lang ) ) {
200
  return str_replace( '"' . $parent . '"', '"' . $parent . '" selected="selected"', $output );
201
  }
212
  * @param int $post_id
213
  */
214
  public function pre_post_update( $post_id ) {
215
+ if ( isset( $_GET['bulk_edit'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
216
  $this->post_id = $post_id;
217
  }
218
  }
232
 
233
  // Edit tags
234
  if ( isset( $_POST['term_lang_choice'] ) ) {
235
+ if ( isset( $_POST['action'] ) && sanitize_key( $_POST['action'] ) === 'add-' . $taxonomy ) { // phpcs:ignore WordPress.Security.NonceVerification
236
+ check_ajax_referer( 'add-' . $taxonomy, '_ajax_nonce-add-' . $taxonomy ); // Category metabox
237
+ } else {
238
+ check_admin_referer( 'pll_language', '_pll_nonce' ); // Edit tags or tags metabox
 
239
  }
240
 
241
+ $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) ) );
242
  }
243
 
244
  // *Post* bulk edit, in case a new term is created
278
  }
279
 
280
  else {
281
+ $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ) );
282
  }
283
  }
284
 
290
  );
291
 
292
  $old_lang = $this->model->term->get_language( $term_id ); // Stores the old language
293
+ $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ); // New language
294
  $translations = $this->model->term->get_translations( $term_id );
295
 
296
  // Checks if the new language already exists in the translation group
311
  // Edit post
312
  elseif ( isset( $_POST['post_lang_choice'] ) ) { // FIXME should be useless now
313
  check_admin_referer( 'pll_language', '_pll_nonce' );
314
+ $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ) );
315
  }
316
  }
317
 
327
  // Security check as 'wp_update_term' can be called from outside WP admin
328
  check_admin_referer( 'pll_language', '_pll_nonce' );
329
 
330
+ $translations = array();
331
+
332
  // Save translations after checking the translated term is in the right language ( as well as cast id to int )
333
+ if ( isset( $_POST['term_tr_lang'] ) ) {
334
+ foreach ( array_map( 'absint', $_POST['term_tr_lang'] ) as $lang => $tr_id ) {
335
+ $tr_lang = $this->model->term->get_language( $tr_id );
336
+ $translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? $tr_id : 0;
337
+ }
338
  }
339
 
340
  $this->model->term->save_translations( $term_id, $translations );
362
  // As 'wp_update_term' can be called from outside WP admin
363
  // 2nd test for creating tags when creating / editing a post
364
  $tax = get_taxonomy( $taxonomy );
365
+ if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
366
  $this->save_language( $term_id, $taxonomy );
367
 
368
+ if ( isset( $_POST['term_tr_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
369
  $translations = $this->save_translations( $term_id );
370
  }
371
  }
397
 
398
  // If the term already exists in another language
399
  if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
400
+ if ( isset( $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
401
+ $slug = $name . '-' . $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) )->slug; // phpcs:ignore WordPress.Security.NonceVerification
402
  }
403
 
404
+ elseif ( isset( $_POST['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
405
+ $slug = $name . '-' . $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) )->slug; // phpcs:ignore WordPress.Security.NonceVerification
406
  }
407
 
408
  // *Post* bulk edit, in case a new term is created
409
+ elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
410
  // Bulk edit does not modify the language
411
+ if ( -1 == $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification
412
  $slug = $name . '-' . $this->model->post->get_language( $this->post_id )->slug;
413
  } else {
414
+ $slug = $name . '-' . $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) )->slug; // phpcs:ignore WordPress.Security.NonceVerification
415
  }
416
  }
417
  }
427
  public function term_lang_choice() {
428
  check_ajax_referer( 'pll_language', '_pll_nonce' );
429
 
430
+ if ( ! isset( $_POST['taxonomy'], $_POST['post_type'], $_POST['lang'] ) ) {
431
+ wp_die( 0 );
432
+ }
433
+
434
+ $lang = $this->model->get_language( sanitize_key( $_POST['lang'] ) );
435
  $term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : null;
436
+ $taxonomy = sanitize_key( $_POST['taxonomy'] );
437
+ $post_type = sanitize_key( $_POST['post_type'] );
438
 
439
  if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
440
+ wp_die( 0 );
441
  }
442
 
443
  ob_start();
457
  'name' => 'parent',
458
  'orderby' => 'name',
459
  'hierarchical' => true,
460
+ 'show_option_none' => __( 'None', 'polylang' ),
461
  'echo' => 0,
462
  );
463
  $x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) );
494
  public function ajax_terms_not_translated() {
495
  check_ajax_referer( 'pll_language', '_pll_nonce' );
496
 
497
+ if ( ! isset( $_GET['term'], $_GET['post_type'], $_GET['taxonomy'], $_GET['term_language'], $_GET['translation_language'] ) ) {
498
+ wp_die( 0 );
499
+ }
500
+
501
+ $s = wp_unslash( $_GET['term'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
502
+ $post_type = sanitize_key( $_GET['post_type'] );
503
+ $taxonomy = sanitize_key( $_GET['taxonomy'] );
504
 
505
  if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
506
+ wp_die( 0 );
507
  }
508
 
509
+ $term_language = $this->model->get_language( sanitize_key( $_GET['term_language'] ) );
510
+ $translation_language = $this->model->get_language( sanitize_key( $_GET['translation_language'] ) );
511
 
512
  $return = array();
513
 
525
  }
526
 
527
  // Add current translation in list
528
+ // Not in add term as term_id is not set
529
+ if ( isset( $_GET['term_id'] ) && 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) {
530
  $term = get_term( $term_id, $taxonomy );
531
  array_unshift(
532
  $return,
538
  );
539
  }
540
 
541
+ wp_die( wp_json_encode( $return ) );
542
  }
543
 
544
  /**
615
  $translations[ $key ] = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id );
616
 
617
  // Hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
618
+ if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) { // phpcs:ignore WordPress.Security.NonceVerification
619
  $_POST['term_tr_lang'][ $key ] = $translations[ $key ];
620
  }
621
  }
admin/admin-filters.php CHANGED
@@ -57,24 +57,27 @@ class PLL_Admin_Filters extends PLL_Filters {
57
  $screen = get_current_screen();
58
 
59
  // Test the Widgets screen and the Customizer to avoid displaying the option in page builders
60
- // Saving the widget reloads the form. And curiously the action is in $_REQUEST but neither in $_POST, not in $_GET.
61
- if ( ( isset( $screen ) && 'widgets' === $screen->base ) || ( isset( $_REQUEST['action'] ) && 'save-widget' === $_REQUEST['action'] ) || isset( $GLOBALS['wp_customize'] ) ) {
62
  $dropdown = new PLL_Walker_Dropdown();
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  printf(
64
  '<p><label for="%1$s">%2$s %3$s</label></p>',
65
  esc_attr( $widget->id . '_lang_choice' ),
66
  esc_html__( 'The widget is displayed for:', 'polylang' ),
67
- $dropdown->walk(
68
- array_merge(
69
- array( (object) array( 'slug' => 0, 'name' => __( 'All languages', 'polylang' ) ) ),
70
- $this->model->get_languages_list()
71
- ),
72
- array(
73
- 'name' => $widget->id . '_lang_choice',
74
- 'class' => 'tags-input pll-lang-choice',
75
- 'selected' => empty( $instance['pll_lang'] ) ? '' : $instance['pll_lang'],
76
- )
77
- )
78
  );
79
  }
80
  }
@@ -92,8 +95,10 @@ class PLL_Admin_Filters extends PLL_Filters {
92
  * @return array Widget options
93
  */
94
  public function widget_update_callback( $instance, $new_instance, $old_instance, $widget ) {
95
- if ( ! empty( $_POST[ $key = $widget->id . '_lang_choice' ] ) && in_array( $_POST[ $key ], $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) ) {
96
- $instance['pll_lang'] = $_POST[ $key ];
 
 
97
  } else {
98
  unset( $instance['pll_lang'] );
99
  }
@@ -112,7 +117,7 @@ class PLL_Admin_Filters extends PLL_Filters {
112
  // Biography translations
113
  foreach ( $this->model->get_languages_list() as $lang ) {
114
  $meta = $lang->slug == $this->options['default_lang'] ? 'description' : 'description_' . $lang->slug;
115
- $description = empty( $_POST[ 'description_' . $lang->slug ] ) ? '' : trim( $_POST[ 'description_' . $lang->slug ] );
116
 
117
  /** This filter is documented in wp-includes/user.php */
118
  $description = apply_filters( 'pre_user_description', $description ); // Applies WP default filter wp_filter_kses
@@ -183,11 +188,11 @@ class PLL_Admin_Filters extends PLL_Filters {
183
  * @return string
184
  */
185
  public function get_locale( $locale ) {
186
- if ( isset( $_POST['post_lang_choice'] ) && $lang = $this->model->get_language( $_POST['post_lang_choice'] ) ) {
187
  $locale = $lang->locale;
188
- } elseif ( isset( $_POST['term_lang_choice'] ) && $lang = $this->model->get_language( $_POST['term_lang_choice'] ) ) {
189
  $locale = $lang->locale;
190
- } elseif ( isset( $_POST['inline_lang_choice'] ) && $lang = $this->model->get_language( $_POST['inline_lang_choice'] ) ) {
191
  $locale = $lang->locale;
192
  } elseif ( ! empty( $this->curlang ) ) {
193
  $locale = $this->curlang->locale;
57
  $screen = get_current_screen();
58
 
59
  // Test the Widgets screen and the Customizer to avoid displaying the option in page builders
60
+ // Saving the widget reloads the form. And curiously the action is in $_REQUEST but neither in $_POST, nor in $_GET.
61
+ if ( ( isset( $screen ) && 'widgets' === $screen->base ) || ( isset( $_REQUEST['action'] ) && 'save-widget' === $_REQUEST['action'] ) || isset( $GLOBALS['wp_customize'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
62
  $dropdown = new PLL_Walker_Dropdown();
63
+
64
+ $dropdown_html = $dropdown->walk(
65
+ array_merge(
66
+ array( (object) array( 'slug' => 0, 'name' => __( 'All languages', 'polylang' ) ) ),
67
+ $this->model->get_languages_list()
68
+ ),
69
+ array(
70
+ 'name' => $widget->id . '_lang_choice',
71
+ 'class' => 'tags-input pll-lang-choice',
72
+ 'selected' => empty( $instance['pll_lang'] ) ? '' : $instance['pll_lang'],
73
+ )
74
+ );
75
+
76
  printf(
77
  '<p><label for="%1$s">%2$s %3$s</label></p>',
78
  esc_attr( $widget->id . '_lang_choice' ),
79
  esc_html__( 'The widget is displayed for:', 'polylang' ),
80
+ $dropdown_html // phpcs:ignore WordPress.Security.EscapeOutput
 
 
 
 
 
 
 
 
 
 
81
  );
82
  }
83
  }
95
  * @return array Widget options
96
  */
97
  public function widget_update_callback( $instance, $new_instance, $old_instance, $widget ) {
98
+ $key = $widget->id . '_lang_choice';
99
+
100
+ if ( ! empty( $_POST[ $key ] ) && $lang = $this->model->get_language( sanitize_key( $_POST[ $key ] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
101
+ $instance['pll_lang'] = $lang->slug;
102
  } else {
103
  unset( $instance['pll_lang'] );
104
  }
117
  // Biography translations
118
  foreach ( $this->model->get_languages_list() as $lang ) {
119
  $meta = $lang->slug == $this->options['default_lang'] ? 'description' : 'description_' . $lang->slug;
120
+ $description = empty( $_POST[ 'description_' . $lang->slug ] ) ? '' : trim( $_POST[ 'description_' . $lang->slug ] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
121
 
122
  /** This filter is documented in wp-includes/user.php */
123
  $description = apply_filters( 'pre_user_description', $description ); // Applies WP default filter wp_filter_kses
188
  * @return string
189
  */
190
  public function get_locale( $locale ) {
191
+ if ( isset( $_POST['post_lang_choice'] ) && $lang = $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
192
  $locale = $lang->locale;
193
+ } elseif ( isset( $_POST['term_lang_choice'] ) && $lang = $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
194
  $locale = $lang->locale;
195
+ } elseif ( isset( $_POST['inline_lang_choice'] ) && $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
196
  $locale = $lang->locale;
197
  } elseif ( ! empty( $this->curlang ) ) {
198
  $locale = $this->curlang->locale;
admin/admin-links.php CHANGED
@@ -7,16 +7,63 @@
7
  */
8
  class PLL_Admin_Links extends PLL_Links {
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  /**
11
  * Get the link to create a new post translation
12
  *
13
  * @since 1.5
14
  *
15
- * @param int $post_id the source post id
16
- * @param object $language the language of the new translation
 
 
17
  * @return string
18
  */
19
- public function get_new_post_translation_link( $post_id, $language ) {
20
  $post_type = get_post_type( $post_id );
21
  $post_type_object = get_post_type_object( get_post_type( $post_id ) );
22
  if ( ! current_user_can( $post_type_object->cap->create_posts ) ) {
@@ -31,15 +78,21 @@ class PLL_Admin_Links extends PLL_Links {
31
  }
32
  }
33
 
34
- if ( 'attachment' == $post_type ) {
35
  $args = array(
36
  'action' => 'translate_media',
37
  'from_media' => $post_id,
38
  'new_lang' => $language->slug,
39
  );
40
 
 
 
41
  // Add nonce for media as we will directly publish a new attachment from a click on this link
42
- $link = wp_nonce_url( add_query_arg( $args, admin_url( 'admin.php' ) ), 'translate_media' );
 
 
 
 
43
  } else {
44
  $args = array(
45
  'post_type' => $post_type,
@@ -47,7 +100,13 @@ class PLL_Admin_Links extends PLL_Links {
47
  'new_lang' => $language->slug,
48
  );
49
 
50
- $link = wp_nonce_url( add_query_arg( $args, admin_url( 'post-new.php' ) ), 'new-post-translation' );
 
 
 
 
 
 
51
  }
52
 
53
  /**
@@ -73,12 +132,7 @@ class PLL_Admin_Links extends PLL_Links {
73
  */
74
  public function new_post_translation_link( $post_id, $language ) {
75
  $link = $this->get_new_post_translation_link( $post_id, $language );
76
- return $link ? sprintf(
77
- '<a href="%1$s" class="pll_icon_add"><span class="screen-reader-text">%2$s</span></a>',
78
- esc_url( $link ),
79
- /* translators: accessibility text, %s is a native language name */
80
- esc_html( sprintf( __( 'Add a translation in %s', 'polylang' ), $language->name ) )
81
- ) : '';
82
  }
83
 
84
  /**
@@ -92,12 +146,7 @@ class PLL_Admin_Links extends PLL_Links {
92
  public function edit_post_translation_link( $post_id ) {
93
  $link = get_edit_post_link( $post_id );
94
  $language = $this->model->post->get_language( $post_id );
95
- return $link ? sprintf(
96
- '<a href="%1$s" class="pll_icon_edit"><span class="screen-reader-text">%2$s</span></a>',
97
- esc_url( $link ),
98
- /* translators: accessibility text, %s is a native language name */
99
- esc_html( sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ) )
100
- ) : '';
101
  }
102
 
103
  /**
@@ -153,12 +202,7 @@ class PLL_Admin_Links extends PLL_Links {
153
  */
154
  public function new_term_translation_link( $term_id, $taxonomy, $post_type, $language ) {
155
  $link = $this->get_new_term_translation_link( $term_id, $taxonomy, $post_type, $language );
156
- return $link ? sprintf(
157
- '<a href="%1$s" class="pll_icon_add"><span class="screen-reader-text">%2$s</span></a>',
158
- esc_url( $link ),
159
- /* translators: accessibility text, %s is a native language name */
160
- esc_html( sprintf( __( 'Add a translation in %s', 'polylang' ), $language->name ) )
161
- ) : '';
162
  }
163
 
164
  /**
@@ -174,11 +218,6 @@ class PLL_Admin_Links extends PLL_Links {
174
  public function edit_term_translation_link( $term_id, $taxonomy, $post_type ) {
175
  $link = get_edit_term_link( $term_id, $taxonomy, $post_type );
176
  $language = $this->model->term->get_language( $term_id );
177
- return $link ? sprintf(
178
- '<a href="%1$s" class="pll_icon_edit"><span class="screen-reader-text">%2$s</span></a>',
179
- esc_url( $link ),
180
- /* translators: accessibility text, %s is a native language name */
181
- esc_html( sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ) )
182
- ) : '';
183
  }
184
  }
7
  */
8
  class PLL_Admin_Links extends PLL_Links {
9
 
10
+ /**
11
+ * Returns html markup for a new translation link
12
+ *
13
+ * @since 2.6
14
+ *
15
+ * @param string $link The new translation link.
16
+ * @param object $language The language of the new translation.
17
+ * @return string
18
+ */
19
+ protected function new_translation_link( $link, $language ) {
20
+ $str = '';
21
+
22
+ if ( $link ) {
23
+ /* translators: accessibility text, %s is a native language name */
24
+ $hint = sprintf( __( 'Add a translation in %s', 'polylang' ), $language->name );
25
+
26
+ $str = sprintf(
27
+ '<a href="%1$s" title="%2$s" class="pll_icon_add"><span class="screen-reader-text">%3$s</span></a>',
28
+ esc_url( $link ),
29
+ esc_attr( $hint ),
30
+ esc_html( $hint )
31
+ );
32
+ }
33
+
34
+ return $str;
35
+ }
36
+
37
+ /**
38
+ * Returns html markup for a translation link
39
+ *
40
+ * @since 2.6
41
+ *
42
+ * @param string $link The translation link.
43
+ * @param object $language The language of the translation.
44
+ * @return string
45
+ */
46
+ public function edit_translation_link( $link, $language ) {
47
+ return $link ? sprintf(
48
+ '<a href="%1$s" class="pll_icon_edit"><span class="screen-reader-text">%2$s</span></a>',
49
+ esc_url( $link ),
50
+ /* translators: accessibility text, %s is a native language name */
51
+ esc_html( sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ) )
52
+ ) : '';
53
+ }
54
+
55
  /**
56
  * Get the link to create a new post translation
57
  *
58
  * @since 1.5
59
  *
60
+ * @param int $post_id The source post id.
61
+ * @param object $language The language of the new translation.
62
+ * @param string $context Optional. Defaults to 'display' which encodes '&' to '&amp;'.
63
+ * Otherwise, preserves '&'.
64
  * @return string
65
  */
66
+ public function get_new_post_translation_link( $post_id, $language, $context = 'display' ) {
67
  $post_type = get_post_type( $post_id );
68
  $post_type_object = get_post_type_object( get_post_type( $post_id ) );
69
  if ( ! current_user_can( $post_type_object->cap->create_posts ) ) {
78
  }
79
  }
80
 
81
+ if ( 'attachment' === $post_type ) {
82
  $args = array(
83
  'action' => 'translate_media',
84
  'from_media' => $post_id,
85
  'new_lang' => $language->slug,
86
  );
87
 
88
+ $link = add_query_arg( $args, admin_url( 'admin.php' ) );
89
+
90
  // Add nonce for media as we will directly publish a new attachment from a click on this link
91
+ if ( 'display' === $context ) {
92
+ $link = wp_nonce_url( $link, 'translate_media' );
93
+ } else {
94
+ $link = add_query_arg( '_wpnonce', wp_create_nonce( 'translate_media' ), $link );
95
+ }
96
  } else {
97
  $args = array(
98
  'post_type' => $post_type,
100
  'new_lang' => $language->slug,
101
  );
102
 
103
+ $link = add_query_arg( $args, admin_url( 'post-new.php' ) );
104
+
105
+ if ( 'display' === $context ) {
106
+ $link = wp_nonce_url( $link, 'new-post-translation' );
107
+ } else {
108
+ $link = add_query_arg( '_wpnonce', wp_create_nonce( 'new-post-translation' ), $link );
109
+ }
110
  }
111
 
112
  /**
132
  */
133
  public function new_post_translation_link( $post_id, $language ) {
134
  $link = $this->get_new_post_translation_link( $post_id, $language );
135
+ return $this->new_translation_link( $link, $language );
 
 
 
 
 
136
  }
137
 
138
  /**
146
  public function edit_post_translation_link( $post_id ) {
147
  $link = get_edit_post_link( $post_id );
148
  $language = $this->model->post->get_language( $post_id );
149
+ return $this->edit_translation_link( $link, $language );
 
 
 
 
 
150
  }
151
 
152
  /**
202
  */
203
  public function new_term_translation_link( $term_id, $taxonomy, $post_type, $language ) {
204
  $link = $this->get_new_term_translation_link( $term_id, $taxonomy, $post_type, $language );
205
+ return $this->new_translation_link( $link, $language );
 
 
 
 
 
206
  }
207
 
208
  /**
218
  public function edit_term_translation_link( $term_id, $taxonomy, $post_type ) {
219
  $link = get_edit_term_link( $term_id, $taxonomy, $post_type );
220
  $language = $this->model->term->get_language( $term_id );
221
+ return $this->edit_translation_link( $link, $language );
 
 
 
 
 
222
  }
223
  }
admin/admin-model.php CHANGED
@@ -28,17 +28,17 @@ class PLL_Admin_Model extends PLL_Model {
28
  * @return bool true if success / false if failed
29
  */
30
  public function add_language( $args ) {
31
- if ( ! $this->validate_lang( $args ) ) {
32
- return false;
 
33
  }
34
 
35
  // First the language taxonomy
36
- $description = serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
37
  $r = wp_insert_term( $args['name'], 'language', array( 'slug' => $args['slug'], 'description' => $description ) );
38
  if ( is_wp_error( $r ) ) {
39
  // Avoid an ugly fatal error if something went wrong ( reported once in the forum )
40
- add_settings_error( 'general', 'pll_add_language', __( 'Impossible to add the language.', 'polylang' ) );
41
- return false;
42
  }
43
  wp_update_term( (int) $r['term_id'], 'language', array( 'term_group' => (int) $args['term_group'] ) ); // can't set the term group directly in wp_insert_term
44
 
@@ -74,8 +74,6 @@ class PLL_Admin_Model extends PLL_Model {
74
 
75
  $this->clean_languages_cache(); // Again to set add mo_id in the cached languages list
76
  flush_rewrite_rules(); // Refresh rewrite rules
77
-
78
- add_settings_error( 'general', 'pll_languages_created', __( 'Language added.', 'polylang' ), 'updated' );
79
  return true;
80
  }
81
 
@@ -90,7 +88,7 @@ class PLL_Admin_Model extends PLL_Model {
90
  $lang = $this->get_language( (int) $lang_id );
91
 
92
  if ( empty( $lang ) ) {
93
- return;
94
  }
95
 
96
  // Oops ! we are deleting the default language...
@@ -157,7 +155,7 @@ class PLL_Admin_Model extends PLL_Model {
157
 
158
  update_option( 'polylang', $this->options );
159
  flush_rewrite_rules(); // refresh rewrite rules
160
- add_settings_error( 'general', 'pll_languages_deleted', __( 'Language deleted.', 'polylang' ), 'updated' );
161
  }
162
 
163
  /**
@@ -181,8 +179,10 @@ class PLL_Admin_Model extends PLL_Model {
181
  */
182
  public function update_language( $args ) {
183
  $lang = $this->get_language( (int) $args['lang_id'] );
184
- if ( ! $this->validate_lang( $args, $lang ) ) {
185
- return false;
 
 
186
  }
187
 
188
  // Update links to this language in posts and terms in case the slug has been modified
@@ -235,7 +235,7 @@ class PLL_Admin_Model extends PLL_Model {
235
  update_option( 'polylang', $this->options );
236
 
237
  // And finally update the language itself
238
- $description = serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
239
  wp_update_term( (int) $lang->term_id, 'language', array( 'slug' => $slug, 'name' => $args['name'], 'description' => $description, 'term_group' => (int) $args['term_group'] ) );
240
  wp_update_term( (int) $lang->tl_term_id, 'term_language', array( 'slug' => 'pll_' . $slug, 'name' => $args['name'] ) );
241
 
@@ -250,7 +250,6 @@ class PLL_Admin_Model extends PLL_Model {
250
 
251
  $this->clean_languages_cache();
252
  flush_rewrite_rules(); // Refresh rewrite rules
253
- add_settings_error( 'general', 'pll_languages_updated', __( 'Language updated.', 'polylang' ), 'updated' );
254
  return true;
255
  }
256
 
@@ -266,35 +265,45 @@ class PLL_Admin_Model extends PLL_Model {
266
  * @return bool true if success / false if failed
267
  */
268
  protected function validate_lang( $args, $lang = null ) {
 
 
269
  // Validate locale with the same pattern as WP 4.3. See #28303
270
  if ( ! preg_match( '#^[a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?$#', $args['locale'], $matches ) ) {
271
- add_settings_error( 'general', 'pll_invalid_locale', __( 'Enter a valid WordPress locale', 'polylang' ) );
272
  }
273
 
274
  // Validate slug characters
275
  if ( ! preg_match( '#^[a-z_-]+$#', $args['slug'] ) ) {
276
- add_settings_error( 'general', 'pll_invalid_slug', __( 'The language code contains invalid characters', 'polylang' ) );
277
  }
278
 
279
  // Validate slug is unique
280
  foreach ( $this->get_languages_list() as $language ) {
281
  if ( $language->slug === $args['slug'] && ( null === $lang || ( isset( $lang ) && $lang->term_id != $language->term_id ) ) ) {
282
- add_settings_error( 'general', 'pll_non_unique_slug', __( 'The language code must be unique', 'polylang' ) );
283
  }
284
  }
285
 
286
  // Validate name
287
  // No need to sanitize it as wp_insert_term will do it for us
288
  if ( empty( $args['name'] ) ) {
289
- add_settings_error( 'general', 'pll_invalid_name', __( 'The language must have a name', 'polylang' ) );
290
  }
291
 
292
  // Validate flag
293
  if ( ! empty( $args['flag'] ) && ! file_exists( POLYLANG_DIR . '/flags/' . $args['flag'] . '.png' ) ) {
294
- add_settings_error( 'general', 'pll_invalid_flag', __( 'The flag does not exist', 'polylang' ) );
 
 
 
 
 
 
 
 
295
  }
296
 
297
- return get_settings_errors() ? false : true;
298
  }
299
 
300
  /**
@@ -355,7 +364,7 @@ class PLL_Admin_Model extends PLL_Model {
355
  $term = uniqid( 'pll_' ); // the term name
356
  $terms[] = $wpdb->prepare( '( %s, %s )', $term, $term );
357
  $slugs[] = $wpdb->prepare( '%s', $term );
358
- $description[ $term ] = serialize( $t );
359
  $count[ $term ] = count( $t );
360
  }
361
 
@@ -386,7 +395,7 @@ class PLL_Admin_Model extends PLL_Model {
386
 
387
  // Prepare objects relationships
388
  foreach ( $terms as $term ) {
389
- $t = unserialize( $term->description );
390
  if ( in_array( $t, $translations ) ) {
391
  foreach ( $t as $object_id ) {
392
  if ( ! empty( $object_id ) ) {
@@ -446,7 +455,7 @@ class PLL_Admin_Model extends PLL_Model {
446
  )
447
  );
448
 
449
- // PHPCS:disable WordPress.DB.PreparedSQL.NotPrepared
450
  $terms = $wpdb->get_col(
451
  sprintf(
452
  "SELECT {$wpdb->term_taxonomy}.term_id FROM {$wpdb->term_taxonomy}
@@ -487,7 +496,7 @@ class PLL_Admin_Model extends PLL_Model {
487
 
488
  foreach ( $terms as $term ) {
489
  $term_ids[ $term->taxonomy ][] = $term->term_id;
490
- $tr = unserialize( $term->description );
491
  if ( ! empty( $tr[ $old_slug ] ) ) {
492
  if ( $new_slug ) {
493
  $tr[ $new_slug ] = $tr[ $old_slug ]; // Suppress this for delete
@@ -501,7 +510,7 @@ class PLL_Admin_Model extends PLL_Model {
501
  $dt['t'][] = (int) $term->term_id;
502
  $dt['tt'][] = (int) $term->term_taxonomy_id;
503
  } else {
504
- $ut['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $term->term_id, serialize( $tr ) );
505
  $ut['in'][] = (int) $term->term_id;
506
  }
507
  }
28
  * @return bool true if success / false if failed
29
  */
30
  public function add_language( $args ) {
31
+ $errors = $this->validate_lang( $args );
32
+ if ( $errors->get_error_code() ) { // Using has_errors() would be more meaningful but is available only since WP 5.0
33
+ return $errors;
34
  }
35
 
36
  // First the language taxonomy
37
+ $description = maybe_serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
38
  $r = wp_insert_term( $args['name'], 'language', array( 'slug' => $args['slug'], 'description' => $description ) );
39
  if ( is_wp_error( $r ) ) {
40
  // Avoid an ugly fatal error if something went wrong ( reported once in the forum )
41
+ return new WP_Error( 'pll_add_language', __( 'Impossible to add the language.', 'polylang' ) );
 
42
  }
43
  wp_update_term( (int) $r['term_id'], 'language', array( 'term_group' => (int) $args['term_group'] ) ); // can't set the term group directly in wp_insert_term
44
 
74
 
75
  $this->clean_languages_cache(); // Again to set add mo_id in the cached languages list
76
  flush_rewrite_rules(); // Refresh rewrite rules
 
 
77
  return true;
78
  }
79
 
88
  $lang = $this->get_language( (int) $lang_id );
89
 
90
  if ( empty( $lang ) ) {
91
+ return false;
92
  }
93
 
94
  // Oops ! we are deleting the default language...
155
 
156
  update_option( 'polylang', $this->options );
157
  flush_rewrite_rules(); // refresh rewrite rules
158
+ return true;
159
  }
160
 
161
  /**
179
  */
180
  public function update_language( $args ) {
181
  $lang = $this->get_language( (int) $args['lang_id'] );
182
+
183
+ $errors = $this->validate_lang( $args, $lang );
184
+ if ( $errors->get_error_code() ) { // Using has_errors() would be more meaningful but is available only since WP 5.0
185
+ return $errors;
186
  }
187
 
188
  // Update links to this language in posts and terms in case the slug has been modified
235
  update_option( 'polylang', $this->options );
236
 
237
  // And finally update the language itself
238
+ $description = maybe_serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
239
  wp_update_term( (int) $lang->term_id, 'language', array( 'slug' => $slug, 'name' => $args['name'], 'description' => $description, 'term_group' => (int) $args['term_group'] ) );
240
  wp_update_term( (int) $lang->tl_term_id, 'term_language', array( 'slug' => 'pll_' . $slug, 'name' => $args['name'] ) );
241
 
250
 
251
  $this->clean_languages_cache();
252
  flush_rewrite_rules(); // Refresh rewrite rules
 
253
  return true;
254
  }
255
 
265
  * @return bool true if success / false if failed
266
  */
267
  protected function validate_lang( $args, $lang = null ) {
268
+ $errors = new WP_Error();
269
+
270
  // Validate locale with the same pattern as WP 4.3. See #28303
271
  if ( ! preg_match( '#^[a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?$#', $args['locale'], $matches ) ) {
272
+ $errors->add( 'pll_invalid_locale', __( 'Enter a valid WordPress locale', 'polylang' ) );
273
  }
274
 
275
  // Validate slug characters
276
  if ( ! preg_match( '#^[a-z_-]+$#', $args['slug'] ) ) {
277
+ $errors->add( 'pll_invalid_slug', __( 'The language code contains invalid characters', 'polylang' ) );
278
  }
279
 
280
  // Validate slug is unique
281
  foreach ( $this->get_languages_list() as $language ) {
282
  if ( $language->slug === $args['slug'] && ( null === $lang || ( isset( $lang ) && $lang->term_id != $language->term_id ) ) ) {
283
+ $errors->add( 'pll_non_unique_slug', __( 'The language code must be unique', 'polylang' ) );
284
  }
285
  }
286
 
287
  // Validate name
288
  // No need to sanitize it as wp_insert_term will do it for us
289
  if ( empty( $args['name'] ) ) {
290
+ $errors->add( 'pll_invalid_name', __( 'The language must have a name', 'polylang' ) );
291
  }
292
 
293
  // Validate flag
294
  if ( ! empty( $args['flag'] ) && ! file_exists( POLYLANG_DIR . '/flags/' . $args['flag'] . '.png' ) ) {
295
+ $flag = PLL_Language::get_flag_informations( $args['flag'] );
296
+
297
+ if ( ! empty( $flag['url'] ) ) {
298
+ $response = function_exists( 'vip_safe_wp_remote_get' ) ? vip_safe_wp_remote_get( esc_url_raw( $flag['url'] ) ) : wp_remote_get( esc_url_raw( $flag['url'] ) );
299
+ }
300
+
301
+ if ( empty( $response ) || is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
302
+ $errors->add( 'pll_invalid_flag', __( 'The flag does not exist', 'polylang' ) );
303
+ }
304
  }
305
 
306
+ return $errors;
307
  }
308
 
309
  /**
364
  $term = uniqid( 'pll_' ); // the term name
365
  $terms[] = $wpdb->prepare( '( %s, %s )', $term, $term );
366
  $slugs[] = $wpdb->prepare( '%s', $term );
367
+ $description[ $term ] = maybe_serialize( $t );
368
  $count[ $term ] = count( $t );
369
  }
370
 
395
 
396
  // Prepare objects relationships
397
  foreach ( $terms as $term ) {
398
+ $t = maybe_unserialize( $term->description );
399
  if ( in_array( $t, $translations ) ) {
400
  foreach ( $t as $object_id ) {
401
  if ( ! empty( $object_id ) ) {
455
  )
456
  );
457
 
458
+ // PHPCS:disable WordPress.DB.PreparedSQL
459
  $terms = $wpdb->get_col(
460
  sprintf(
461
  "SELECT {$wpdb->term_taxonomy}.term_id FROM {$wpdb->term_taxonomy}
496
 
497
  foreach ( $terms as $term ) {
498
  $term_ids[ $term->taxonomy ][] = $term->term_id;
499
+ $tr = maybe_unserialize( $term->description );
500
  if ( ! empty( $tr[ $old_slug ] ) ) {
501
  if ( $new_slug ) {
502
  $tr[ $new_slug ] = $tr[ $old_slug ]; // Suppress this for delete
510
  $dt['t'][] = (int) $term->term_id;
511
  $dt['tt'][] = (int) $term->term_taxonomy_id;
512
  } else {
513
+ $ut['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $term->term_id, maybe_serialize( $tr ) );
514
  $ut['in'][] = (int) $term->term_id;
515
  }
516
  }
admin/admin-nav-menu.php CHANGED
@@ -17,8 +17,6 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
17
  public function __construct( &$polylang ) {
18
  parent::__construct( $polylang );
19
 
20
- $this->theme = get_option( 'stylesheet' );
21
-
22
  // Populates nav menus locations
23
  // Since WP 4.4, must be done before customize_register is fired
24
  add_filter( 'theme_mod_nav_menu_locations', array( $this, 'theme_mod_nav_menu_locations' ), 20 );
@@ -36,15 +34,11 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
36
  public function admin_init() {
37
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
38
  add_action( 'wp_update_nav_menu_item', array( $this, 'wp_update_nav_menu_item' ), 10, 2 );
39
- add_filter( 'wp_get_nav_menu_items', array( $this, 'translate_switcher_title' ) );
40
 
41
  // Translation of menus based on chosen locations
42
  add_filter( 'pre_update_option_theme_mods_' . $this->theme, array( $this, 'pre_update_option_theme_mods' ) );
43
  add_action( 'delete_nav_menu', array( $this, 'delete_nav_menu' ) );
44
 
45
- // Filter _wp_auto_add_pages_to_menu by language
46
- add_action( 'transition_post_status', array( $this, 'auto_add_pages_to_menu' ), 5, 3 ); // before _wp_auto_add_pages_to_menu
47
-
48
  // FIXME is it possible to choose the order ( after theme locations in WP3.5 and older ) ?
49
  // FIXME not displayed if Polylang is activated before the first time the user goes to nav menus http://core.trac.wordpress.org/ticket/16828
50
  add_meta_box( 'pll_lang_switch_box', __( 'Language switcher', 'polylang' ), array( $this, 'lang_switch' ), 'nav-menus', 'side', 'high' );
@@ -68,17 +62,17 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
68
  <ul id="lang-switch-checklist" class="categorychecklist form-no-clear">
69
  <li>
70
  <label class="menu-item-title">
71
- <input type="checkbox" class="menu-item-checkbox" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-object-id]" value="-1"> <?php esc_html_e( 'Language switcher', 'polylang' ); ?>
72
  </label>
73
- <input type="hidden" class="menu-item-type" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-type]" value="custom">
74
- <input type="hidden" class="menu-item-title" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-title]" value="<?php esc_html_e( 'Language switcher', 'polylang' ); ?>">
75
- <input type="hidden" class="menu-item-url" name="menu-item[<?php echo $_nav_menu_placeholder; ?>][menu-item-url]" value="#pll_switcher">
76
  </li>
77
  </ul>
78
  </div>
79
  <p class="button-controls">
80
  <span class="add-to-menu">
81
- <input type="submit" <?php disabled( $nav_menu_selected_id, 0 ); ?> class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-post-type-menu-item" id="submit-posttype-lang-switch">
82
  <span class="spinner"></span>
83
  </span>
84
  </p>
@@ -101,7 +95,7 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
101
  wp_enqueue_script( 'pll_nav_menu', plugins_url( '/js/nav-menu' . $suffix . '.js', POLYLANG_FILE ), array( 'jquery' ), POLYLANG_VERSION );
102
 
103
  $data['strings'] = PLL_Switcher::get_switcher_options( 'menu', 'string' ); // The strings for the options
104
- $data['title'] = __( 'Language switcher', 'polylang' ); // The title
105
 
106
  // Get all language switcher menu items
107
  $items = get_posts(
@@ -133,7 +127,7 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
133
  * @param int $menu_item_db_id
134
  */
135
  public function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0 ) {
136
- if ( empty( $_POST['menu-item-url'][ $menu_item_db_id ] ) || '#pll_switcher' != $_POST['menu-item-url'][ $menu_item_db_id ] ) {
137
  return;
138
  }
139
 
@@ -157,23 +151,6 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
157
  }
158
  }
159
 
160
- /**
161
- * Translates the language switcher menu items title in case the user switches the admin language
162
- *
163
- * @since 1.1.1
164
- *
165
- * @param array $items
166
- * @return array modified $items
167
- */
168
- public function translate_switcher_title( $items ) {
169
- foreach ( $items as $item ) {
170
- if ( '#pll_switcher' == $item->url ) {
171
- $item->post_title = __( 'Language switcher', 'polylang' );
172
- }
173
- }
174
- return $items;
175
- }
176
-
177
  /**
178
  * Assign menu languages and translations based on ( temporary ) locations
179
  *
@@ -210,14 +187,14 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
210
  if ( current_user_can( 'edit_theme_options' ) && isset( $mods['nav_menu_locations'] ) ) {
211
 
212
  // Manage Locations tab in Appearance -> Menus
213
- if ( isset( $_GET['action'] ) && 'locations' == $_GET['action'] ) {
214
  check_admin_referer( 'save-menu-locations' );
215
  $this->options['nav_menus'][ $this->theme ] = array();
216
  }
217
 
218
  // Edit Menus tab in Appearance -> Menus
219
  // Add the test of $_POST['update-nav-menu-nonce'] to avoid conflict with Vantage theme
220
- elseif ( isset( $_POST['action'], $_POST['update-nav-menu-nonce'] ) && 'update' == $_POST['action'] ) {
221
  check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' );
222
  $this->options['nav_menus'][ $this->theme ] = array();
223
  }
@@ -289,48 +266,4 @@ class PLL_Admin_Nav_Menu extends PLL_Nav_Menu {
289
  update_option( 'polylang', $this->options );
290
  }
291
  }
292
-
293
- /**
294
- * Filters the option nav_menu_options for auto added pages to menu
295
- *
296
- * @since 0.9.4
297
- *
298
- * @param array $options
299
- * @return array Modified options
300
- */
301
- public function nav_menu_options( $options ) {
302
- $options['auto_add'] = array_intersect( $options['auto_add'], $this->auto_add_menus );
303
- return $options;
304
- }
305
-
306
- /**
307
- * Filters _wp_auto_add_pages_to_menu by language
308
- *
309
- * @since 0.9.4
310
- *
311
- * @param string $new_status Transition to this post status.
312
- * @param string $old_status Previous post status.
313
- * @param object $post Post data.
314
- */
315
- public function auto_add_pages_to_menu( $new_status, $old_status, $post ) {
316
- if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type || ! empty( $post->post_parent ) ) {
317
- return;
318
- }
319
-
320
- if ( ! empty( $this->options['nav_menus'][ $this->theme ] ) ) {
321
- $this->auto_add_menus = array();
322
-
323
- $lang = $this->model->post->get_language( $post->ID );
324
- $lang = empty( $lang ) ? $this->options['default_lang'] : $lang->slug; // If the page has no language yet, the default language will be assigned
325
-
326
- // Get all the menus in the page language
327
- foreach ( $this->options['nav_menus'][ $this->theme ] as $loc ) {
328
- if ( ! empty( $loc[ $lang ] ) ) {
329
- $this->auto_add_menus[] = $loc[ $lang ];
330
- }
331
- }
332
-
333
- add_filter( 'option_nav_menu_options', array( $this, 'nav_menu_options' ) );
334
- }
335
- }
336
  }
17
  public function __construct( &$polylang ) {
18
  parent::__construct( $polylang );
19
 
 
 
20
  // Populates nav menus locations
21
  // Since WP 4.4, must be done before customize_register is fired
22
  add_filter( 'theme_mod_nav_menu_locations', array( $this, 'theme_mod_nav_menu_locations' ), 20 );
34
  public function admin_init() {
35
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
36
  add_action( 'wp_update_nav_menu_item', array( $this, 'wp_update_nav_menu_item' ), 10, 2 );
 
37
 
38
  // Translation of menus based on chosen locations
39
  add_filter( 'pre_update_option_theme_mods_' . $this->theme, array( $this, 'pre_update_option_theme_mods' ) );
40
  add_action( 'delete_nav_menu', array( $this, 'delete_nav_menu' ) );
41
 
 
 
 
42
  // FIXME is it possible to choose the order ( after theme locations in WP3.5 and older ) ?
43
  // FIXME not displayed if Polylang is activated before the first time the user goes to nav menus http://core.trac.wordpress.org/ticket/16828
44
  add_meta_box( 'pll_lang_switch_box', __( 'Language switcher', 'polylang' ), array( $this, 'lang_switch' ), 'nav-menus', 'side', 'high' );
62
  <ul id="lang-switch-checklist" class="categorychecklist form-no-clear">
63
  <li>
64
  <label class="menu-item-title">
65
+ <input type="checkbox" class="menu-item-checkbox" name="menu-item[<?php echo (int) $_nav_menu_placeholder; ?>][menu-item-object-id]" value="-1"> <?php esc_html_e( 'Languages', 'polylang' ); ?>
66
  </label>
67
+ <input type="hidden" class="menu-item-type" name="menu-item[<?php echo (int) $_nav_menu_placeholder; ?>][menu-item-type]" value="custom">
68
+ <input type="hidden" class="menu-item-title" name="menu-item[<?php echo (int) $_nav_menu_placeholder; ?>][menu-item-title]" value="<?php esc_html_e( 'Languages', 'polylang' ); ?>">
69
+ <input type="hidden" class="menu-item-url" name="menu-item[<?php echo (int) $_nav_menu_placeholder; ?>][menu-item-url]" value="#pll_switcher">
70
  </li>
71
  </ul>
72
  </div>
73
  <p class="button-controls">
74
  <span class="add-to-menu">
75
+ <input type="submit" <?php disabled( $nav_menu_selected_id, 0 ); ?> class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu', 'polylang' ); ?>" name="add-post-type-menu-item" id="submit-posttype-lang-switch">
76
  <span class="spinner"></span>
77
  </span>
78
  </p>
95
  wp_enqueue_script( 'pll_nav_menu', plugins_url( '/js/nav-menu' . $suffix . '.js', POLYLANG_FILE ), array( 'jquery' ), POLYLANG_VERSION );
96
 
97
  $data['strings'] = PLL_Switcher::get_switcher_options( 'menu', 'string' ); // The strings for the options
98
+ $data['title'] = __( 'Languages', 'polylang' ); // The title
99
 
100
  // Get all language switcher menu items
101
  $items = get_posts(
127
  * @param int $menu_item_db_id
128
  */
129
  public function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0 ) {
130
+ if ( empty( $_POST['menu-item-url'][ $menu_item_db_id ] ) || '#pll_switcher' !== $_POST['menu-item-url'][ $menu_item_db_id ] ) { // phpcs:ignore WordPress.Security.NonceVerification
131
  return;
132
  }
133
 
151
  }
152
  }
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  /**
155
  * Assign menu languages and translations based on ( temporary ) locations
156
  *
187
  if ( current_user_can( 'edit_theme_options' ) && isset( $mods['nav_menu_locations'] ) ) {
188
 
189
  // Manage Locations tab in Appearance -> Menus
190
+ if ( isset( $_GET['action'] ) && 'locations' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
191
  check_admin_referer( 'save-menu-locations' );
192
  $this->options['nav_menus'][ $this->theme ] = array();
193
  }
194
 
195
  // Edit Menus tab in Appearance -> Menus
196
  // Add the test of $_POST['update-nav-menu-nonce'] to avoid conflict with Vantage theme
197
+ elseif ( isset( $_POST['action'], $_POST['update-nav-menu-nonce'] ) && 'update' === $_POST['action'] ) {
198
  check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' );
199
  $this->options['nav_menus'][ $this->theme ] = array();
200
  }
266
  update_option( 'polylang', $this->options );
267
  }
268
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }
admin/admin-notices.php CHANGED
@@ -8,7 +8,7 @@
8
  * @since 2.3.9
9
  */
10
  class PLL_Admin_Notices {
11
- static private $notices = array();
12
 
13
  /**
14
  * Constructor
@@ -155,7 +155,8 @@ class PLL_Admin_Notices {
155
  printf(
156
  '<a class="notice-dismiss" href="%s"><span class="screen-reader-text">%s</span></a>',
157
  esc_url( wp_nonce_url( add_query_arg( 'pll-hide-notice', $name ), $name, '_pll_notice_nonce' ) ),
158
- esc_html__( 'Dismiss this notice.' )
 
159
  );
160
  }
161
 
@@ -173,7 +174,7 @@ class PLL_Admin_Notices {
173
  <?php
174
  printf(
175
  /* translators: %1$s is link start tag, %2$s is link end tag. */
176
- esc_html__( 'We have noticed that you are using Polylang with WooCommerce. We recommend you to use %1$sPolylang for WooCommerce%2$s to ensure the compatibility.', 'polylang' ),
177
  '<a href="https://polylang.pro/downloads/polylang-for-woocommerce/">',
178
  '</a>'
179
  );
@@ -198,7 +199,7 @@ class PLL_Admin_Notices {
198
  <?php
199
  printf(
200
  /* translators: %1$s is link start tag, %2$s is link end tag. */
201
- esc_html__( 'We have noticed that you are using Polylang for some time. We hope that you love it! We would be thrilled if you could %1$sgive us a 5 stars rating%2$s.', 'polylang' ),
202
  '<a href="https://wordpress.org/support/plugin/polylang/reviews/?rate=5#new-post">',
203
  '</a>'
204
  );
8
  * @since 2.3.9
9
  */
10
  class PLL_Admin_Notices {
11
+ private static $notices = array();
12
 
13
  /**
14
  * Constructor
155
  printf(
156
  '<a class="notice-dismiss" href="%s"><span class="screen-reader-text">%s</span></a>',
157
  esc_url( wp_nonce_url( add_query_arg( 'pll-hide-notice', $name ), $name, '_pll_notice_nonce' ) ),
158
+ /* translators: accessibility text */
159
+ esc_html__( 'Dismiss this notice.', 'polylang' )
160
  );
161
  }
162
 
174
  <?php
175
  printf(
176
  /* translators: %1$s is link start tag, %2$s is link end tag. */
177
+ esc_html__( 'We have noticed that you are using Polylang with WooCommerce. To ensure compatibility, we recommend you use %1$sPolylang for WooCommerce%2$s.', 'polylang' ),
178
  '<a href="https://polylang.pro/downloads/polylang-for-woocommerce/">',
179
  '</a>'
180
  );
199
  <?php
200
  printf(
201
  /* translators: %1$s is link start tag, %2$s is link end tag. */
202
+ esc_html__( 'We have noticed that you have been using Polylang for some time. We hope you love it, and we would really appreciate it if you would %1$sgive us a 5 stars rating%2$s.', 'polylang' ),
203
  '<a href="https://wordpress.org/support/plugin/polylang/reviews/?rate=5#new-post">',
204
  '</a>'
205
  );
admin/admin-static-pages.php CHANGED
@@ -6,6 +6,7 @@
6
  * @since 1.8
7
  */
8
  class PLL_Admin_Static_Pages extends PLL_Static_Pages {
 
9
 
10
  /**
11
  * Constructor: setups filters and actions
@@ -17,6 +18,8 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
17
  public function __construct( &$polylang ) {
18
  parent::__construct( $polylang );
19
 
 
 
20
  // Removes the editor and the template select dropdown for pages for posts
21
  add_filter( 'use_block_editor_for_post', array( $this, 'use_block_editor_for_post' ), 10, 2 ); // Since WP 5.0
22
  add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
@@ -33,6 +36,8 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
33
 
34
  // Prevents WP resetting the option
35
  add_filter( 'pre_update_option_show_on_front', array( $this, 'update_show_on_front' ), 10, 2 );
 
 
36
  }
37
 
38
  /**
@@ -87,11 +92,11 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
87
  */
88
  public function display_post_states( $post_states, $post ) {
89
  if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_on_front' ) ) ) ) {
90
- $post_states['page_on_front'] = __( 'Front Page' );
91
  }
92
 
93
  if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ) ) ) {
94
- $post_states['page_for_posts'] = __( 'Posts Page' );
95
  }
96
 
97
  return $post_states;
@@ -184,4 +189,40 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
184
  }
185
  return $value;
186
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
6
  * @since 1.8
7
  */
8
  class PLL_Admin_Static_Pages extends PLL_Static_Pages {
9
+ protected $links;
10
 
11
  /**
12
  * Constructor: setups filters and actions
18
  public function __construct( &$polylang ) {
19
  parent::__construct( $polylang );
20
 
21
+ $this->links = &$polylang->links;
22
+
23
  // Removes the editor and the template select dropdown for pages for posts
24
  add_filter( 'use_block_editor_for_post', array( $this, 'use_block_editor_for_post' ), 10, 2 ); // Since WP 5.0
25
  add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
36
 
37
  // Prevents WP resetting the option
38
  add_filter( 'pre_update_option_show_on_front', array( $this, 'update_show_on_front' ), 10, 2 );
39
+
40
+ add_action( 'admin_notices', array( $this, 'notice_must_translate' ) );
41
  }
42
 
43
  /**
92
  */
93
  public function display_post_states( $post_states, $post ) {
94
  if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_on_front' ) ) ) ) {
95
+ $post_states['page_on_front'] = __( 'Front Page', 'polylang' );
96
  }
97
 
98
  if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ) ) ) {
99
+ $post_states['page_for_posts'] = __( 'Posts Page', 'polylang' );
100
  }
101
 
102
  return $post_states;
189
  }
190
  return $value;
191
  }
192
+
193
+ /**
194
+ * Add a notice to translate the static front page if it is not translated in all languages
195
+ * This is especially useful after a new language is created.
196
+ * The notice is not dismissible and displayed on the Languages pages and the list of pages.
197
+ *
198
+ * @since 2.6
199
+ */
200
+ public function notice_must_translate() {
201
+ $screen = get_current_screen();
202
+
203
+ if ( $this->page_on_front && ( 'toplevel_page_mlang' === $screen->id || 'edit-page' === $screen->id ) ) {
204
+ $untranslated = array();
205
+
206
+ foreach ( $this->model->get_languages_list() as $language ) {
207
+ if ( ! $this->model->post->get( $this->page_on_front, $language ) ) {
208
+ $untranslated[] = sprintf(
209
+ '<a href="%s">%s</a>',
210
+ esc_url( $this->links->get_new_post_translation_link( $this->page_on_front, $language ) ),
211
+ esc_html( $language->name )
212
+ );
213
+ }
214
+ }
215
+
216
+ if ( ! empty( $untranslated ) ) {
217
+ printf(
218
+ '<div class="error"><p>%s</p></div>',
219
+ sprintf(
220
+ /* translators: %s is a comma separated list of native language names */
221
+ esc_html__( 'You must translate your static front page in %s.', 'polylang' ),
222
+ implode( ', ', $untranslated ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
223
+ )
224
+ );
225
+ }
226
+ }
227
+ }
228
  }
admin/admin-strings.php CHANGED
@@ -6,8 +6,8 @@
6
  * @since 1.6
7
  */
8
  class PLL_Admin_Strings {
9
- static protected $strings = array(); // strings to translate
10
- static protected $default_strings; // default strings to register
11
 
12
  /**
13
  * Add filters
@@ -51,10 +51,10 @@ class PLL_Admin_Strings {
51
  public static function &get_strings() {
52
  self::$default_strings = array(
53
  'options' => array(
54
- 'blogname' => __( 'Site Title' ),
55
- 'blogdescription' => __( 'Tagline' ),
56
- 'date_format' => __( 'Date Format' ),
57
- 'time_format' => __( 'Time Format' ),
58
  ),
59
  'widget_title' => __( 'Widget title', 'polylang' ),
60
  'widget_text' => __( 'Widget text', 'polylang' ),
@@ -125,11 +125,11 @@ class PLL_Admin_Strings {
125
  }
126
 
127
  if ( $name == self::$default_strings['widget_title'] ) {
128
- $translation = strip_tags( $translation );
129
  }
130
 
131
  if ( $name == self::$default_strings['widget_text'] && ! current_user_can( 'unfiltered_html' ) ) {
132
- $translation = wp_unslash( wp_filter_post_kses( addslashes( $translation ) ) ); // wp_filter_post_kses() expects slashed
133
  }
134
 
135
  return $translation;
6
  * @since 1.6
7
  */
8
  class PLL_Admin_Strings {
9
+ protected static $strings = array(); // strings to translate
10
+ protected static $default_strings; // default strings to register
11
 
12
  /**
13
  * Add filters
51
  public static function &get_strings() {
52
  self::$default_strings = array(
53
  'options' => array(
54
+ 'blogname' => __( 'Site Title', 'polylang' ),
55
+ 'blogdescription' => __( 'Tagline', 'polylang' ),
56
+ 'date_format' => __( 'Date Format', 'polylang' ),
57
+ 'time_format' => __( 'Time Format', 'polylang' ),
58
  ),
59
  'widget_title' => __( 'Widget title', 'polylang' ),
60
  'widget_text' => __( 'Widget text', 'polylang' ),
125
  }
126
 
127
  if ( $name == self::$default_strings['widget_title'] ) {
128
+ $translation = sanitize_text_field( $translation );
129
  }
130
 
131
  if ( $name == self::$default_strings['widget_text'] && ! current_user_can( 'unfiltered_html' ) ) {
132
+ $translation = wp_kses_post( $translation );
133
  }
134
 
135
  return $translation;
admin/admin.php CHANGED
@@ -48,7 +48,7 @@ class PLL_Admin extends PLL_Admin_Base {
48
  }
49
 
50
  /**
51
- * Aetups filters and action needed on all admin pages and on plugins page
52
  * Loads the settings pages or the filters base on the request
53
  *
54
  * @since 1.2
@@ -60,6 +60,12 @@ class PLL_Admin extends PLL_Admin_Base {
60
  // Priority 5 to make sure filters are there before customize_register is fired
61
  if ( $this->model->get_languages_list() ) {
62
  add_action( 'wp_loaded', array( $this, 'add_filters' ), 5 );
 
 
 
 
 
 
63
  }
64
  }
65
 
@@ -120,5 +126,68 @@ class PLL_Admin extends PLL_Admin_Base {
120
 
121
  $this->posts = new PLL_CRUD_Posts( $this );
122
  $this->terms = new PLL_CRUD_Terms( $this );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  }
124
  }
48
  }
49
 
50
  /**
51
+ * Setups filters and action needed on all admin pages and on plugins page
52
  * Loads the settings pages or the filters base on the request
53
  *
54
  * @since 1.2
60
  // Priority 5 to make sure filters are there before customize_register is fired
61
  if ( $this->model->get_languages_list() ) {
62
  add_action( 'wp_loaded', array( $this, 'add_filters' ), 5 );
63
+ add_action( 'admin_init', array( $this, 'maybe_load_sync_post' ) );
64
+
65
+ // Bulk Translate
66
+ if ( class_exists( 'PLL_Bulk_Translate' ) ) {
67
+ add_action( 'current_screen', array( $this->bulk_translate = new PLL_Bulk_Translate( $this ), 'init' ) );
68
+ }
69
  }
70
  }
71
 
126
 
127
  $this->posts = new PLL_CRUD_Posts( $this );
128
  $this->terms = new PLL_CRUD_Terms( $this );
129
+
130
+ // Advanced media
131
+ if ( $this->options['media_support'] && class_exists( 'PLL_Admin_Advanced_Media' ) ) {
132
+ $this->advanced_media = new PLL_Admin_Advanced_Media( $this );
133
+ }
134
+
135
+ // Share term slugs
136
+ if ( get_option( 'permalink_structure' ) && $this->options['force_lang'] && class_exists( 'PLL_Admin_Share_Term_Slug' ) ) {
137
+ $this->share_term_slug = new PLL_Admin_Share_Term_Slug( $this );
138
+ }
139
+
140
+ if ( class_exists( 'PLL_Sync_Content' ) ) {
141
+ $this->sync_content = new PLL_Sync_Content( $this );
142
+ }
143
+
144
+ // Duplicate content
145
+ if ( class_exists( 'PLL_Duplicate' ) ) {
146
+ $this->duplicate = new PLL_Duplicate( $this );
147
+ }
148
+
149
+ if ( class_exists( 'PLL_Duplicate_REST' ) ) {
150
+ $this->duplicate_rest = new PLL_Duplicate_REST();
151
+ }
152
+
153
+ // Block editor metabox
154
+ if ( pll_use_block_editor_plugin() ) {
155
+ $this->block_editor_plugin = new PLL_Block_Editor_Plugin( $this );
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Load the post synchronization object, depending on the editor in use.
161
+ *
162
+ * @since 2.6
163
+ */
164
+ public function maybe_load_sync_post() {
165
+ // Post synchronization
166
+ if ( 'post-new.php' === $GLOBALS['pagenow'] && function_exists( 'use_block_editor_for_post' ) ) {
167
+ // We need to wait until we know which editor is in use
168
+ add_filter( 'use_block_editor_for_post', array( $this, '_maybe_load_sync_post' ), 999 ); // After the plugin Classic Editor
169
+ } elseif ( 'post.php' === $GLOBALS['pagenow'] && function_exists( 'use_block_editor_for_post' ) && isset( $_GET['post'] ) && empty( $_GET['meta-box-loader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
170
+ $this->_maybe_load_sync_post( use_block_editor_for_post( (int) $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
171
+ } else {
172
+ $this->_maybe_load_sync_post( false );
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Load the post synchronization object, depending on the editor in use.
178
+ *
179
+ * @since 2.6
180
+ *
181
+ * @param bool $is_block_editor Whether to use the block editor or not
182
+ * @return bool
183
+ */
184
+ public function _maybe_load_sync_post( $is_block_editor ) {
185
+ if ( class_exists( 'PLL_Sync_Post_REST' ) && pll_use_block_editor_plugin() && $is_block_editor ) {
186
+ $this->sync_post = new PLL_Sync_Post_REST( $this );
187
+ } elseif ( class_exists( 'PLL_Sync_Post' ) ) {
188
+ $this->sync_post = new PLL_Sync_Post( $this );
189
+ }
190
+
191
+ return $is_block_editor;
192
  }
193
  }
admin/view-translations-media.php CHANGED
@@ -18,22 +18,20 @@ if ( ! defined( 'ABSPATH' ) ) {
18
  }
19
  ?>
20
  <tr>
21
- <td class = "pll-media-language-column"><span class = "pll-translation-flag"><?php echo $language->flag; ?></span><?php echo esc_html( $language->name ); ?></td>
22
  <td class = "pll-media-edit-column">
23
  <?php
24
- // The translation exists
25
  if ( ( $translation_id = $this->model->post->get_translation( $post_id, $language ) ) && $translation_id !== $post_id ) {
 
26
  printf(
27
- '<input type="hidden" name="media_tr_lang[%s]" value="%d" />%s',
28
  esc_attr( $language->slug ),
29
- esc_attr( $translation_id ),
30
- $this->links->edit_post_translation_link( $translation_id )
31
  );
32
- }
33
-
34
- // No translation
35
- else {
36
- echo $this->links->new_post_translation_link( $post_id, $language );
37
  }
38
  ?>
39
  </td>
18
  }
19
  ?>
20
  <tr>
21
+ <td class = "pll-media-language-column"><span class = "pll-translation-flag"><?php echo $language->flag; // phpcs:ignore WordPress.Security.EscapeOutput ?></span><?php echo esc_html( $language->name ); ?></td>
22
  <td class = "pll-media-edit-column">
23
  <?php
 
24
  if ( ( $translation_id = $this->model->post->get_translation( $post_id, $language ) ) && $translation_id !== $post_id ) {
25
+ // The translation exists
26
  printf(
27
+ '<input type="hidden" name="media_tr_lang[%s]" value="%d" />',
28
  esc_attr( $language->slug ),
29
+ esc_attr( $translation_id )
 
30
  );
31
+ echo $this->links->edit_post_translation_link( $translation_id ); // phpcs:ignore WordPress.Security.EscapeOutput
32
+ } else {
33
+ // No translation
34
+ echo $this->links->new_post_translation_link( $post_id, $language ); // phpcs:ignore WordPress.Security.EscapeOutput
 
35
  }
36
  ?>
37
  </td>
admin/view-translations-post.php CHANGED
@@ -17,12 +17,12 @@ if ( ! defined( 'ABSPATH' ) ) {
17
  }
18
 
19
  $value = $this->model->post->get_translation( $post_ID, $language );
20
- if ( ! $value || $value == $post_ID ) { // $value == $post_ID happens if the post has been ( auto )saved before changing the language
21
  $value = '';
22
  }
23
 
24
- if ( isset( $_GET['from_post'] ) ) {
25
- $value = $this->model->post->get( (int) $_GET['from_post'], $language );
26
  }
27
 
28
  $link = $add_link = $this->links->new_post_translation_link( $post_ID, $language );
@@ -33,9 +33,9 @@ if ( ! defined( 'ABSPATH' ) ) {
33
  }
34
  ?>
35
  <tr>
36
- <th class = "pll-language-column"><?php echo $language->flag ? $language->flag : esc_html( $language->slug ); ?></th>
37
- <td class = "hidden"><?php echo $add_link; ?></td>
38
- <td class = "pll-edit-column pll-column-icon"><?php echo $link; ?></td>
39
  <?php
40
 
41
  /**
@@ -55,11 +55,11 @@ if ( ! defined( 'ABSPATH' ) ) {
55
  esc_attr( $language->slug ),
56
  /* translators: accessibility text */
57
  esc_html__( 'Translation', 'polylang' ),
58
- empty( $value ) ? 0 : esc_attr( $selected->ID ),
59
- empty( $value ) ? '' : esc_attr( $selected->post_title ),
60
- empty( $link ) ? ' disabled="disabled"' : '',
61
  esc_attr( $language->get_locale( 'display' ) ),
62
- $language->is_rtl ? 'rtl' : 'ltr'
63
  );
64
  ?>
65
  </td>
17
  }
18
 
19
  $value = $this->model->post->get_translation( $post_ID, $language );
20
+ if ( ! $value || $value == $post_ID ) { // $value == $post_ID happens if the post has been (auto)saved before changing the language
21
  $value = '';
22
  }
23
 
24
+ if ( ! empty( $from_post_id ) ) {
25
+ $value = $this->model->post->get( $from_post_id, $language );
26
  }
27
 
28
  $link = $add_link = $this->links->new_post_translation_link( $post_ID, $language );
33
  }
34
  ?>
35
  <tr>
36
+ <th class = "pll-language-column"><?php echo $language->flag ? $language->flag : esc_html( $language->slug ); // phpcs:ignore WordPress.Security.EscapeOutput ?></th>
37
+ <td class = "hidden"><?php echo $add_link; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
38
+ <td class = "pll-edit-column pll-column-icon"><?php echo $link; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
39
  <?php
40
 
41
  /**
55
  esc_attr( $language->slug ),
56
  /* translators: accessibility text */
57
  esc_html__( 'Translation', 'polylang' ),
58
+ ( empty( $value ) ? 0 : esc_attr( $selected->ID ) ),
59
+ ( empty( $value ) ? '' : esc_attr( $selected->post_title ) ),
60
+ disabled( empty( $link ), true, false ),
61
  esc_attr( $language->get_locale( 'display' ) ),
62
+ ( $language->is_rtl ? 'rtl' : 'ltr' )
63
  );
64
  ?>
65
  </td>
admin/view-translations-term.php CHANGED
@@ -34,7 +34,7 @@ else {
34
  if ( isset( $term_id ) && ( $translation_id = $this->model->term->get_translation( $term_id, $language ) ) && $translation_id != $term_id ) {
35
  $translation = get_term( $translation_id, $taxonomy );
36
  }
37
- if ( isset( $_GET['from_tag'] ) && ( $translation_id = $this->model->term->get( (int) $_GET['from_tag'], $language ) ) && ! $this->model->term->get_translation( $translation_id, $lang ) ) {
38
  $translation = get_term( $translation_id, $taxonomy );
39
  }
40
 
@@ -48,7 +48,7 @@ else {
48
  ?>
49
  <tr>
50
  <th class = "pll-language-column">
51
- <span class = "pll-translation-flag"><?php echo $language->flag ? $language->flag : esc_html( $language->slug ); ?></span>
52
  <?php
53
  printf(
54
  '<span class="pll-language-name%1$s">%2$s</span>',
@@ -60,8 +60,8 @@ else {
60
  <?php
61
  if ( isset( $term_id ) ) {
62
  ?>
63
- <td class = "hidden"><?php echo $add_link; ?></td>
64
- <td class = "pll-edit-column"><?php echo $link; ?></td>
65
  <?php
66
  }
67
  ?>
@@ -74,11 +74,11 @@ else {
74
  esc_attr( $language->slug ),
75
  /* translators: accessibility text */
76
  esc_html__( 'Translation', 'polylang' ),
77
- empty( $translation ) ? 0 : esc_attr( $translation->term_id ),
78
- empty( $translation ) ? '' : esc_attr( $translation->name ),
79
- empty( $disabled ) ? '' : ' disabled="disabled"',
80
  esc_attr( $language->get_locale( 'display' ) ),
81
- $language->is_rtl ? 'rtl' : 'ltr'
82
  );
83
  ?>
84
  </td>
34
  if ( isset( $term_id ) && ( $translation_id = $this->model->term->get_translation( $term_id, $language ) ) && $translation_id != $term_id ) {
35
  $translation = get_term( $translation_id, $taxonomy );
36
  }
37
+ if ( ! empty( $from_term_id ) && ( $translation_id = $this->model->term->get( $from_term_id, $language ) ) && ! $this->model->term->get_translation( $translation_id, $lang ) ) {
38
  $translation = get_term( $translation_id, $taxonomy );
39
  }
40
 
48
  ?>
49
  <tr>
50
  <th class = "pll-language-column">
51
+ <span class = "pll-translation-flag"><?php echo $language->flag ? $language->flag : esc_html( $language->slug ); // phpcs:ignore WordPress.Security.EscapeOutput ?></span>
52
  <?php
53
  printf(
54
  '<span class="pll-language-name%1$s">%2$s</span>',
60
  <?php
61
  if ( isset( $term_id ) ) {
62
  ?>
63
+ <td class = "hidden"><?php echo $add_link; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
64
+ <td class = "pll-edit-column"><?php echo $link; // phpcs:ignore WordPress.Security.EscapeOutput ?></td>
65
  <?php
66
  }
67
  ?>
74
  esc_attr( $language->slug ),
75
  /* translators: accessibility text */
76
  esc_html__( 'Translation', 'polylang' ),
77
+ ( empty( $translation ) ? 0 : esc_attr( $translation->term_id ) ),
78
+ ( empty( $translation ) ? '' : esc_attr( $translation->name ) ),
79
+ disabled( empty( $disabled ), false, false ),
80
  esc_attr( $language->get_locale( 'display' ) ),
81
+ ( $language->is_rtl ? 'rtl' : 'ltr' )
82
  );
83
  ?>
84
  </td>
css/admin.css CHANGED
@@ -219,6 +219,25 @@ td[class*='column-language_'] {
219
  float: right;
220
  }
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  /* language and translations in edit-tags.php */
223
  .pll-translation-flag { /* also for media */
224
  margin-right: 14px;
@@ -305,6 +324,26 @@ td[class*='column-language_'] {
305
  text-decoration: none;
306
  }
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  @media screen and ( max-width: 782px ) {
309
  /* settings */
310
  #wpbody-content .pll-settings .pll-configure > td {
219
  float: right;
220
  }
221
 
222
+ /* Languages metabox buttons */
223
+ .pll-button {
224
+ padding: 0;
225
+ height: 20px;
226
+ background: none;
227
+ border: none;
228
+ font-size: 20px;
229
+ cursor: pointer;
230
+ }
231
+
232
+ .pll-button:not(.wp-ui-text-highlight) {
233
+ color: #DDDDDD;
234
+ }
235
+
236
+ #pll-duplicate {
237
+ float: right;
238
+ margin: 13px 7px;
239
+ }
240
+
241
  /* language and translations in edit-tags.php */
242
  .pll-translation-flag { /* also for media */
243
  margin-right: 14px;
324
  text-decoration: none;
325
  }
326
 
327
+ /* Bulk translate */
328
+ .bulk-translate-save .button {
329
+ margin-right: 20px;
330
+ }
331
+
332
+ #wpbody-content #pll-translate fieldset {
333
+ display: inline-block;
334
+ width: 300px;
335
+ }
336
+
337
+ #pll-translate .pll-translation-flag {
338
+ margin-right: 0.3em;
339
+ }
340
+
341
+ #pll-translate .title {
342
+ display: block;
343
+ margin: 0.2em 0;
344
+ line-height: 2.5;
345
+ }
346
+
347
  @media screen and ( max-width: 782px ) {
348
  /* settings */
349
  #wpbody-content .pll-settings .pll-configure > td {
css/admin.min.css CHANGED
@@ -1 +1 @@
1
- #pll-licenses-table td,.translation label{vertical-align:top}#post-translations a,.pll-notice a.notice-dismiss{text-decoration:none}#add-lang select{width:95%}.column-locale,.languages .column-slug{width:15%}.column-default_lang{width:5%}.column-count,.column-flag,.column-term_group{width:10%}.icon-default-lang:before{font-family:dashicons;content:"\f155"}.form-field input[type=radio]{width:auto;margin-right:2px}#pll-about-box p,#pll-recommended p{text-align:justify}#pll-about-box input{margin:0;padding:0;float:right}.stringstranslations .column-context,.stringstranslations .column-name{width:10%}.stringstranslations .column-string{width:33%}.translation label{display:inline-block;width:23%}.translation input,.translation textarea{width:72%}.pll-settings{margin-top:20px}.pll-settings .plugin-title{width:25%}#wpbody-content .pll-settings .pll-configure tr{display:table-row}#wpbody-content .pll-settings .pll-configure td{display:table-cell}#wpbody-content .pll-settings .pll-configure>td{padding:20px 20px 20px 40px}.pll-configure legend{font-size:14px;font-weight:600;margin-bottom:.5em}.pll-configure td .description{margin-top:2px;margin-bottom:.5em}.pll-configure p.submit{margin-top:20px}.pll-configure .button{margin-right:20px}.pll-configure fieldset{margin-bottom:1.5em}.pll-inline-block-list{margin:0}.pll-inline-block-list li{display:inline-block;margin:0;width:250px}#pll-domains-table td{padding:2px 2px 2px 1.5em;-webkit-box-shadow:none;box-shadow:none;border:none}.pll-settings-url-col{display:inline-block;width:49%;vertical-align:top}#pll-licenses-table label{font-size:1em;font-weight:600}.pll-configure .pll-deactivate-license{margin:0 0 0 20px}td[class*=column-language_],th[class*=column-language_]{width:1.5em}.pll-dir-rtl input[type=text],.pll-dir-rtl textarea{direction:rtl}.pll-dir-ltr input[type=text],.pll-dir-ltr textarea{direction:ltr}.pll-dir-ltr .tr_lang,.pll-dir-rtl .tr_lang{direction:inherit}#post-translations p{float:left}#post-translations table{table-layout:fixed;width:100%;clear:both}#post-translations .pll-column-icon,#post-translations .pll-language-column{width:20px}#post-translations .tr_lang{width:100%}#post-translations td{padding:2px}#post-translations .spinner,#term-translations .spinner{float:none;margin:0;background-position:center;width:auto}.pll-column-icon{text-align:center}#select-post-language .pll-select-flag{padding:4px;margin-right:32px}#select-media-language .pll-select-flag{padding:4px;margin-right:10px}.pll-media-edit-column{float:right}.pll-translation-flag{margin-right:14px}#select-add-term-language .pll-select-flag{padding:11px;margin-right:13px}#select-edit-term-language .pll-select-flag{padding:11px;margin-right:4px}#term-translations p{font-weight:400;font-style:normal;padding:2px;color:#23282d}#add-term-translations,#edit-term-translations{width:95%}#term-translations .pll-language-column{line-height:28px;width:20%}#add-term-translations .pll-language-column,#term-translations .pll-edit-column{width:20px}#edit-term-translations .pll-language-column{padding:15px 10px;font-weight:400}.pll_icon_tick:before{content:"\f147"}.pll_icon_add:before{content:"\f132"}.pll_icon_edit:before{content:"\f464"}[class^=pll_icon_]{font:20px/1 dashicons;vertical-align:middle}#wpadminbar #wp-admin-bar-languages .ab-item img{margin:0 8px 0 2px}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{float:none;top:4px}#wpadminbar #wp-admin-bar-languages .ab-icon:before{content:"\f326";top:1px}.pll-notice.notice{padding-right:38px;position:relative}@media screen and (max-width:782px){#wpbody-content .pll-settings .pll-configure>td{padding:20px}#wpbody-content .pll-settings #cb{padding:20px 9px}.pll-inline-block{width:auto}.pll-settings-url-col{display:block;width:100%}#wpbody-content .pll-settings #pll-licenses-table td{display:block}.pll-configure .pll-deactivate-license{margin:10px 0 5px}.stringstranslations .column-context,.stringstranslations .column-name{display:none}.translation label{display:block;width:95%;padding-left:0}.translation input{width:95%}#edit-term-translations .pll-language-name,#select-add-term-language .pll-select-flag,#select-edit-term-language .pll-select-flag{display:none}#edit-term-translations{width:100%}#add-term-translations .pll-language-column{line-height:38px}#edit-term-translations td{padding:8px 10px}#edit-term-translations .pll-edit-column,#edit-term-translations .pll-language-column{width:20px}.term-translations .pll-edit-column,.term-translations .pll-language-column,.term-translations .pll-translation-column{display:table-cell}.term-translations .hidden{display:none}#wpadminbar #wp-admin-bar-languages{display:block}#wpadminbar #wp-admin-bar-languages>.ab-item{width:50px;text-align:center}#wpadminbar #wp-admin-bar-languages>.ab-item .ab-icon:before{font:32px/1 dashicons;top:-1px}#wpadminbar #wp-admin-bar-languages>.ab-item img{margin:19px 0}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{margin-right:6px;font-size:20px!important;line-height:20px!important}}
1
+ #pll-licenses-table td,.translation label{vertical-align:top}#post-translations a,.pll-notice a.notice-dismiss{text-decoration:none}#add-lang select{width:95%}.column-locale,.languages .column-slug{width:15%}.column-default_lang{width:5%}.column-count,.column-flag,.column-term_group{width:10%}.icon-default-lang:before{font-family:dashicons;content:"\f155"}.form-field input[type=radio]{width:auto;margin-right:2px}#pll-about-box p,#pll-recommended p{text-align:justify}#pll-about-box input{margin:0;padding:0;float:right}.stringstranslations .column-context,.stringstranslations .column-name{width:10%}.stringstranslations .column-string{width:33%}.translation label{display:inline-block;width:23%}.translation input,.translation textarea{width:72%}.pll-settings{margin-top:20px}.pll-settings .plugin-title{width:25%}#wpbody-content .pll-settings .pll-configure tr{display:table-row}#wpbody-content .pll-settings .pll-configure td{display:table-cell}#wpbody-content .pll-settings .pll-configure>td{padding:20px 20px 20px 40px}.pll-configure legend{font-size:14px;font-weight:600;margin-bottom:.5em}.pll-configure td .description{margin-top:2px;margin-bottom:.5em}.pll-configure p.submit{margin-top:20px}.pll-configure .button{margin-right:20px}.pll-configure fieldset{margin-bottom:1.5em}.pll-inline-block-list{margin:0}.pll-inline-block-list li{display:inline-block;margin:0;width:250px}#pll-domains-table td{padding:2px 2px 2px 1.5em;-webkit-box-shadow:none;box-shadow:none;border:none}.pll-settings-url-col{display:inline-block;width:49%;vertical-align:top}#pll-licenses-table label{font-size:1em;font-weight:600}.pll-configure .pll-deactivate-license{margin:0 0 0 20px}td[class*=column-language_],th[class*=column-language_]{width:1.5em}.pll-dir-rtl input[type=text],.pll-dir-rtl textarea{direction:rtl}.pll-dir-ltr input[type=text],.pll-dir-ltr textarea{direction:ltr}.pll-dir-ltr .tr_lang,.pll-dir-rtl .tr_lang{direction:inherit}#post-translations p{float:left}#post-translations table{table-layout:fixed;width:100%;clear:both}#post-translations .pll-column-icon,#post-translations .pll-language-column{width:20px}#post-translations .tr_lang{width:100%}#post-translations td{padding:2px}#post-translations .spinner,#term-translations .spinner{float:none;margin:0;background-position:center;width:auto}.pll-column-icon{text-align:center}#select-post-language .pll-select-flag{padding:4px;margin-right:32px}#select-media-language .pll-select-flag{padding:4px;margin-right:10px}.pll-media-edit-column{float:right}.pll-button{padding:0;height:20px;background:0 0;border:none;font-size:20px;cursor:pointer}.pll-button:not(.wp-ui-text-highlight){color:#DDD}#pll-duplicate{float:right;margin:13px 7px}.pll-translation-flag{margin-right:14px}#select-add-term-language .pll-select-flag{padding:11px;margin-right:13px}#select-edit-term-language .pll-select-flag{padding:11px;margin-right:4px}#term-translations p{font-weight:400;font-style:normal;padding:2px;color:#23282d}#add-term-translations,#edit-term-translations{width:95%}#term-translations .pll-language-column{line-height:28px;width:20%}#add-term-translations .pll-language-column,#term-translations .pll-edit-column{width:20px}#edit-term-translations .pll-language-column{padding:15px 10px;font-weight:400}.pll_icon_tick:before{content:"\f147"}.pll_icon_add:before{content:"\f132"}.pll_icon_edit:before{content:"\f464"}[class^=pll_icon_]{font:20px/1 dashicons;vertical-align:middle}#wpadminbar #wp-admin-bar-languages .ab-item img{margin:0 8px 0 2px}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{float:none;top:4px}#wpadminbar #wp-admin-bar-languages .ab-icon:before{content:"\f326";top:1px}.pll-notice.notice{padding-right:38px;position:relative}.bulk-translate-save .button{margin-right:20px}#wpbody-content #pll-translate fieldset{display:inline-block;width:300px}#pll-translate .pll-translation-flag{margin-right:.3em}#pll-translate .title{display:block;margin:.2em 0;line-height:2.5}@media screen and (max-width:782px){#wpbody-content .pll-settings .pll-configure>td{padding:20px}#wpbody-content .pll-settings #cb{padding:20px 9px}.pll-inline-block{width:auto}.pll-settings-url-col{display:block;width:100%}#wpbody-content .pll-settings #pll-licenses-table td{display:block}.pll-configure .pll-deactivate-license{margin:10px 0 5px}.stringstranslations .column-context,.stringstranslations .column-name{display:none}.translation label{display:block;width:95%;padding-left:0}.translation input{width:95%}#edit-term-translations .pll-language-name,#select-add-term-language .pll-select-flag,#select-edit-term-language .pll-select-flag{display:none}#edit-term-translations{width:100%}#add-term-translations .pll-language-column{line-height:38px}#edit-term-translations td{padding:8px 10px}#edit-term-translations .pll-edit-column,#edit-term-translations .pll-language-column{width:20px}.term-translations .pll-edit-column,.term-translations .pll-language-column,.term-translations .pll-translation-column{display:table-cell}.term-translations .hidden{display:none}#wpadminbar #wp-admin-bar-languages{display:block}#wpadminbar #wp-admin-bar-languages>.ab-item{width:50px;text-align:center}#wpadminbar #wp-admin-bar-languages>.ab-item .ab-icon:before{font:32px/1 dashicons;top:-1px}#wpadminbar #wp-admin-bar-languages>.ab-item img{margin:19px 0}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{margin-right:6px;font-size:20px!important;line-height:20px!important}}
frontend/choose-lang-content.php CHANGED
@@ -6,7 +6,7 @@
6
  *
7
  * @since 1.2
8
  */
9
- class PLL_Choose_Lang_Content extends PLL_Choose_lang {
10
 
11
  /**
12
  * Defers the language choice to the 'wp' action (when the content is known)
6
  *
7
  * @since 1.2
8
  */
9
+ class PLL_Choose_Lang_Content extends PLL_Choose_Lang {
10
 
11
  /**
12
  * Defers the language choice to the 'wp' action (when the content is known)
frontend/choose-lang-url.php CHANGED
@@ -7,7 +7,7 @@
7
  *
8
  * @since 1.2
9
  */
10
- class PLL_Choose_Lang_Url extends PLL_Choose_lang {
11
  protected $index = 'index.php'; // Need this before $wp_rewrite is created, also hardcoded in wp-includes/rewrite.php
12
 
13
  /**
@@ -31,25 +31,27 @@ class PLL_Choose_Lang_Url extends PLL_Choose_lang {
31
  * @since 1.2
32
  */
33
  public function set_language_from_url() {
34
- $host = str_replace( 'www.', '', parse_url( $this->links_model->home, PHP_URL_HOST ) ); // Remove www. for the comparison
35
- $home_path = parse_url( $this->links_model->home, PHP_URL_PATH );
36
 
37
- $requested_host = parse_url( 'http://' . str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ), PHP_URL_HOST ); // Remove the port and www. for the comparison
38
- $requested_uri = rtrim( str_replace( $this->index, '', $_SERVER['REQUEST_URI'] ), '/' ); // Some PHP setups turn requests for / into /index.php in REQUEST_URI
 
 
39
 
40
  // Home is requested
41
- if ( $requested_host == $host && $requested_uri == $home_path && empty( $_SERVER['QUERY_STRING'] ) ) {
42
  $this->home_language();
43
  add_action( 'setup_theme', array( $this, 'home_requested' ) );
44
  }
45
 
46
  // Take care to post & page preview http://wordpress.org/support/topic/static-frontpage-url-parameter-url-language-information
47
- elseif ( isset( $_GET['preview'] ) && ( ( isset( $_GET['p'] ) && $id = (int) $_GET['p'] ) || ( isset( $_GET['page_id'] ) && $id = (int) $_GET['page_id'] ) ) ) {
48
  $curlang = ( $lg = $this->model->post->get_language( $id ) ) ? $lg : $this->model->get_language( $this->options['default_lang'] );
49
  }
50
 
51
  // Take care to ( unattached ) attachments
52
- elseif ( isset( $_GET['attachment_id'] ) && $id = (int) $_GET['attachment_id'] ) {
53
  $curlang = ( $lg = $this->model->post->get_language( $id ) ) ? $lg : $this->get_preferred_language();
54
  }
55
 
7
  *
8
  * @since 1.2
9
  */
10
+ class PLL_Choose_Lang_Url extends PLL_Choose_Lang {
11
  protected $index = 'index.php'; // Need this before $wp_rewrite is created, also hardcoded in wp-includes/rewrite.php
12
 
13
  /**
31
  * @since 1.2
32
  */
33
  public function set_language_from_url() {
34
+ $host = str_replace( 'www.', '', wp_parse_url( $this->links_model->home, PHP_URL_HOST ) ); // Remove www. for the comparison
35
+ $home_path = (string) wp_parse_url( $this->links_model->home, PHP_URL_PATH );
36
 
37
+ $requested_url = pll_get_requested_url();
38
+ $requested_host = str_replace( 'www.', '', wp_parse_url( $requested_url, PHP_URL_HOST ) ); // Remove www. for the comparison
39
+ $requested_path = rtrim( str_replace( $this->index, '', wp_parse_url( $requested_url, PHP_URL_PATH ) ), '/' ); // Some PHP setups turn requests for / into /index.php in REQUEST_URI
40
+ $requested_query = wp_parse_url( $requested_url, PHP_URL_QUERY );
41
 
42
  // Home is requested
43
+ if ( $requested_host === $host && $requested_path === $home_path && empty( $requested_query ) ) {
44
  $this->home_language();
45
  add_action( 'setup_theme', array( $this, 'home_requested' ) );
46
  }
47
 
48
  // Take care to post & page preview http://wordpress.org/support/topic/static-frontpage-url-parameter-url-language-information
49
+ elseif ( isset( $_GET['preview'] ) && ( ( isset( $_GET['p'] ) && $id = (int) $_GET['p'] ) || ( isset( $_GET['page_id'] ) && $id = (int) $_GET['page_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
50
  $curlang = ( $lg = $this->model->post->get_language( $id ) ) ? $lg : $this->model->get_language( $this->options['default_lang'] );
51
  }
52
 
53
  // Take care to ( unattached ) attachments
54
+ elseif ( isset( $_GET['attachment_id'] ) && $id = (int) $_GET['attachment_id'] ) { // phpcs:ignore WordPress.Security.NonceVerification
55
  $curlang = ( $lg = $this->model->post->get_language( $id ) ) ? $lg : $this->get_preferred_language();
56
  }
57
 
frontend/choose-lang.php CHANGED
@@ -32,8 +32,8 @@ abstract class PLL_Choose_Lang {
32
  * @since 1.8
33
  */
34
  public function init() {
35
- if ( Polylang::is_ajax_on_front() || false === stripos( $_SERVER['SCRIPT_FILENAME'], 'index.php' ) ) {
36
- $this->set_language( empty( $_REQUEST['lang'] ) ? $this->get_preferred_language() : $this->model->get_language( $_REQUEST['lang'] ) );
37
  }
38
 
39
  add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ) ); // sets the language of comment
@@ -101,7 +101,7 @@ abstract class PLL_Choose_Lang {
101
  $this->curlang->slug,
102
  time() + $expiration,
103
  COOKIEPATH,
104
- 2 == $this->options['force_lang'] ? parse_url( $this->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN,
105
  is_ssl()
106
  );
107
  }
@@ -120,7 +120,11 @@ abstract class PLL_Choose_Lang {
120
 
121
  if ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
122
  // Break up string into pieces ( languages and q factors )
123
- preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*((?>1|0)(?>\.[0-9]+)?))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse );
 
 
 
 
124
 
125
  $k = $lang_parse[1];
126
  $v = $lang_parse[4];
@@ -196,7 +200,7 @@ abstract class PLL_Choose_Lang {
196
  public function get_preferred_language() {
197
  // check first if the user was already browsing this site
198
  if ( isset( $_COOKIE[ PLL_COOKIE ] ) ) {
199
- return $this->model->get_language( $_COOKIE[ PLL_COOKIE ] );
200
  }
201
 
202
  /**
@@ -221,11 +225,11 @@ abstract class PLL_Choose_Lang {
221
  * @since 1.2
222
  */
223
  protected function home_language() {
224
- // test referer in case PLL_COOKIE is set to false
225
- // thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser
226
- $language = $this->options['hide_default'] && ( ( isset( $_SERVER['HTTP_REFERER'] ) && in_array( parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_HOST ), $this->links_model->get_hosts() ) ) || ! $this->options['browser'] ) ?
227
  $this->model->get_language( $this->options['default_lang'] ) :
228
- $this->get_preferred_language(); // sets the language according to browser preference or default language
229
  $this->set_language( $language );
230
  }
231
 
@@ -252,9 +256,12 @@ abstract class PLL_Choose_Lang {
252
  // Test to avoid crash if get_home_url returns something wrong
253
  // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
254
  // Don't redirect if $_POST is not empty as it could break other plugins
255
- // Don't forget the query string which may be added by plugins
256
- elseif ( is_string( $redirect = $this->curlang->home_url ) && empty( $_POST ) ) {
257
- $redirect = empty( $_SERVER['QUERY_STRING'] ) ? $redirect : $redirect . ( $this->links_model->using_permalinks ? '?' : '&' ) . $_SERVER['QUERY_STRING'];
 
 
 
258
 
259
  /**
260
  * When a visitor reaches the site home, Polylang redirects to the home page in the correct language.
@@ -267,7 +274,7 @@ abstract class PLL_Choose_Lang {
267
  */
268
  if ( $redirect = apply_filters( 'pll_redirect_home', $redirect ) ) {
269
  $this->maybe_setcookie();
270
- wp_redirect( $redirect, 302, POLYLANG );
271
  exit;
272
  }
273
  }
32
  * @since 1.8
33
  */
34
  public function init() {
35
+ if ( Polylang::is_ajax_on_front() || ! wp_using_themes() ) {
36
+ $this->set_language( empty( $_REQUEST['lang'] ) ? $this->get_preferred_language() : $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
37
  }
38
 
39
  add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ) ); // sets the language of comment
101
  $this->curlang->slug,
102
  time() + $expiration,
103
  COOKIEPATH,
104
+ 2 == $this->options['force_lang'] ? wp_parse_url( $this->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN,
105
  is_ssl()
106
  );
107
  }
120
 
121
  if ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
122
  // Break up string into pieces ( languages and q factors )
123
+ preg_match_all(
124
+ '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*((?>1|0)(?>\.[0-9]+)?))?/i',
125
+ sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ),
126
+ $lang_parse
127
+ );
128
 
129
  $k = $lang_parse[1];
130
  $v = $lang_parse[4];
200
  public function get_preferred_language() {
201
  // check first if the user was already browsing this site
202
  if ( isset( $_COOKIE[ PLL_COOKIE ] ) ) {
203
+ return $this->model->get_language( sanitize_key( $_COOKIE[ PLL_COOKIE ] ) );
204
  }
205
 
206
  /**
225
  * @since 1.2
226
  */
227
  protected function home_language() {
228
+ // Test referer in case PLL_COOKIE is set to false. Since WP 3.6.1, wp_get_referer() validates the host which is exactly what we want
229
+ // Thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser
230
+ $language = $this->options['hide_default'] && ( wp_get_referer() || ! $this->options['browser'] ) ?
231
  $this->model->get_language( $this->options['default_lang'] ) :
232
+ $this->get_preferred_language(); // Sets the language according to browser preference or default language
233
  $this->set_language( $language );
234
  }
235
 
256
  // Test to avoid crash if get_home_url returns something wrong
257
  // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1
258
  // Don't redirect if $_POST is not empty as it could break other plugins
259
+ elseif ( is_string( $redirect = $this->curlang->home_url ) && empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification
260
+ // Don't forget the query string which may be added by plugins
261
+ $query_string = wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY );
262
+ if ( ! empty( $query_string ) ) {
263
+ $redirect .= ( $this->links_model->using_permalinks ? '?' : '&' ) . $query_string;
264
+ }
265
 
266
  /**
267
  * When a visitor reaches the site home, Polylang redirects to the home page in the correct language.
274
  */
275
  if ( $redirect = apply_filters( 'pll_redirect_home', $redirect ) ) {
276
  $this->maybe_setcookie();
277
+ wp_safe_redirect( $redirect, 302, POLYLANG );
278
  exit;
279
  }
280
  }
frontend/frontend-auto-translate.php CHANGED
@@ -190,7 +190,7 @@ class PLL_Frontend_Auto_Translate {
190
  if ( ! empty( $qv[ $key ] ) ) {
191
  // post__in used by the 2 functions below
192
  // Useless to filter them as output is already in the right language and would result in performance loss
193
- foreach ( debug_backtrace() as $trace ) {
194
  if ( in_array( $trace['function'], array( 'wp_nav_menu', 'gallery_shortcode' ) ) ) {
195
  return;
196
  }
190
  if ( ! empty( $qv[ $key ] ) ) {
191
  // post__in used by the 2 functions below
192
  // Useless to filter them as output is already in the right language and would result in performance loss
193
+ foreach ( debug_backtrace() as $trace ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions
194
  if ( in_array( $trace['function'], array( 'wp_nav_menu', 'gallery_shortcode' ) ) ) {
195
  return;
196
  }
frontend/frontend-filters-links.php CHANGED
@@ -269,7 +269,7 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
269
  );
270
  }
271
 
272
- $traces = version_compare( PHP_VERSION, '5.2.5', '>=' ) ? debug_backtrace( false ) : debug_backtrace();
273
  unset( $traces[0], $traces[1] ); // We don't need the last 2 calls: this function + call_user_func_array (or apply_filters on PHP7+)
274
 
275
  foreach ( $traces as $trace ) {
@@ -323,7 +323,7 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
323
  }
324
 
325
  // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id=
326
- if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) {
327
  return;
328
  }
329
 
@@ -334,7 +334,7 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
334
  }
335
 
336
  if ( empty( $requested_url ) ) {
337
- $requested_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
338
  }
339
 
340
  if ( is_single() || is_page() ) {
@@ -362,11 +362,12 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
362
  }
363
 
364
  if ( 3 === $this->options['force_lang'] ) {
 
365
  foreach ( $this->options['domains'] as $lang => $domain ) {
366
- $host = parse_url( $domain, PHP_URL_HOST );
367
- if ( 'www.' . $_SERVER['HTTP_HOST'] === $host || 'www.' . $host === $_SERVER['HTTP_HOST'] ) {
368
  $language = $this->model->get_language( $lang );
369
- $redirect_url = str_replace( '://' . $_SERVER['HTTP_HOST'], '://' . $host, $requested_url );
370
  }
371
  }
372
  }
@@ -398,7 +399,7 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
398
 
399
  // The language is not correctly set so let's redirect to the correct url for this object
400
  if ( $do_redirect && $redirect_url && $requested_url != $redirect_url ) {
401
- wp_redirect( $redirect_url, 301, POLYLANG );
402
  exit;
403
  }
404
 
269
  );
270
  }
271
 
272
+ $traces = version_compare( PHP_VERSION, '5.2.5', '>=' ) ? debug_backtrace( false ) : debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
273
  unset( $traces[0], $traces[1] ); // We don't need the last 2 calls: this function + call_user_func_array (or apply_filters on PHP7+)
274
 
275
  foreach ( $traces as $trace ) {
323
  }
324
 
325
  // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id=
326
+ if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
327
  return;
328
  }
329
 
334
  }
335
 
336
  if ( empty( $requested_url ) ) {
337
+ $requested_url = pll_get_requested_url();
338
  }
339
 
340
  if ( is_single() || is_page() ) {
362
  }
363
 
364
  if ( 3 === $this->options['force_lang'] ) {
365
+ $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST );
366
  foreach ( $this->options['domains'] as $lang => $domain ) {
367
+ $host = wp_parse_url( $domain, PHP_URL_HOST );
368
+ if ( 'www.' . $requested_host === $host || 'www.' . $host === $requested_host ) {
369
  $language = $this->model->get_language( $lang );
370
+ $redirect_url = str_replace( '://' . $requested_host, '://' . $host, $requested_url );
371
  }
372
  }
373
  }
399
 
400
  // The language is not correctly set so let's redirect to the correct url for this object
401
  if ( $do_redirect && $redirect_url && $requested_url != $redirect_url ) {
402
+ wp_safe_redirect( $redirect_url, 301, POLYLANG );
403
  exit;
404
  }
405
 
frontend/frontend-filters-search.php CHANGED
@@ -80,8 +80,8 @@ class PLL_Frontend_Filters_Search {
80
  public function admin_bar_search_menu( $wp_admin_bar ) {
81
  $form = '<form action="' . esc_url( home_url( '/' ) ) . '" method="get" id="adminbarsearch">';
82
  $form .= '<input class="adminbar-input" name="s" id="adminbar-search" type="text" value="" maxlength="150" />';
83
- $form .= '<label for="adminbar-search" class="screen-reader-text">' . esc_html__( 'Search' ) . '</label>';
84
- $form .= '<input type="submit" class="adminbar-button" value="' . esc_attr__( 'Search' ) . '"/>';
85
  $form .= '</form>';
86
 
87
  $wp_admin_bar->add_menu(
@@ -129,6 +129,6 @@ class PLL_Frontend_Filters_Search {
129
  }
130
  }
131
  //]]>";
132
- echo '<script type="text/javascript">' . $js . '</script>';
133
  }
134
  }
80
  public function admin_bar_search_menu( $wp_admin_bar ) {
81
  $form = '<form action="' . esc_url( home_url( '/' ) ) . '" method="get" id="adminbarsearch">';
82
  $form .= '<input class="adminbar-input" name="s" id="adminbar-search" type="text" value="" maxlength="150" />';
83
+ $form .= '<label for="adminbar-search" class="screen-reader-text">' . esc_html__( 'Search', 'polylang' ) . '</label>';
84
+ $form .= '<input type="submit" class="adminbar-button" value="' . esc_attr__( 'Search', 'polylang' ) . '"/>';
85
  $form .= '</form>';
86
 
87
  $wp_admin_bar->add_menu(
129
  }
130
  }
131
  //]]>";
132
+ echo '<script type="text/javascript">' . $js . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput
133
  }
134
  }
frontend/frontend-filters.php CHANGED
@@ -48,7 +48,7 @@ class PLL_Frontend_Filters extends PLL_Filters {
48
 
49
  // Support theme customizer
50
  // FIXME of course does not work if 'transport' is set to 'postMessage'
51
- if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) {
52
  add_filter( 'pre_option_blogname', 'pll__', 20 );
53
  add_filter( 'pre_option_blogdescription', 'pll__', 20 );
54
  }
@@ -94,7 +94,7 @@ class PLL_Frontend_Filters extends PLL_Filters {
94
  $_posts = array_fill_keys( $languages, array() ); // Init with empty arrays
95
  $languages = implode( ',', $languages );
96
 
97
- // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
98
  $relations = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM {$wpdb->term_relationships} WHERE object_id IN ({$posts}) AND term_taxonomy_id IN ({$languages})" );
99
 
100
  foreach ( $relations as $relation ) {
@@ -167,7 +167,7 @@ class PLL_Frontend_Filters extends PLL_Filters {
167
  return $sidebars_widgets;
168
  }
169
 
170
- $cache_key = md5( serialize( $sidebars_widgets ) );
171
  $_sidebars_widgets = $this->cache->get( "sidebars_widgets_{$cache_key}" );
172
 
173
  if ( false !== $_sidebars_widgets ) {
48
 
49
  // Support theme customizer
50
  // FIXME of course does not work if 'transport' is set to 'postMessage'
51
+ if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
52
  add_filter( 'pre_option_blogname', 'pll__', 20 );
53
  add_filter( 'pre_option_blogdescription', 'pll__', 20 );
54
  }
94
  $_posts = array_fill_keys( $languages, array() ); // Init with empty arrays
95
  $languages = implode( ',', $languages );
96
 
97
+ // PHPCS:ignore WordPress.DB.PreparedSQL
98
  $relations = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM {$wpdb->term_relationships} WHERE object_id IN ({$posts}) AND term_taxonomy_id IN ({$languages})" );
99
 
100
  foreach ( $relations as $relation ) {
167
  return $sidebars_widgets;
168
  }
169
 
170
+ $cache_key = md5( maybe_serialize( $sidebars_widgets ) );
171
  $_sidebars_widgets = $this->cache->get( "sidebars_widgets_{$cache_key}" );
172
 
173
  if ( false !== $_sidebars_widgets ) {
frontend/frontend-links.php CHANGED
@@ -58,12 +58,12 @@ class PLL_Frontend_Links extends PLL_Links {
58
  $hide = $this->options['default_lang'] == $language->slug && $this->options['hide_default'];
59
 
60
  // Post and attachment
61
- if ( is_single() && ( $this->options['media_support'] || ! is_attachment() ) && ( $id = $this->model->post->get( $queried_object_id, $language ) ) && $this->current_user_can_read( $id ) ) {
62
  $url = get_permalink( $id );
63
  }
64
 
65
  // Page
66
- elseif ( is_page() && ( $id = $this->model->post->get( $queried_object_id, $language ) ) && $this->current_user_can_read( $id ) ) {
67
  $url = get_page_link( $id );
68
  }
69
 
@@ -181,7 +181,7 @@ class PLL_Frontend_Links extends PLL_Links {
181
  * @return string
182
  */
183
  public function get_archive_url( $language ) {
184
- $url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
185
  $url = $this->links_model->switch_language_in_link( $url, $language );
186
  $url = $this->links_model->remove_paged_from_link( $url );
187
 
58
  $hide = $this->options['default_lang'] == $language->slug && $this->options['hide_default'];
59
 
60
  // Post and attachment
61
+ if ( is_single() && ( $this->options['media_support'] || ! is_attachment() ) && ( $id = $this->model->post->get( $queried_object_id, $language ) ) && $this->model->post->current_user_can_read( $id ) ) {
62
  $url = get_permalink( $id );
63
  }
64
 
65
  // Page
66
+ elseif ( is_page() && ( $id = $this->model->post->get( $queried_object_id, $language ) ) && $this->model->post->current_user_can_read( $id ) ) {
67
  $url = get_page_link( $id );
68
  }
69
 
181
  * @return string
182
  */
183
  public function get_archive_url( $language ) {
184
+ $url = pll_get_requested_url();
185
  $url = $this->links_model->switch_language_in_link( $url, $language );
186
  $url = $this->links_model->remove_paged_from_link( $url );
187
 
frontend/frontend-nav-menu.php CHANGED
@@ -30,7 +30,7 @@ class PLL_Frontend_Nav_Menu extends PLL_Nav_Menu {
30
  add_filter( 'wp_nav_menu_args', array( $this, 'wp_nav_menu_args' ) );
31
 
32
  // The customizer
33
- if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) {
34
  add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_before_customizer' ) );
35
  add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_after_customizer' ), 2000 );
36
  }
@@ -105,7 +105,8 @@ class PLL_Frontend_Nav_Menu extends PLL_Nav_Menu {
105
 
106
  // parent item for dropdown
107
  if ( ! empty( $options['dropdown'] ) ) {
108
- $item->title = $this->get_item_title( $this->curlang->flag, $this->curlang->name, $options );
 
109
  $item->attr_title = '';
110
  $item->classes = array( 'pll-parent-menu-item' );
111
  $new_items[] = $item;
@@ -223,20 +224,16 @@ class PLL_Frontend_Nav_Menu extends PLL_Nav_Menu {
223
  }
224
 
225
  // Support for theme customizer
226
- // Let's look for multilingual menu locations directly in $_POST as there are not in customizer object
227
- if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) {
228
- $customized = json_decode( wp_unslash( $_POST['customized'] ) );
229
-
230
- if ( is_object( $customized ) ) {
231
- foreach ( $customized as $key => $c ) {
232
- if ( false !== strpos( $key, 'nav_menu_locations[' ) ) {
233
- $loc = substr( trim( $key, ']' ), 19 );
234
- $infos = $this->explode_location( $loc );
235
- if ( $infos['lang'] == $this->curlang->slug ) {
236
- $menus[ $infos['location'] ] = $c;
237
- } elseif ( $this->curlang->slug == $this->options['default_lang'] ) {
238
- $menus[ $loc ] = $c;
239
- }
240
  }
241
  }
242
  }
30
  add_filter( 'wp_nav_menu_args', array( $this, 'wp_nav_menu_args' ) );
31
 
32
  // The customizer
33
+ if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
34
  add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_before_customizer' ) );
35
  add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_after_customizer' ), 2000 );
36
  }
105
 
106
  // parent item for dropdown
107
  if ( ! empty( $options['dropdown'] ) ) {
108
+ $name = isset( $options['display_names_as'] ) && 'slug' === $options['display_names_as'] ? $this->curlang->slug : $this->curlang->name;
109
+ $item->title = $this->get_item_title( $this->curlang->flag, $name, $options );
110
  $item->attr_title = '';
111
  $item->classes = array( 'pll-parent-menu-item' );
112
  $new_items[] = $item;
224
  }
225
 
226
  // Support for theme customizer
227
+ if ( is_customize_preview() ) {
228
+ global $wp_customize;
229
+ foreach ( $wp_customize->unsanitized_post_values() as $key => $value ) {
230
+ if ( false !== strpos( $key, 'nav_menu_locations[' ) ) {
231
+ $loc = substr( trim( $key, ']' ), 19 );
232
+ $infos = $this->explode_location( $loc );
233
+ if ( $infos['lang'] === $this->curlang->slug ) {
234
+ $menus[ $infos['location'] ] = (int) $value;
235
+ } elseif ( $this->curlang->slug === $this->options['default_lang'] ) {
236
+ $menus[ $loc ] = (int) $value;
 
 
 
 
237
  }
238
  }
239
  }
frontend/frontend-static-pages.php CHANGED
@@ -47,7 +47,7 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
47
  add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
48
 
49
  // Support theme customizer
50
- if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) {
51
  add_filter( 'pre_option_page_on_front', 'pll_get_post', 20 );
52
  add_filter( 'pre_option_page_for_post', 'pll_get_post', 20 );
53
  }
@@ -90,7 +90,7 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
90
  $url = is_paged() ? $this->links_model->add_paged_to_link( $this->links->get_home_url(), $wp_query->query_vars['page'] ) : $this->links->get_home_url();
91
 
92
  // Don't forget additional query vars
93
- $query = parse_url( $redirect_url, PHP_URL_QUERY );
94
  if ( ! empty( $query ) ) {
95
  parse_str( $query, $query_vars );
96
  $query_vars = rawurlencode_deep( $query_vars ); // WP encodes query vars values
@@ -138,7 +138,7 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
138
  * @return bool|string
139
  */
140
  public function pll_check_canonical_url( $redirect_url ) {
141
- return $this->options['redirect_lang'] && ! empty( $this->curlang->page_on_front ) && is_page( $this->curlang->page_on_front ) ? false : $redirect_url;
142
  }
143
 
144
  /**
@@ -175,10 +175,15 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
175
 
176
  // Redirect the language page to the homepage when using a static front page
177
  elseif ( ( $this->options['redirect_lang'] || $this->options['hide_default'] ) && $this->is_front_page( $query ) && $lang = $this->model->get_language( get_query_var( 'lang' ) ) ) {
178
- $query->set( 'page_id', $lang->page_on_front );
179
- $query->is_singular = $query->is_page = true;
180
  $query->is_archive = $query->is_tax = false;
181
- unset( $query->query_vars['lang'], $query->queried_object ); // Reset queried object
 
 
 
 
 
 
 
182
  }
183
 
184
  // Fix paged static front page in plain permalinks when Settings > Reading doesn't match the default language
47
  add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
48
 
49
  // Support theme customizer
50
+ if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
51
  add_filter( 'pre_option_page_on_front', 'pll_get_post', 20 );
52
  add_filter( 'pre_option_page_for_post', 'pll_get_post', 20 );
53
  }
90
  $url = is_paged() ? $this->links_model->add_paged_to_link( $this->links->get_home_url(), $wp_query->query_vars['page'] ) : $this->links->get_home_url();
91
 
92
  // Don't forget additional query vars
93
+ $query = wp_parse_url( $redirect_url, PHP_URL_QUERY );
94
  if ( ! empty( $query ) ) {
95
  parse_str( $query, $query_vars );
96
  $query_vars = rawurlencode_deep( $query_vars ); // WP encodes query vars values
138
  * @return bool|string
139
  */
140
  public function pll_check_canonical_url( $redirect_url ) {
141
+ return $this->options['redirect_lang'] && ! $this->options['force_lang'] && ! empty( $this->curlang->page_on_front ) && is_page( $this->curlang->page_on_front ) ? false : $redirect_url;
142
  }
143
 
144
  /**
175
 
176
  // Redirect the language page to the homepage when using a static front page
177
  elseif ( ( $this->options['redirect_lang'] || $this->options['hide_default'] ) && $this->is_front_page( $query ) && $lang = $this->model->get_language( get_query_var( 'lang' ) ) ) {
 
 
178
  $query->is_archive = $query->is_tax = false;
179
+ if ( ! empty( $lang->page_on_front ) ) {
180
+ $query->set( 'page_id', $lang->page_on_front );
181
+ $query->is_singular = $query->is_page = true;
182
+ unset( $query->query_vars['lang'], $query->queried_object ); // Reset queried object
183
+ } else {
184
+ // Handle case where the static front page hasn't be translated to avoid a possible infinite redirect loop.
185
+ $query->is_home = true;
186
+ }
187
  }
188
 
189
  // Fix paged static front page in plain permalinks when Settings > Reading doesn't match the default language
frontend/frontend.php CHANGED
@@ -10,7 +10,7 @@
10
  * links_model => inherited, reference to PLL_Links_Model object
11
  * links => reference to PLL_Links object
12
  * static_pages => reference to PLL_Frontend_Static_Pages object
13
- * choose_lang => reference to PLL_Choose_lang object
14
  * curlang => current language
15
  * filters => reference to PLL_Frontend_Filters object
16
  * filters_links => reference to PLL_Frontend_Filters_Links object
@@ -51,54 +51,51 @@ class PLL_Frontend extends PLL_Base {
51
  }
52
  }
53
 
54
- /**
55
- * Is the current request a REST API request?
56
- * Inspired by WP::parse_request()
57
- * Needed because at this point, the constant REST_REQUEST is not defined yet
58
- *
59
- * @since 2.4.1
60
- *
61
- * @return bool
62
- */
63
- public function is_rest_request() {
64
- $home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
65
- $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
66
-
67
- $req_uri = trim( $_SERVER['REQUEST_URI'], '/' );
68
- $req_uri = preg_replace( $home_path_regex, '', $req_uri );
69
- $req_uri = trim( $req_uri, '/' );
70
- $req_uri = str_replace( 'index.php', '', $req_uri );
71
- $req_uri = trim( $req_uri, '/' );
72
-
73
- return 0 === strpos( $req_uri, rest_get_url_prefix() . '/' );
74
- }
75
-
76
  /**
77
  * Setups the language chooser based on options
78
  *
79
  * @since 1.2
80
  */
81
  public function init() {
 
 
82
  $this->links = new PLL_Frontend_Links( $this );
83
 
84
- // Don't set any language for REST requests when Polylang Pro is not active
85
- if ( ! class_exists( 'PLL_REST_Translated_Object' ) && $this->is_rest_request() ) {
86
- /** This action is documented in include/class-polylang.php */
87
- do_action( 'pll_no_language_defined' );
88
- } else {
89
- // Static front page and page for posts
90
- if ( 'page' === get_option( 'show_on_front' ) ) {
91
- $this->static_pages = new PLL_Frontend_Static_Pages( $this );
 
 
 
 
 
 
 
 
 
 
 
92
  }
 
93
 
94
- // Setup the language chooser
95
- $c = array( 'Content', 'Url', 'Url', 'Domain' );
96
- $class = 'PLL_Choose_Lang_' . $c[ $this->options['force_lang'] ];
97
- $this->choose_lang = new $class( $this );
98
- $this->choose_lang->init();
 
99
 
100
- // Need to load nav menu class early to correctly define the locations in the customizer when the language is set from the content
101
- $this->nav_menu = new PLL_Frontend_Nav_Menu( $this );
 
 
 
 
102
  }
103
  }
104
 
@@ -163,7 +160,7 @@ class PLL_Frontend extends PLL_Base {
163
 
164
  // Remove pages query when the language is set unless we do a search
165
  // Take care not to break the single page, attachment and taxonomies queries!
166
- if ( empty( $qv['post_type'] ) && ! $query->is_search && ! $query->is_page && ! $query->is_attachment && empty( $taxonomies ) ) {
167
  $query->set( 'post_type', 'post' );
168
  }
169
 
10
  * links_model => inherited, reference to PLL_Links_Model object
11
  * links => reference to PLL_Links object
12
  * static_pages => reference to PLL_Frontend_Static_Pages object
13
+ * choose_lang => reference to PLL_Choose_Lang object
14
  * curlang => current language
15
  * filters => reference to PLL_Frontend_Filters object
16
  * filters_links => reference to PLL_Frontend_Filters_Links object
51
  }
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  /**
55
  * Setups the language chooser based on options
56
  *
57
  * @since 1.2
58
  */
59
  public function init() {
60
+ parent::init();
61
+
62
  $this->links = new PLL_Frontend_Links( $this );
63
 
64
+ // Static front page and page for posts
65
+ if ( 'page' === get_option( 'show_on_front' ) ) {
66
+ $this->static_pages = new PLL_Frontend_Static_Pages( $this );
67
+ }
68
+
69
+ // Setup the language chooser
70
+ $c = array( 'Content', 'Url', 'Url', 'Domain' );
71
+ $class = 'PLL_Choose_Lang_' . $c[ $this->options['force_lang'] ];
72
+ $this->choose_lang = new $class( $this );
73
+ $this->choose_lang->init();
74
+
75
+ // Need to load nav menu class early to correctly define the locations in the customizer when the language is set from the content
76
+ $this->nav_menu = new PLL_Frontend_Nav_Menu( $this );
77
+
78
+ // Cross domain
79
+ if ( PLL_COOKIE ) {
80
+ $class = array( 2 => 'PLL_Xdata_Subdomain', 3 => 'PLL_Xdata_Domain' );
81
+ if ( isset( $class[ $this->options['force_lang'] ] ) && class_exists( $class[ $this->options['force_lang'] ] ) ) {
82
+ $this->xdata = new $class[ $this->options['force_lang'] ]( $this );
83
  }
84
+ }
85
 
86
+ if ( get_option( 'permalink_structure' ) ) {
87
+ // Translate slugs
88
+ if ( class_exists( 'PLL_Frontend_Translate_Slugs' ) ) {
89
+ $slugs_model = new PLL_Translate_Slugs_Model( $this );
90
+ $this->translate_slugs = new PLL_Frontend_Translate_Slugs( $slugs_model, $this->curlang );
91
+ }
92
 
93
+ // Share term slugs
94
+ if ( $this->options['force_lang'] && class_exists( 'PLL_Share_Term_Slug' ) ) {
95
+ $this->share_term_slug = version_compare( $GLOBALS['wp_version'], '4.8', '<' ) ?
96
+ new PLL_Frontend_Share_Term_Slug( $this ) :
97
+ new PLL_Share_Term_Slug( $this );
98
+ }
99
  }
100
  }
101
 
160
 
161
  // Remove pages query when the language is set unless we do a search
162
  // Take care not to break the single page, attachment and taxonomies queries!
163
+ if ( empty( $qv['post_type'] ) && ! $query->is_search && ! $query->is_singular && empty( $taxonomies ) ) {
164
  $query->set( 'post_type', 'post' );
165
  }
166
 
include/api.php CHANGED
@@ -123,7 +123,7 @@ function pll_register_string( $name, $string, $context = 'polylang', $multiline
123
  * @return string the string translation in the current language
124
  */
125
  function pll__( $string ) {
126
- return is_scalar( $string ) ? __( $string, 'pll_string' ) : $string; // PHPCS:ignore WordPress.WP.I18n.NonSingularStringLiteralText
127
  }
128
 
129
  /**
@@ -152,13 +152,14 @@ function pll_esc_attr__( $string ) {
152
 
153
  /**
154
  * Echoes a translated string ( previously registered with pll_register_string )
 
155
  *
156
  * @since 0.6
157
  *
158
  * @param string $string The string to translate
159
  */
160
  function pll_e( $string ) {
161
- echo pll__( $string );
162
  }
163
 
164
  /**
@@ -169,7 +170,7 @@ function pll_e( $string ) {
169
  * @param string $string The string to translate
170
  */
171
  function pll_esc_html_e( $string ) {
172
- echo pll_esc_html__( $string );
173
  }
174
 
175
  /**
@@ -180,7 +181,7 @@ function pll_esc_html_e( $string ) {
180
  * @param string $string The string to translate
181
  */
182
  function pll_esc_attr_e( $string ) {
183
- echo pll_esc_attr__( $string );
184
  }
185
 
186
  /**
123
  * @return string the string translation in the current language
124
  */
125
  function pll__( $string ) {
126
+ return is_scalar( $string ) ? __( $string, 'pll_string' ) : $string; // PHPCS:ignore WordPress.WP.I18n
127
  }
128
 
129
  /**
152
 
153
  /**
154
  * Echoes a translated string ( previously registered with pll_register_string )
155
+ * It is an equivalent of _e() and is not escaped.
156
  *
157
  * @since 0.6
158
  *
159
  * @param string $string The string to translate
160
  */
161
  function pll_e( $string ) {
162
+ echo pll__( $string ); // phpcs:ignore
163
  }
164
 
165
  /**
170
  * @param string $string The string to translate
171
  */
172
  function pll_esc_html_e( $string ) {
173
+ echo pll_esc_html__( $string ); // phpcs:ignore WordPress.Security.EscapeOutput
174
  }
175
 
176
  /**
181
  * @param string $string The string to translate
182
  */
183
  function pll_esc_attr_e( $string ) {
184
+ echo pll_esc_attr__( $string ); // phpcs:ignore WordPress.Security.EscapeOutput
185
  }
186
 
187
  /**
include/base.php CHANGED
@@ -32,6 +32,30 @@ abstract class PLL_Base {
32
  add_action( 'switch_blog', array( $this, 'switch_blog' ), 10, 2 );
33
  }
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  /**
36
  * Registers our widgets
37
  *
@@ -110,15 +134,15 @@ abstract class PLL_Base {
110
  foreach ( $this as $prop => &$obj ) {
111
  if ( is_object( $obj ) && method_exists( $obj, $func ) ) {
112
  if ( WP_DEBUG ) {
113
- $debug = debug_backtrace();
114
  $i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func
115
- trigger_error(
116
  sprintf(
117
  '%1$s was called incorrectly in %3$s on line %4$s: the call to $polylang->%1$s() has been deprecated in Polylang 1.2, use PLL()->%2$s->%1$s() instead.' . "\nError handler",
118
- $func,
119
- $prop,
120
- $debug[ $i ]['file'],
121
- $debug[ $i ]['line']
122
  )
123
  );
124
  }
@@ -126,7 +150,15 @@ abstract class PLL_Base {
126
  }
127
  }
128
 
129
- $debug = debug_backtrace();
130
- trigger_error( sprintf( 'Call to undefined function PLL()->%1$s() in %2$s on line %3$s' . "\nError handler", $func, $debug[0]['file'], $debug[0]['line'] ), E_USER_ERROR );
 
 
 
 
 
 
 
 
131
  }
132
  }
32
  add_action( 'switch_blog', array( $this, 'switch_blog' ), 10, 2 );
33
  }
34
 
35
+ /**
36
+ * Instantiate classes always needed
37
+ *
38
+ * @since 2.6
39
+ */
40
+ public function init() {
41
+ // REST API
42
+ if ( class_exists( 'PLL_REST_API' ) ) {
43
+ $this->rest_api = new PLL_REST_API( $this );
44
+ }
45
+
46
+ if ( $this->model->get_languages_list() ) {
47
+ // Active languages
48
+ if ( class_exists( 'PLL_Active_Languages' ) ) {
49
+ $this->active_languages = new PLL_Active_Languages( $this );
50
+ }
51
+
52
+ // Share post slugs
53
+ if ( get_option( 'permalink_structure' ) && $this->options['force_lang'] && class_exists( 'PLL_Share_Post_Slug' ) ) {
54
+ $this->share_post_slug = new PLL_Share_Post_Slug( $this );
55
+ }
56
+ }
57
+ }
58
+
59
  /**
60
  * Registers our widgets
61
  *
134
  foreach ( $this as $prop => &$obj ) {
135
  if ( is_object( $obj ) && method_exists( $obj, $func ) ) {
136
  if ( WP_DEBUG ) {
137
+ $debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
138
  $i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func
139
+ trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
140
  sprintf(
141
  '%1$s was called incorrectly in %3$s on line %4$s: the call to $polylang->%1$s() has been deprecated in Polylang 1.2, use PLL()->%2$s->%1$s() instead.' . "\nError handler",
142
+ esc_html( $func ),
143
+ esc_html( $prop ),
144
+ esc_html( $debug[ $i ]['file'] ),
145
+ absint( $debug[ $i ]['line'] )
146
  )
147
  );
148
  }
150
  }
151
  }
152
 
153
+ $debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
154
+ trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
155
+ sprintf(
156
+ 'Call to undefined function PLL()->%1$s() in %2$s on line %3$s' . "\nError handler",
157
+ esc_html( $func ),
158
+ esc_html( $debug[0]['file'] ),
159
+ absint( $debug[0]['line'] )
160
+ ),
161
+ E_USER_ERROR
162
+ );
163
  }
164
  }
include/class-polylang.php CHANGED
@@ -68,18 +68,32 @@ class Polylang {
68
  }
69
 
70
  $class = str_replace( '_', '-', strtolower( substr( $class, 4 ) ) );
71
- $to_find = array( 'media', 'share', 'slug', 'slugs', 'sync', 'translate', 'wpml', 'xdata', 'rest', 'bulk' );
72
- $dir = implode( '-', array_intersect( explode( '-', $class ), $to_find ) );
73
-
74
- $dirs = array(
75
- PLL_FRONT_INC,
76
- PLL_MODULES_INC,
77
- PLL_MODULES_INC . "/$dir",
78
- PLL_MODULES_INC . '/plugins',
79
- PLL_INSTALL_INC,
80
- PLL_ADMIN_INC,
81
- PLL_SETTINGS_INC,
82
- PLL_INC,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  );
84
 
85
  foreach ( $dirs as $dir ) {
@@ -100,8 +114,8 @@ class Polylang {
100
  public static function is_ajax_on_front() {
101
  // Special test for plupload which does not use jquery ajax and thus does not pass our ajax prefilter
102
  // Special test for customize_save done in frontend but for which we want to load the admin
103
- $in = isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], array( 'upload-attachment', 'customize_save' ) );
104
- $is_ajax_on_front = wp_doing_ajax() && empty( $_REQUEST['pll_ajax_backend'] ) && ! $in;
105
 
106
  /**
107
  * Filters whether the current request is an ajax request on front.
@@ -113,6 +127,28 @@ class Polylang {
113
  return apply_filters( 'pll_is_ajax_on_front', $is_ajax_on_front );
114
  }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  /**
117
  * Defines constants
118
  * May be overridden by a plugin if set before plugins_loaded, 1
@@ -132,12 +168,12 @@ class Polylang {
132
 
133
  // Admin
134
  if ( ! defined( 'PLL_ADMIN' ) ) {
135
- define( 'PLL_ADMIN', defined( 'DOING_CRON' ) || ( defined( 'WP_CLI' ) && WP_CLI ) || ( is_admin() && ! PLL_AJAX_ON_FRONT ) );
136
  }
137
 
138
  // Settings page whatever the tab
139
  if ( ! defined( 'PLL_SETTINGS' ) ) {
140
- define( 'PLL_SETTINGS', is_admin() && ( ( isset( $_GET['page'] ) && 0 === strpos( $_GET['page'], 'mlang' ) ) || ! empty( $_REQUEST['pll_ajax_settings'] ) ) );
141
  }
142
  }
143
 
@@ -176,17 +212,6 @@ class Polylang {
176
  $model = new $class( $options );
177
  $links_model = $model->get_links_model();
178
 
179
- if ( PLL_SETTINGS ) {
180
- $polylang = new PLL_Settings( $links_model );
181
- }
182
- elseif ( PLL_ADMIN ) {
183
- $polylang = new PLL_Admin( $links_model );
184
- }
185
- // Do nothing on frontend if no language is defined
186
- elseif ( $model->get_languages_list() && empty( $_GET['deactivate-polylang'] ) ) {
187
- $polylang = new PLL_Frontend( $links_model );
188
- }
189
-
190
  if ( ! $model->get_languages_list() ) {
191
  /**
192
  * Fires when no language has been defined yet
@@ -197,7 +222,30 @@ class Polylang {
197
  do_action( 'pll_no_language_defined' );
198
  }
199
 
200
- if ( ! empty( $polylang ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  /**
202
  * Fires after the $polylang object is created and before the API is loaded
203
  *
@@ -227,5 +275,3 @@ class Polylang {
227
  }
228
  }
229
  }
230
-
231
- new Polylang();
68
  }
69
 
70
  $class = str_replace( '_', '-', strtolower( substr( $class, 4 ) ) );
71
+ $dirs = array();
72
+ $parts = explode( '-', $class );
73
+ $parts = array_values( array_diff( $parts, array( 'frontend', 'admin', 'settings', 'advanced' ) ) );
74
+ if ( isset( $parts[0] ) ) {
75
+ $dirs[] = PLL_MODULES_INC . "/{$parts[0]}";
76
+ if ( isset( $parts[1] ) ) {
77
+ $dirs[] = PLL_MODULES_INC . "/{$parts[0]}-{$parts[1]}";
78
+ if ( isset( $parts[2] ) && in_array( $parts[1], array( 'post', 'term' ) ) ) {
79
+ $dirs[] = PLL_MODULES_INC . "/{$parts[0]}-{$parts[2]}";
80
+ }
81
+ }
82
+ }
83
+
84
+ $dirs = array_merge(
85
+ array(
86
+ PLL_FRONT_INC,
87
+ PLL_MODULES_INC,
88
+ ),
89
+ $dirs,
90
+ array(
91
+ PLL_MODULES_INC . '/plugins',
92
+ PLL_INSTALL_INC,
93
+ PLL_ADMIN_INC,
94
+ PLL_SETTINGS_INC,
95
+ PLL_INC,
96
+ )
97
  );
98
 
99
  foreach ( $dirs as $dir ) {
114
  public static function is_ajax_on_front() {
115
  // Special test for plupload which does not use jquery ajax and thus does not pass our ajax prefilter
116
  // Special test for customize_save done in frontend but for which we want to load the admin
117
+ $in = isset( $_REQUEST['action'] ) && in_array( sanitize_key( $_REQUEST['action'] ), array( 'upload-attachment', 'customize_save' ) ); // phpcs:ignore WordPress.Security.NonceVerification
118
+ $is_ajax_on_front = wp_doing_ajax() && empty( $_REQUEST['pll_ajax_backend'] ) && ! $in; // phpcs:ignore WordPress.Security.NonceVerification
119
 
120
  /**
121
  * Filters whether the current request is an ajax request on front.
127
  return apply_filters( 'pll_is_ajax_on_front', $is_ajax_on_front );
128
  }
129
 
130
+ /**
131
+ * Is the current request a REST API request?
132
+ * Inspired by WP::parse_request()
133
+ * Needed because at this point, the constant REST_REQUEST is not defined yet
134
+ *
135
+ * @since 2.4.1
136
+ *
137
+ * @return bool
138
+ */
139
+ public static function is_rest_request() {
140
+ $home_path = trim( wp_parse_url( home_url(), PHP_URL_PATH ), '/' );
141
+ $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
142
+
143
+ $req_uri = trim( wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
144
+ $req_uri = preg_replace( $home_path_regex, '', $req_uri );
145
+ $req_uri = trim( $req_uri, '/' );
146
+ $req_uri = str_replace( 'index.php', '', $req_uri );
147
+ $req_uri = trim( $req_uri, '/' );
148
+
149
+ return 0 === strpos( $req_uri, rest_get_url_prefix() . '/' );
150
+ }
151
+
152
  /**
153
  * Defines constants
154
  * May be overridden by a plugin if set before plugins_loaded, 1
168
 
169
  // Admin
170
  if ( ! defined( 'PLL_ADMIN' ) ) {
171
+ define( 'PLL_ADMIN', wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) || ( is_admin() && ! PLL_AJAX_ON_FRONT ) );
172
  }
173
 
174
  // Settings page whatever the tab
175
  if ( ! defined( 'PLL_SETTINGS' ) ) {
176
+ define( 'PLL_SETTINGS', is_admin() && ( ( isset( $_GET['page'] ) && 0 === strpos( sanitize_key( $_GET['page'] ), 'mlang' ) ) || ! empty( $_REQUEST['pll_ajax_settings'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
177
  }
178
  }
179
 
212
  $model = new $class( $options );
213
  $links_model = $model->get_links_model();
214
 
 
 
 
 
 
 
 
 
 
 
 
215
  if ( ! $model->get_languages_list() ) {
216
  /**
217
  * Fires when no language has been defined yet
222
  do_action( 'pll_no_language_defined' );
223
  }
224
 
225
+ $class = '';
226
+
227
+ if ( PLL_SETTINGS ) {
228
+ $class = 'PLL_Settings';
229
+ } elseif ( PLL_ADMIN ) {
230
+ $class = 'PLL_Admin';
231
+ } elseif ( self::is_rest_request() ) {
232
+ $class = 'PLL_REST_Request';
233
+ } elseif ( $model->get_languages_list() && empty( $_GET['deactivate-polylang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
234
+ $class = 'PLL_Frontend';
235
+ }
236
+
237
+ /**
238
+ * Filters the class to use to instantiate the $polylang object
239
+ *
240
+ * @since 2.6
241
+ *
242
+ * @param string $class A class name.
243
+ */
244
+ $class = apply_filters( 'pll_context', $class );
245
+
246
+ if ( ! empty( $class ) ) {
247
+ $polylang = new $class( $links_model );
248
+
249
  /**
250
  * Fires after the $polylang object is created and before the API is loaded
251
  *
275
  }
276
  }
277
  }
 
 
include/crud-posts.php CHANGED
@@ -42,10 +42,10 @@ class PLL_CRUD_Posts {
42
  */
43
  public function set_default_language( $post_id ) {
44
  if ( ! $this->model->post->get_language( $post_id ) ) {
45
- if ( ! empty( $_GET['new_lang'] ) && $lang = $this->model->get_language( $_GET['new_lang'] ) ) {
46
  // Defined only on admin.
47
  $this->model->post->set_language( $post_id, $lang );
48
- } elseif ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( $_REQUEST['lang'] ) ) {
49
  // Testing $this->pref_lang makes this test pass only on admin.
50
  $this->model->post->set_language( $post_id, $lang );
51
  } elseif ( ( $parent_id = wp_get_post_parent_id( $post_id ) ) && $parent_lang = $this->model->post->get_language( $parent_id ) ) {
@@ -260,13 +260,13 @@ class PLL_CRUD_Posts {
260
  $post->ID = null; // Will force the creation
261
  $post->post_parent = ( $post->post_parent && $tr_parent = $this->model->post->get_translation( $post->post_parent, $lang->slug ) ) ? $tr_parent : 0;
262
  $post->tax_input = array( 'language' => array( $lang->slug ) ); // Assigns the language
263
- $tr_id = wp_insert_attachment( $post );
264
  remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload
265
 
266
  // Copy metadata, attached file and alternative text
267
  foreach ( array( '_wp_attachment_metadata', '_wp_attached_file', '_wp_attachment_image_alt' ) as $key ) {
268
  if ( $meta = get_post_meta( $post_id, $key, true ) ) {
269
- add_post_meta( $tr_id, $key, $meta );
270
  }
271
  }
272
 
42
  */
43
  public function set_default_language( $post_id ) {
44
  if ( ! $this->model->post->get_language( $post_id ) ) {
45
+ if ( ! empty( $_GET['new_lang'] ) && $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
46
  // Defined only on admin.
47
  $this->model->post->set_language( $post_id, $lang );
48
+ } elseif ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
49
  // Testing $this->pref_lang makes this test pass only on admin.
50
  $this->model->post->set_language( $post_id, $lang );
51
  } elseif ( ( $parent_id = wp_get_post_parent_id( $post_id ) ) && $parent_lang = $this->model->post->get_language( $parent_id ) ) {
260
  $post->ID = null; // Will force the creation
261
  $post->post_parent = ( $post->post_parent && $tr_parent = $this->model->post->get_translation( $post->post_parent, $lang->slug ) ) ? $tr_parent : 0;
262
  $post->tax_input = array( 'language' => array( $lang->slug ) ); // Assigns the language
263
+ $tr_id = wp_insert_attachment( wp_slash( (array) $post ) );
264
  remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload
265
 
266
  // Copy metadata, attached file and alternative text
267
  foreach ( array( '_wp_attachment_metadata', '_wp_attached_file', '_wp_attachment_image_alt' ) as $key ) {
268
  if ( $meta = get_post_meta( $post_id, $key, true ) ) {
269
+ add_post_meta( $tr_id, $key, wp_slash( $meta ) );
270
  }
271
  }
272
 
include/crud-terms.php CHANGED
@@ -49,7 +49,7 @@ class PLL_CRUD_Terms {
49
  */
50
  protected function set_default_language( $term_id, $taxonomy ) {
51
  if ( ! $this->model->term->get_language( $term_id ) ) {
52
- if ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( $_REQUEST['lang'] ) ) {
53
  // Testing $this->pref_lang makes this test pass only on frontend.
54
  $this->model->term->set_language( $term_id, $lang );
55
  } elseif ( ( $term = get_term( $term_id, $taxonomy ) ) && ! empty( $term->parent ) && $parent_lang = $this->model->term->get_language( $term->parent ) ) {
49
  */
50
  protected function set_default_language( $term_id, $taxonomy ) {
51
  if ( ! $this->model->term->get_language( $term_id ) ) {
52
+ if ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
53
  // Testing $this->pref_lang makes this test pass only on frontend.
54
  $this->model->term->set_language( $term_id, $lang );
55
  } elseif ( ( $term = get_term( $term_id, $taxonomy ) ) && ! empty( $term->parent ) && $parent_lang = $this->model->term->get_language( $term->parent ) ) {
include/filters.php CHANGED
@@ -353,7 +353,7 @@ class PLL_Filters {
353
  if ( ! empty( $user_data_to_export ) ) {
354
  $data_to_export[] = array(
355
  'group_id' => 'user',
356
- 'group_label' => __( 'User' ),
357
  'item_id' => "user-{$user->ID}",
358
  'data' => $user_data_to_export,
359
  );
353
  if ( ! empty( $user_data_to_export ) ) {
354
  $data_to_export[] = array(
355
  'group_id' => 'user',
356
+ 'group_label' => __( 'User', 'polylang' ),
357
  'item_id' => "user-{$user->ID}",
358
  'data' => $user_data_to_export,
359
  );
include/functions.php CHANGED
@@ -84,6 +84,36 @@ if ( ! function_exists( 'wp_doing_ajax' ) ) {
84
  }
85
  }
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  /**
88
  * Determines whether we should load the cache compatibility
89
  *
@@ -102,3 +132,41 @@ function pll_is_cache_active() {
102
  */
103
  return apply_filters( 'pll_is_cache_active', ( defined( 'WP_CACHE' ) && WP_CACHE ) || defined( 'WPFC_MAIN_PATH' ) );
104
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
  }
86
 
87
+ if ( ! function_exists( 'wp_doing_cron' ) ) {
88
+ /**
89
+ * Determines whether the current request is a WordPress cron request.
90
+ * Backward compatibility function for WP < 4.8
91
+ *
92
+ * @since 2.6
93
+ *
94
+ * @return bool True if it's a WordPress cron request, false otherwise.
95
+ */
96
+ function wp_doing_cron() {
97
+ /** This filter is documented in wp-includes/load.php */
98
+ return apply_filters( 'wp_doing_cron', defined( 'DOING_CRON' ) && DOING_CRON );
99
+ }
100
+ }
101
+
102
+ if ( ! function_exists( 'wp_using_themes' ) ) {
103
+ /**
104
+ * Determines whether the current request should use themes.
105
+ * Backward compatibility function for WP < 5.1
106
+ *
107
+ * @since 2.6
108
+ *
109
+ * @return bool True if themes should be used, false otherwise.
110
+ */
111
+ function wp_using_themes() {
112
+ /** This filter is documented in wp-includes/load.php */
113
+ return apply_filters( 'wp_using_themes', defined( 'WP_USE_THEMES' ) && WP_USE_THEMES );
114
+ }
115
+ }
116
+
117
  /**
118
  * Determines whether we should load the cache compatibility
119
  *
132
  */
133
  return apply_filters( 'pll_is_cache_active', ( defined( 'WP_CACHE' ) && WP_CACHE ) || defined( 'WPFC_MAIN_PATH' ) );
134
  }
135
+
136
+ /**
137
+ * Get the the current requested url
138
+ *
139
+ * @since 2.6
140
+ *
141
+ * @return string Requested url
142
+ */
143
+ function pll_get_requested_url() {
144
+ if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
145
+ return set_url_scheme( esc_url_raw( wp_unslash( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) );
146
+ }
147
+
148
+ if ( WP_DEBUG ) {
149
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions
150
+ trigger_error( '$_SERVER[\'HTTP_HOST\'] or $_SERVER[\'REQUEST_URI\'] are required but not set.' );
151
+ }
152
+
153
+ return '';
154
+ }
155
+
156
+ /**
157
+ * Determines whether we should load the block editor plugin or the legacy languages metabox.
158
+ *
159
+ * @since 2.6.0
160
+ *
161
+ * return bool True to use the block editor plugin.
162
+ */
163
+ function pll_use_block_editor_plugin() {
164
+ /**
165
+ * Filters whether we should load the block editor plugin or the legacy languages metabox.
166
+ *
167
+ * @since 2.6.0
168
+ *
169
+ * @param bool $use_plugin True when loading the block editor plugin.
170
+ */
171
+ return class_exists( 'PLL_Block_Editor_Plugin' ) && apply_filters( 'pll_use_block_editor_plugin', ! defined( 'PLL_USE_BLOCK_EDITOR_PLUGIN' ) || PLL_USE_BLOCK_EDITOR_PLUGIN );
172
+ }
include/language.php CHANGED
@@ -81,7 +81,7 @@ class PLL_Language {
81
 
82
  $this->mo_id = PLL_MO::get_id( $this );
83
 
84
- include PLL_SETTINGS_INC . '/languages.php';
85
  $this->w3c = isset( $languages[ $this->locale ]['w3c'] ) ? $languages[ $this->locale ]['w3c'] : str_replace( '_', '-', $this->locale );
86
  if ( isset( $languages[ $this->locale ]['facebook'] ) ) {
87
  $this->facebook = $languages[ $this->locale ]['facebook'];
@@ -90,16 +90,23 @@ class PLL_Language {
90
  }
91
 
92
  /**
93
- * Sets flag_url and flag properties
 
 
 
 
94
  *
95
- * @since 1.2
 
 
 
96
  */
97
- public function set_flag() {
98
- $flags['flag']['url'] = '';
99
 
100
  // Polylang builtin flags
101
- if ( ! empty( $this->flag_code ) && file_exists( POLYLANG_DIR . ( $file = '/flags/' . $this->flag_code . '.png' ) ) ) {
102
- $flags['flag']['url'] = $_url = plugins_url( $file, POLYLANG_FILE );
103
  }
104
 
105
  /**
@@ -114,18 +121,31 @@ class PLL_Language {
114
  * @param array $flag Information about the flag
115
  * @param string $code Flag code
116
  */
117
- $flags['flag'] = apply_filters( 'pll_flag', $flags['flag'], $this->flag_code );
118
 
119
- if ( empty( $flags['flag']['src'] ) ) {
120
  // If using predefined flags and base64 encoded flags are preferred
121
- if ( isset( $_url ) && $flags['flag']['url'] === $_url && ( ! defined( 'PLL_ENCODED_FLAGS' ) || PLL_ENCODED_FLAGS ) ) {
122
- $flags['flag']['src'] = 'data:image/png;base64,' . base64_encode( file_get_contents( POLYLANG_DIR . $file ) );
 
 
123
  } else {
124
- $flags['flag']['src'] = esc_url( set_url_scheme( $flags['flag']['url'], 'relative' ) );
125
  }
126
  }
127
 
128
- $flags['flag']['url'] = esc_url_raw( $flags['flag']['url'] );
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  // Custom flags ?
131
  $directories = array(
81
 
82
  $this->mo_id = PLL_MO::get_id( $this );
83
 
84
+ $languages = include PLL_SETTINGS_INC . '/languages.php';
85
  $this->w3c = isset( $languages[ $this->locale ]['w3c'] ) ? $languages[ $this->locale ]['w3c'] : str_replace( '_', '-', $this->locale );
86
  if ( isset( $languages[ $this->locale ]['facebook'] ) ) {
87
  $this->facebook = $languages[ $this->locale ]['facebook'];
90
  }
91
 
92
  /**
93
+ * Get the flag informations
94
+ * 'url' => Flag url
95
+ * 'src' => Optional, src attribute value if different of the url, for example if base64 encoded
96
+ * 'width' => Optional, flag width in pixels
97
+ * 'height' => Optional, flag height in pixels
98
  *
99
+ * @since 2.6
100
+ *
101
+ * @param string $code Flag code.
102
+ * @return array Flag informations.
103
  */
104
+ public static function get_flag_informations( $code ) {
105
+ $flag['url'] = '';
106
 
107
  // Polylang builtin flags
108
+ if ( ! empty( $code ) && file_exists( POLYLANG_DIR . ( $file = '/flags/' . $code . '.png' ) ) ) {
109
+ $flag['url'] = $_url = plugins_url( $file, POLYLANG_FILE );
110
  }
111
 
112
  /**
121
  * @param array $flag Information about the flag
122
  * @param string $code Flag code
123
  */
124
+ $flag = apply_filters( 'pll_flag', $flag, $code );
125
 
126
+ if ( empty( $flag['src'] ) ) {
127
  // If using predefined flags and base64 encoded flags are preferred
128
+ if ( isset( $_url ) && $flag['url'] === $_url && ( ! defined( 'PLL_ENCODED_FLAGS' ) || PLL_ENCODED_FLAGS ) ) {
129
+ list( $flag['width'], $flag['height'] ) = getimagesize( POLYLANG_DIR . $file );
130
+ $file_contents = file_get_contents( POLYLANG_DIR . $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
131
+ $flag['src'] = 'data:image/png;base64,' . base64_encode( $file_contents ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
132
  } else {
133
+ $flag['src'] = esc_url( set_url_scheme( $flag['url'], 'relative' ) );
134
  }
135
  }
136
 
137
+ $flag['url'] = esc_url_raw( $flag['url'] );
138
+
139
+ return $flag;
140
+ }
141
+
142
+ /**
143
+ * Sets flag_url and flag properties
144
+ *
145
+ * @since 1.2
146
+ */
147
+ public function set_flag() {
148
+ $flags['flag'] = self::get_flag_informations( $this->flag_code );
149
 
150
  // Custom flags ?
151
  $directories = array(
include/links-abstract-domain.php CHANGED
@@ -36,7 +36,11 @@ abstract class PLL_Links_Abstract_Domain extends PLL_Links_Permalinks {
36
  * @return string language slug
37
  */
38
  public function get_language_from_url( $url = '' ) {
39
- $host = empty( $url ) ? $_SERVER['HTTP_HOST'] : parse_url( $url, PHP_URL_HOST );
 
 
 
 
40
  return ( $lang = array_search( $host, $this->get_hosts() ) ) ? $lang : '';
41
  }
42
 
36
  * @return string language slug
37
  */
38
  public function get_language_from_url( $url = '' ) {
39
+ if ( empty( $url ) ) {
40
+ $url = pll_get_requested_url();
41
+ }
42
+
43
+ $host = wp_parse_url( $url, PHP_URL_HOST );
44
  return ( $lang = array_search( $host, $this->get_hosts() ) ) ? $lang : '';
45
  }
46
 
include/links-default.php CHANGED
@@ -75,7 +75,7 @@ class PLL_Links_Default extends PLL_Links_Model {
75
  */
76
  public function get_language_from_url( $url = '' ) {
77
  if ( empty( $url ) ) {
78
- $url = $_SERVER['REQUEST_URI'];
79
  }
80
 
81
  $pattern = '#lang=(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')#';
75
  */
76
  public function get_language_from_url( $url = '' ) {
77
  if ( empty( $url ) ) {
78
+ $url = pll_get_requested_url();
79
  }
80
 
81
  $pattern = '#lang=(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')#';
include/links-directory.php CHANGED
@@ -59,7 +59,7 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
59
  if ( ! empty( $lang ) ) {
60
  $base = $this->options['rewrite'] ? '' : 'language/';
61
  $slug = $this->options['default_lang'] == $lang->slug && $this->options['hide_default'] ? '' : $base . $lang->slug . '/';
62
- $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
63
 
64
  if ( false === strpos( $url, $new = $root . $slug ) ) {
65
  $pattern = str_replace( '/', '\/', $root );
@@ -87,7 +87,7 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
87
  }
88
 
89
  if ( ! empty( $languages ) ) {
90
- $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
91
 
92
  $pattern = str_replace( '/', '\/', $root );
93
  $pattern = '#' . $pattern . ( $this->options['rewrite'] ? '' : 'language\/' ) . '(' . implode( '|', $languages ) . ')(\/|$)#';
@@ -108,14 +108,13 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
108
  */
109
  public function get_language_from_url( $url = '' ) {
110
  if ( empty( $url ) ) {
111
- $path = $_SERVER['REQUEST_URI'];
112
- } else {
113
- $path = parse_url( $url, PHP_URL_PATH );
114
  }
115
 
 
116
  $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
117
 
118
- $pattern = parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH );
119
  $pattern = str_replace( '/', '\/', $pattern );
120
  $pattern = '#' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(\/|$)#';
121
  return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language
59
  if ( ! empty( $lang ) ) {
60
  $base = $this->options['rewrite'] ? '' : 'language/';
61
  $slug = $this->options['default_lang'] == $lang->slug && $this->options['hide_default'] ? '' : $base . $lang->slug . '/';
62
+ $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '/^https?:\/\//', '://', $this->home . '/' . $this->root );
63
 
64
  if ( false === strpos( $url, $new = $root . $slug ) ) {
65
  $pattern = str_replace( '/', '\/', $root );
87
  }
88
 
89
  if ( ! empty( $languages ) ) {
90
+ $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '/^https?:\/\//', '://', $this->home . '/' . $this->root );
91
 
92
  $pattern = str_replace( '/', '\/', $root );
93
  $pattern = '#' . $pattern . ( $this->options['rewrite'] ? '' : 'language\/' ) . '(' . implode( '|', $languages ) . ')(\/|$)#';
108
  */
109
  public function get_language_from_url( $url = '' ) {
110
  if ( empty( $url ) ) {
111
+ $url = pll_get_requested_url();
 
 
112
  }
113
 
114
+ $path = wp_parse_url( $url, PHP_URL_PATH );
115
  $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
116
 
117
+ $pattern = wp_parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH );
118
  $pattern = str_replace( '/', '\/', $pattern );
119
  $pattern = '#' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(\/|$)#';
120
  return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language
include/links-domain.php CHANGED
@@ -38,7 +38,7 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
38
  */
39
  public function add_language_to_link( $url, $lang ) {
40
  if ( ! empty( $lang ) && ! empty( $this->hosts[ $lang->slug ] ) ) {
41
- $url = preg_replace( '#:\/\/(' . parse_url( $this->home, PHP_URL_HOST ) . ')($|\/.*)#', '://' . $this->hosts[ $lang->slug ] . '$2', $url );
42
  }
43
  return $url;
44
  }
@@ -54,7 +54,7 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
54
  */
55
  public function remove_language_from_link( $url ) {
56
  if ( ! empty( $this->hosts ) ) {
57
- $url = preg_replace( '#:\/\/(' . implode( '|', $this->hosts ) . ')($|\/.*)#', '://' . parse_url( $this->home, PHP_URL_HOST ) . '$2', $url );
58
  }
59
  return $url;
60
  }
@@ -82,8 +82,16 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
82
  public function get_hosts() {
83
  $hosts = array();
84
  foreach ( $this->options['domains'] as $lang => $domain ) {
85
- $hosts[ $lang ] = parse_url( $domain, PHP_URL_HOST );
 
 
 
 
 
 
 
86
  }
 
87
  return $hosts;
88
  }
89
  }
38
  */
39
  public function add_language_to_link( $url, $lang ) {
40
  if ( ! empty( $lang ) && ! empty( $this->hosts[ $lang->slug ] ) ) {
41
+ $url = preg_replace( '#:\/\/(' . wp_parse_url( $this->home, PHP_URL_HOST ) . ')($|\/.*)#', '://' . $this->hosts[ $lang->slug ] . '$2', $url );
42
  }
43
  return $url;
44
  }
54
  */
55
  public function remove_language_from_link( $url ) {
56
  if ( ! empty( $this->hosts ) ) {
57
+ $url = preg_replace( '#:\/\/(' . implode( '|', $this->hosts ) . ')($|\/.*)#', '://' . wp_parse_url( $this->home, PHP_URL_HOST ) . '$2', $url );
58
  }
59
  return $url;
60
  }
82
  public function get_hosts() {
83
  $hosts = array();
84
  foreach ( $this->options['domains'] as $lang => $domain ) {
85
+ $host = wp_parse_url( $domain, PHP_URL_HOST );
86
+ // idn_to_ascii is much faster than the WordPress method.
87
+ if ( function_exists( 'idn_to_ascii' ) ) {
88
+ // The use of the constant is mandatory in PHP 7.2 and PHP 7.3 to avoid a deprecated notice.
89
+ $hosts[ $lang ] = defined( 'INTL_IDNA_VARIANT_UTS46' ) ? idn_to_ascii( $host, 0, INTL_IDNA_VARIANT_UTS46 ) : idn_to_ascii( $host );
90
+ } else {
91
+ $hosts[ $lang ] = Requests_IDNAEncoder::encode( $host );
92
+ }
93
  }
94
+
95
  return $hosts;
96
  }
97
  }
include/links-model.php CHANGED
@@ -52,7 +52,7 @@ abstract class PLL_Links_Model {
52
  * @return array list of hosts
53
  */
54
  public function get_hosts() {
55
- return array( parse_url( $this->home, PHP_URL_HOST ) );
56
  }
57
 
58
  /**
52
  * @return array list of hosts
53
  */
54
  public function get_hosts() {
55
+ return array( wp_parse_url( $this->home, PHP_URL_HOST ) );
56
  }
57
 
58
  /**
include/links-subdomain.php CHANGED
@@ -72,7 +72,7 @@ class PLL_Links_Subdomain extends PLL_Links_Abstract_Domain {
72
  public function get_hosts() {
73
  $hosts = array();
74
  foreach ( $this->model->get_languages_list() as $lang ) {
75
- $hosts[ $lang->slug ] = parse_url( $this->home_url( $lang ), PHP_URL_HOST );
76
  }
77
  return $hosts;
78
  }
72
  public function get_hosts() {
73
  $hosts = array();
74
  foreach ( $this->model->get_languages_list() as $lang ) {
75
+ $hosts[ $lang->slug ] = wp_parse_url( $this->home_url( $lang ), PHP_URL_HOST );
76
  }
77
  return $hosts;
78
  }
include/links.php CHANGED
@@ -33,33 +33,4 @@ class PLL_Links {
33
  $language = is_object( $language ) ? $language : $this->model->get_language( $language );
34
  return $is_search ? $language->search_url : $language->home_url;
35
  }
36
-
37
- /**
38
- * Checks if the current user can read the post
39
- *
40
- * @since 1.5
41
- *
42
- * @param int $post_id
43
- * @return bool
44
- */
45
- public function current_user_can_read( $post_id ) {
46
- $post = get_post( $post_id );
47
-
48
- if ( 'inherit' === $post->post_status && $post->post_parent ) {
49
- $post = get_post( $post->post_parent );
50
- }
51
-
52
- if ( 'inherit' === $post->post_status || in_array( $post->post_status, get_post_stati( array( 'public' => true ) ) ) ) {
53
- return true;
54
- }
55
-
56
- // Follow WP practices, which shows links to private posts ( when readable ), but not for draft posts ( ex: get_adjacent_post_link() )
57
- if ( in_array( $post->post_status, get_post_stati( array( 'private' => true ) ) ) ) {
58
- $post_type_object = get_post_type_object( $post->post_type );
59
- $user = wp_get_current_user();
60
- return is_user_logged_in() && ( current_user_can( $post_type_object->cap->read_private_posts ) || $user->ID == $post->post_author ); // Comparison must not be strict!
61
- }
62
-
63
- return false;
64
- }
65
  }
33
  $language = is_object( $language ) ? $language : $this->model->get_language( $language );
34
  return $is_search ? $language->search_url : $language->home_url;
35
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
include/model.php CHANGED
@@ -385,7 +385,7 @@ class PLL_Model {
385
 
386
  // create a new category
387
  // FIXME this is translated in admin language when we would like it in $lang
388
- $cat_name = __( 'Uncategorized' );
389
  $cat_slug = sanitize_title( $cat_name . '-' . $lang->slug );
390
  $cat = wp_insert_term( $cat_name, 'category', array( 'slug' => $cat_slug ) );
391
 
@@ -443,12 +443,12 @@ class PLL_Model {
443
  }
444
 
445
  /**
446
- * Gets the number of posts per language in a date, author or post type archive
447
  *
448
  * @since 1.2
449
  *
450
- * @param object $lang
451
- * @param array $q WP_Query arguments ( accepted: post_type, m, year, monthnum, day, author, author_name, post_format )
452
  * @return int
453
  */
454
  public function count_posts( $lang, $q = array() ) {
@@ -467,11 +467,11 @@ class PLL_Model {
467
  }
468
 
469
  if ( empty( $q['post_type'] ) ) {
470
- $q['post_type'] = array( 'post' ); // we *need* a post type
471
  }
472
 
473
- $cache_key = md5( serialize( $q ) );
474
- $counts = wp_cache_get( $cache_key, 'pll_count_posts' );
475
 
476
  if ( false === $counts ) {
477
  $select = "SELECT pll_tr.term_taxonomy_id, COUNT( * ) AS num_posts FROM {$wpdb->posts}";
@@ -515,7 +515,7 @@ class PLL_Model {
515
  $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_author = %d", $q['author'] );
516
  }
517
 
518
- // filtered taxonomies ( post_format )
519
  foreach ( $this->get_filtered_taxonomies_query_vars() as $tax_qv ) {
520
 
521
  if ( ! empty( $q[ $tax_qv ] ) ) {
@@ -532,7 +532,7 @@ class PLL_Model {
532
  $counts[ $row['term_taxonomy_id'] ] = $row['num_posts'];
533
  }
534
 
535
- wp_cache_set( $cache_key, $counts, 'pll_count_posts' );
536
  }
537
 
538
  return empty( $counts[ $lang->term_taxonomy_id ] ) ? 0 : $counts[ $lang->term_taxonomy_id ];
@@ -610,24 +610,32 @@ class PLL_Model {
610
 
611
  if ( ! empty( $o ) && is_object( $this->$o ) && method_exists( $this->$o, $f ) ) {
612
  if ( WP_DEBUG ) {
613
- $debug = debug_backtrace();
614
  $i = 1 + empty( $debug[1]['line'] ); // the file and line are in $debug[2] if the function was called using call_user_func
615
 
616
- trigger_error(
617
  sprintf(
618
  '%1$s was called incorrectly in %4$s on line %5$s: the call to $polylang->model->%1$s() has been deprecated in Polylang 1.8, use PLL()->model->%2$s->%3$s() instead.' . "\nError handler",
619
- $func,
620
- $o,
621
- $f,
622
- $debug[ $i ]['file'],
623
- $debug[ $i ]['line']
624
  )
625
  );
626
  }
627
  return call_user_func_array( array( $this->$o, $f ), $args );
628
  }
629
 
630
- $debug = debug_backtrace();
631
- trigger_error( sprintf( 'Call to undefined function PLL()->model->%1$s() in %2$s on line %3$s' . "\nError handler", $func, $debug[0]['file'], $debug[0]['line'] ), E_USER_ERROR );
 
 
 
 
 
 
 
 
632
  }
633
  }
385
 
386
  // create a new category
387
  // FIXME this is translated in admin language when we would like it in $lang
388
+ $cat_name = __( 'Uncategorized', 'polylang' );
389
  $cat_slug = sanitize_title( $cat_name . '-' . $lang->slug );
390
  $cat = wp_insert_term( $cat_name, 'category', array( 'slug' => $cat_slug ) );
391
 
443
  }
444
 
445
  /**
446
+ * Gets the number of posts per language in a date, author or post type archive.
447
  *
448
  * @since 1.2
449
  *
450
+ * @param object $lang PLL_Language instance.
451
+ * @param array $q WP_Query arguments ( accepted: post_type, m, year, monthnum, day, author, author_name, post_format ).
452
  * @return int
453
  */
454
  public function count_posts( $lang, $q = array() ) {
467
  }
468
 
469
  if ( empty( $q['post_type'] ) ) {
470
+ $q['post_type'] = array( 'post' ); // We *need* a post type.
471
  }
472
 
473
+ $cache_key = 'pll_count_posts_' . md5( maybe_serialize( $q ) );
474
+ $counts = wp_cache_get( $cache_key, 'counts' );
475
 
476
  if ( false === $counts ) {
477
  $select = "SELECT pll_tr.term_taxonomy_id, COUNT( * ) AS num_posts FROM {$wpdb->posts}";
515
  $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_author = %d", $q['author'] );
516
  }
517
 
518
+ // Filtered taxonomies ( post_format ).
519
  foreach ( $this->get_filtered_taxonomies_query_vars() as $tax_qv ) {
520
 
521
  if ( ! empty( $q[ $tax_qv ] ) ) {
532
  $counts[ $row['term_taxonomy_id'] ] = $row['num_posts'];
533
  }
534
 
535
+ wp_cache_set( $cache_key, $counts, 'counts' );
536
  }
537
 
538
  return empty( $counts[ $lang->term_taxonomy_id ] ) ? 0 : $counts[ $lang->term_taxonomy_id ];
610
 
611
  if ( ! empty( $o ) && is_object( $this->$o ) && method_exists( $this->$o, $f ) ) {
612
  if ( WP_DEBUG ) {
613
+ $debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
614
  $i = 1 + empty( $debug[1]['line'] ); // the file and line are in $debug[2] if the function was called using call_user_func
615
 
616
+ trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
617
  sprintf(
618
  '%1$s was called incorrectly in %4$s on line %5$s: the call to $polylang->model->%1$s() has been deprecated in Polylang 1.8, use PLL()->model->%2$s->%3$s() instead.' . "\nError handler",
619
+ esc_html( $func ),
620
+ esc_html( $o ),
621
+ esc_html( $f ),
622
+ esc_html( $debug[ $i ]['file'] ),
623
+ absint( $debug[ $i ]['line'] )
624
  )
625
  );
626
  }
627
  return call_user_func_array( array( $this->$o, $f ), $args );
628
  }
629
 
630
+ $debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
631
+ trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
632
+ sprintf(
633
+ 'Call to undefined function PLL()->model->%1$s() in %2$s on line %3$s' . "\nError handler",
634
+ esc_html( $func ),
635
+ esc_html( $debug[0]['file'] ),
636
+ absint( $debug[0]['line'] )
637
+ ),
638
+ E_USER_ERROR
639
+ );
640
  }
641
  }
include/nav-menu.php CHANGED
@@ -20,8 +20,31 @@ class PLL_Nav_Menu {
20
  $this->model = &$polylang->model;
21
  $this->options = &$polylang->options;
22
 
 
 
 
 
23
  // Integration with WP customizer
24
  add_action( 'customize_register', array( $this, 'create_nav_menu_locations' ), 5 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
  /**
@@ -76,4 +99,48 @@ class PLL_Nav_Menu {
76
  }
77
  return array_combine( array( 'location', 'lang' ), $infos );
78
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
20
  $this->model = &$polylang->model;
21
  $this->options = &$polylang->options;
22
 
23
+ $this->theme = get_option( 'stylesheet' );
24
+
25
+ add_filter( 'wp_setup_nav_menu_item', array( $this, 'wp_setup_nav_menu_item' ) );
26
+
27
  // Integration with WP customizer
28
  add_action( 'customize_register', array( $this, 'create_nav_menu_locations' ), 5 );
29
+
30
+ // Filter _wp_auto_add_pages_to_menu by language
31
+ add_action( 'transition_post_status', array( $this, 'auto_add_pages_to_menu' ), 5, 3 ); // before _wp_auto_add_pages_to_menu
32
+ }
33
+
34
+ /**
35
+ * Assigns the title and label to the language switcher menu items
36
+ *
37
+ * @since 2.6
38
+ *
39
+ * @param object $item Menu item.
40
+ * @return object
41
+ */
42
+ public function wp_setup_nav_menu_item( $item ) {
43
+ if ( '#pll_switcher' === $item->url ) {
44
+ $item->post_title = __( 'Languages', 'polylang' );
45
+ $item->type_label = __( 'Language switcher', 'polylang' );
46
+ }
47
+ return $item;
48
  }
49
 
50
  /**
99
  }
100
  return array_combine( array( 'location', 'lang' ), $infos );
101
  }
102
+
103
+ /**
104
+ * Filters the option nav_menu_options for auto added pages to menu
105
+ *
106
+ * @since 0.9.4
107
+ *
108
+ * @param array $options
109
+ * @return array Modified options
110
+ */
111
+ public function nav_menu_options( $options ) {
112
+ $options['auto_add'] = array_intersect( $options['auto_add'], $this->auto_add_menus );
113
+ return $options;
114
+ }
115
+
116
+ /**
117
+ * Filters _wp_auto_add_pages_to_menu by language
118
+ *
119
+ * @since 0.9.4
120
+ *
121
+ * @param string $new_status Transition to this post status.
122
+ * @param string $old_status Previous post status.
123
+ * @param object $post Post data.
124
+ */
125
+ public function auto_add_pages_to_menu( $new_status, $old_status, $post ) {
126
+ if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type || ! empty( $post->post_parent ) ) {
127
+ return;
128
+ }
129
+
130
+ if ( ! empty( $this->options['nav_menus'][ $this->theme ] ) ) {
131
+ $this->auto_add_menus = array();
132
+
133
+ $lang = $this->model->post->get_language( $post->ID );
134
+ $lang = empty( $lang ) ? $this->options['default_lang'] : $lang->slug; // If the page has no language yet, the default language will be assigned
135
+
136
+ // Get all the menus in the page language
137
+ foreach ( $this->options['nav_menus'][ $this->theme ] as $loc ) {
138
+ if ( ! empty( $loc[ $lang ] ) ) {
139
+ $this->auto_add_menus[] = $loc[ $lang ];
140
+ }
141
+ }
142
+
143
+ add_filter( 'option_nav_menu_options', array( $this, 'nav_menu_options' ) );
144
+ }
145
+ }
146
  }
include/olt-manager.php CHANGED
@@ -10,7 +10,7 @@
10
  * @since 1.2
11
  */
12
  class PLL_OLT_Manager {
13
- static protected $instance; // For singleton
14
  protected $default_locale;
15
  protected $list_textdomains = array(); // All text domains
16
  public $labels = array(); // Post types and taxonomies labels to translate
10
  * @since 1.2
11
  */
12
  class PLL_OLT_Manager {
13
+ protected static $instance; // For singleton
14
  protected $default_locale;
15
  protected $list_textdomains = array(); // All text domains
16
  public $labels = array(); // Post types and taxonomies labels to translate
include/pointer.php CHANGED
@@ -33,7 +33,7 @@ class PLL_Pointer {
33
  * @param array $args
34
  */
35
  public function __construct( $args ) {
36
- trigger_error( 'The class PLL_Pointer has been deprecated since Polylang 2.3.9 and will be removed in a future version.', E_USER_ERROR );
37
 
38
  $this->args = $args;
39
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
@@ -108,6 +108,6 @@ class PLL_Pointer {
108
  empty( $this->args['width'] ) ? '' : sprintf( 'pointerWidth: %d,', $this->args['width'] ),
109
  empty( $b ) ? '' : $b
110
  );
111
- echo '<script type="text/javascript">' . $js . '</script>';
112
  }
113
  }
33
  * @param array $args
34
  */
35
  public function __construct( $args ) {
36
+ trigger_error( 'The class PLL_Pointer has been deprecated since Polylang 2.3.9 and will be removed in a future version.', E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
37
 
38
  $this->args = $args;
39
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
108
  empty( $this->args['width'] ) ? '' : sprintf( 'pointerWidth: %d,', $this->args['width'] ),
109
  empty( $b ) ? '' : $b
110
  );
111
+ echo '<script type="text/javascript">' . $js . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput
112
  }
113
  }
include/query.php CHANGED
@@ -7,7 +7,7 @@
7
  */
8
  class PLL_Query {
9
 
10
- static protected $excludes = array(
11
  'p',
12
  'post_parent',
13
  'attachment',
7
  */
8
  class PLL_Query {
9
 
10
+ protected static $excludes = array(
11
  'p',
12
  'post_parent',
13
  'attachment',
include/rest-request.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * REST API controller
5
+ * accessible as $polylang global object
6
+ *
7
+ * Properties:
8
+ * options => inherited, reference to Polylang options array
9
+ * model => inherited, reference to PLL_Model object
10
+ * links_model => inherited, reference to PLL_Links_Model object
11
+ * links => reference to PLL_Admin_Links object
12
+ * static_pages => reference to PLL_Static_Pages object
13
+ * filters_links => reference to PLL_Filters_Links object
14
+ * posts => reference to PLL_CRUD_Posts object
15
+ * terms => reference to PLL_CRUD_Terms object
16
+ * sync => reference to PLL_Sync object
17
+ *
18
+ * @since 2.6
19
+ */
20
+ class PLL_REST_Request extends PLL_Base {
21
+ public $links, $posts, $terms, $filters_links, $sync;
22
+
23
+ /**
24
+ * Setup filters
25
+ *
26
+ * @since 2.6
27
+ */
28
+ public function init() {
29
+ parent::init();
30
+
31
+ if ( $this->model->get_languages_list() ) {
32
+ $this->filters_links = new PLL_Filters_Links( $this );
33
+
34
+ // Static front page and page for posts
35
+ if ( 'page' === get_option( 'show_on_front' ) ) {
36
+ $this->static_pages = new PLL_Static_Pages( $this );
37
+ }
38
+
39
+ $this->links = new PLL_Admin_Links( $this );
40
+ $this->posts = new PLL_CRUD_Posts( $this );
41
+ $this->terms = new PLL_CRUD_Terms( $this );
42
+ $this->sync = new PLL_Sync( $this );
43
+
44
+ $this->nav_menu = new PLL_Nav_Menu( $this ); // For auto added pages to menu
45
+
46
+ // Share term slugs
47
+ if ( get_option( 'permalink_structure' ) && $this->options['force_lang'] && class_exists( 'PLL_Share_Term_Slug' ) ) {
48
+ $this->share_term_slug = version_compare( $GLOBALS['wp_version'], '4.8', '<' ) ?
49
+ new PLL_Frontend_Share_Term_Slug( $this ) :
50
+ new PLL_Share_Term_Slug( $this );
51
+ }
52
+
53
+ // Translate slugs, only for pretty permalinks
54
+ if ( get_option( 'permalink_structure' ) && class_exists( 'PLL_Translate_Slugs' ) ) {
55
+ $curlang = null;
56
+ $slugs_model = new PLL_Translate_Slugs_Model( $this );
57
+ $this->translate_slugs = new PLL_Translate_Slugs( $slugs_model, $curlang );
58
+ }
59
+
60
+ if ( class_exists( 'PLL_Sync_Content' ) ) {
61
+ $this->sync_content = new PLL_Sync_Content( $this );
62
+ }
63
+
64
+ if ( class_exists( 'PLL_Sync_Post_REST' ) ) {
65
+ $this->sync_post = new PLL_Sync_Post_REST( $this );
66
+ }
67
+
68
+ if ( class_exists( 'PLL_Duplicate_REST' ) ) {
69
+ $this->duplicate_rest = new PLL_Duplicate_REST();
70
+ }
71
+ }
72
+ }
73
+ }
include/static-pages.php CHANGED
@@ -5,7 +5,7 @@
5
  *
6
  * @since 1.8
7
  */
8
- abstract class PLL_Static_Pages {
9
  public $model, $options;
10
  public $page_on_front, $page_for_posts;
11
 
@@ -17,7 +17,7 @@ abstract class PLL_Static_Pages {
17
  * @param object $polylang
18
  */
19
  public function __construct( &$polylang ) {
20
- $this->model = &$polylang->model;
21
  $this->options = &$polylang->options;
22
  $this->curlang = &$polylang->curlang;
23
 
@@ -33,6 +33,9 @@ abstract class PLL_Static_Pages {
33
 
34
  // Refresh rewrite rules when the page on front is modified
35
  add_action( 'update_option_page_on_front', 'flush_rewrite_rules' );
 
 
 
36
  }
37
 
38
  /**
@@ -44,9 +47,7 @@ abstract class PLL_Static_Pages {
44
  if ( 'page' == get_option( 'show_on_front' ) ) {
45
  $this->page_on_front = get_option( 'page_on_front' );
46
  $this->page_for_posts = get_option( 'page_for_posts' );
47
- }
48
-
49
- else {
50
  $this->page_on_front = 0;
51
  $this->page_for_posts = 0;
52
  }
@@ -62,7 +63,9 @@ abstract class PLL_Static_Pages {
62
  * @return string modified link
63
  */
64
  public function page_link( $link, $id ) {
65
- if ( ( $lang = $this->model->post->get_language( $id ) ) && $id == $lang->page_on_front ) {
 
 
66
  return $lang->home_url;
67
  }
68
  return $link;
@@ -99,4 +102,22 @@ abstract class PLL_Static_Pages {
99
  // Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache
100
  return isset( $this->curlang->page_for_posts ) && ! doing_action( 'switch_blog' ) ? $this->curlang->page_for_posts : $v;
101
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
5
  *
6
  * @since 1.8
7
  */
8
+ class PLL_Static_Pages {
9
  public $model, $options;
10
  public $page_on_front, $page_for_posts;
11
 
17
  * @param object $polylang
18
  */
19
  public function __construct( &$polylang ) {
20
+ $this->model = &$polylang->model;
21
  $this->options = &$polylang->options;
22
  $this->curlang = &$polylang->curlang;
23
 
33
 
34
  // Refresh rewrite rules when the page on front is modified
35
  add_action( 'update_option_page_on_front', 'flush_rewrite_rules' );
36
+
37
+ // OEmbed
38
+ add_filter( 'oembed_request_post_id', array( $this, 'oembed_request_post_id' ), 10, 2 );
39
  }
40
 
41
  /**
47
  if ( 'page' == get_option( 'show_on_front' ) ) {
48
  $this->page_on_front = get_option( 'page_on_front' );
49
  $this->page_for_posts = get_option( 'page_for_posts' );
50
+ } else {
 
 
51
  $this->page_on_front = 0;
52
  $this->page_for_posts = 0;
53
  }
63
  * @return string modified link
64
  */
65
  public function page_link( $link, $id ) {
66
+ $lang = $this->model->post->get_language( $id );
67
+
68
+ if ( $lang && $id == $lang->page_on_front ) {
69
  return $lang->home_url;
70
  }
71
  return $link;
102
  // Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache
103
  return isset( $this->curlang->page_for_posts ) && ! doing_action( 'switch_blog' ) ? $this->curlang->page_for_posts : $v;
104
  }
105
+
106
+ /**
107
+ * Fixes the oembed for the translated static front page
108
+ * when the language page is redirected to the front page
109
+ *
110
+ * @since 2.6
111
+ *
112
+ * @param int $post_id The post ID.
113
+ * @param string $url The requested URL.
114
+ */
115
+ public function oembed_request_post_id( $post_id, $url ) {
116
+ foreach ( $this->model->get_languages_list() as $lang ) {
117
+ if ( trailingslashit( $url ) === trailingslashit( $lang->home_url ) ) {
118
+ $post_id = $lang->page_on_front;
119
+ }
120
+ }
121
+ return $post_id;
122
+ }
123
  }
include/switcher.php CHANGED
@@ -19,7 +19,7 @@ class PLL_Switcher {
19
  */
20
  public static function get_switcher_options( $type = 'widget', $key = 'string' ) {
21
  $options = array(
22
- 'dropdown' => array( 'string' => __( 'Displays as dropdown', 'polylang' ), 'default' => 0 ),
23
  'show_names' => array( 'string' => __( 'Displays language names', 'polylang' ), 'default' => 1 ),
24
  'show_flags' => array( 'string' => __( 'Displays flags', 'polylang' ), 'default' => 0 ),
25
  'force_home' => array( 'string' => __( 'Forces link to front page', 'polylang' ), 'default' => 0 ),
@@ -67,7 +67,7 @@ class PLL_Switcher {
67
  }
68
  }
69
 
70
- if ( null !== $args['post_id'] && ( $tr_id = $links->model->post->get( $args['post_id'], $language ) ) && $links->current_user_can_read( $tr_id ) ) {
71
  $url = get_permalink( $tr_id );
72
  } elseif ( null === $args['post_id'] ) {
73
  $url = $links->get_translation_url( $language );
@@ -204,13 +204,13 @@ class PLL_Switcher {
204
  //]]>
205
  </script>',
206
  'urls_' . preg_replace( '#[^a-zA-Z0-9]#', '', $args['dropdown'] ),
207
- json_encode( $urls ),
208
  esc_js( $args['name'] )
209
  );
210
  }
211
 
212
  if ( $args['echo'] ) {
213
- echo $out;
214
  }
215
  return $out;
216
  }
19
  */
20
  public static function get_switcher_options( $type = 'widget', $key = 'string' ) {
21
  $options = array(
22
+ 'dropdown' => array( 'string' => __( 'Displays as a dropdown', 'polylang' ), 'default' => 0 ),
23
  'show_names' => array( 'string' => __( 'Displays language names', 'polylang' ), 'default' => 1 ),
24
  'show_flags' => array( 'string' => __( 'Displays flags', 'polylang' ), 'default' => 0 ),
25
  'force_home' => array( 'string' => __( 'Forces link to front page', 'polylang' ), 'default' => 0 ),
67
  }
68
  }
69
 
70
+ if ( null !== $args['post_id'] && ( $tr_id = $links->model->post->get( $args['post_id'], $language ) ) && $links->model->post->current_user_can_read( $tr_id ) ) {
71
  $url = get_permalink( $tr_id );
72
  } elseif ( null === $args['post_id'] ) {
73
  $url = $links->get_translation_url( $language );
204
  //]]>
205
  </script>',
206
  'urls_' . preg_replace( '#[^a-zA-Z0-9]#', '', $args['dropdown'] ),
207
+ wp_json_encode( $urls ),
208
  esc_js( $args['name'] )
209
  );
210
  }
211
 
212
  if ( $args['echo'] ) {
213
+ echo $out; // phpcs:ignore WordPress.Security.EscapeOutput
214
  }
215
  return $out;
216
  }
include/translated-object.php CHANGED
@@ -7,7 +7,7 @@
7
  */
8
  abstract class PLL_Translated_Object {
9
  public $model;
10
- protected $object_type, $tax_language, $tax_translations, $tax_tt;
11
 
12
  /**
13
  * Constructor
@@ -115,14 +115,14 @@ abstract class PLL_Translated_Object {
115
 
116
  // create a new term if necessary
117
  if ( empty( $term ) ) {
118
- wp_insert_term( $group = uniqid( 'pll_' ), $this->tax_translations, array( 'description' => serialize( $translations ) ) );
119
  }
120
  else {
121
  // take care not to overwrite extra data stored in description field, if any
122
- $d = unserialize( $term->description );
123
  $d = is_array( $d ) ? array_diff_key( $d, $old_translations ) : array(); // remove old translations
124
  $d = array_merge( $d, $translations ); // add new one
125
- wp_update_term( $group = (int) $term->term_id, $this->tax_translations, array( 'description' => serialize( $d ) ) );
126
  }
127
 
128
  // link all translations to the new term
@@ -153,7 +153,7 @@ abstract class PLL_Translated_Object {
153
  $term = $this->get_object_term( $id, $this->tax_translations );
154
 
155
  if ( ! empty( $term ) ) {
156
- $d = unserialize( $term->description );
157
  $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
158
  unset( $d[ $slug ] );
159
 
@@ -161,7 +161,7 @@ abstract class PLL_Translated_Object {
161
  wp_delete_term( (int) $term->term_id, $this->tax_translations );
162
  }
163
  else {
164
- wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => serialize( $d ) ) );
165
  }
166
  }
167
  }
@@ -176,7 +176,7 @@ abstract class PLL_Translated_Object {
176
  */
177
  public function get_translations( $id ) {
178
  $term = $this->get_object_term( $id, $this->tax_translations );
179
- $translations = empty( $term ) ? array() : unserialize( $term->description );
180
 
181
  // make sure we return only translations ( thus we allow plugins to store other information in the array )
182
  if ( is_array( $translations ) ) {
@@ -272,4 +272,42 @@ abstract class PLL_Translated_Object {
272
  $tt_id = $this->tax_tt;
273
  return $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $lang->$tt_id ) );
274
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  }
7
  */
8
  abstract class PLL_Translated_Object {
9
  public $model;
10
+ protected $object_type, $type, $tax_language, $tax_translations, $tax_tt;
11
 
12
  /**
13
  * Constructor
115
 
116
  // create a new term if necessary
117
  if ( empty( $term ) ) {
118
+ wp_insert_term( $group = uniqid( 'pll_' ), $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
119
  }
120
  else {
121
  // take care not to overwrite extra data stored in description field, if any
122
+ $d = maybe_unserialize( $term->description );
123
  $d = is_array( $d ) ? array_diff_key( $d, $old_translations ) : array(); // remove old translations
124
  $d = array_merge( $d, $translations ); // add new one
125
+ wp_update_term( $group = (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
126
  }
127
 
128
  // link all translations to the new term
153
  $term = $this->get_object_term( $id, $this->tax_translations );
154
 
155
  if ( ! empty( $term ) ) {
156
+ $d = maybe_unserialize( $term->description );
157
  $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
158
  unset( $d[ $slug ] );
159
 
161
  wp_delete_term( (int) $term->term_id, $this->tax_translations );
162
  }
163
  else {
164
+ wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
165
  }
166
  }
167
  }
176
  */
177
  public function get_translations( $id ) {
178
  $term = $this->get_object_term( $id, $this->tax_translations );
179
+ $translations = empty( $term ) ? array() : maybe_unserialize( $term->description );
180
 
181
  // make sure we return only translations ( thus we allow plugins to store other information in the array )
182
  if ( is_array( $translations ) ) {
272
  $tt_id = $this->tax_tt;
273
  return $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $lang->$tt_id ) );
274
  }
275
+
276
+ /**
277
+ * Check if a user can synchronize translations
278
+ *
279
+ * @since 2.6
280
+ *
281
+ * @param int $id Object id
282
+ * @return bool
283
+ */
284
+ public function current_user_can_synchronize( $id ) {
285
+ /**
286
+ * Filters whether a synchronization capability check should take place
287
+ *
288
+ * @since 2.6
289
+ *
290
+ * @param $check null to enable the capability check,
291
+ * true to always allow the synchronization,
292
+ * false to always disallow the synchronization.
293
+ * Defaults to true.
294
+ * @param $id The synchronization source object id
295
+ */
296
+ $check = apply_filters( "pll_pre_current_user_can_synchronize_{$this->type}", true, $id );
297
+ if ( null !== $check ) {
298
+ return $check;
299
+ }
300
+
301
+ if ( ! current_user_can( "edit_{$this->type}", $id ) ) {
302
+ return false;
303
+ }
304
+
305
+ foreach ( $this->get_translations( $id ) as $tr_id ) {
306
+ if ( $tr_id !== $id && ! current_user_can( "edit_{$this->type}", $tr_id ) ) {
307
+ return false;
308
+ }
309
+ }
310
+
311
+ return true;
312
+ }
313
  }
include/translated-post.php CHANGED
@@ -16,7 +16,8 @@ class PLL_Translated_Post extends PLL_Translated_Object {
16
  */
17
  public function __construct( &$model ) {
18
  // init properties
19
- $this->object_type = null;
 
20
  $this->tax_language = 'language';
21
  $this->tax_translations = 'post_translations';
22
  $this->tax_tt = 'term_taxonomy_id';
@@ -146,4 +147,93 @@ class PLL_Translated_Post extends PLL_Translated_Object {
146
  $query->query_vars['update_post_term_cache'] = true;
147
  }
148
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
16
  */
17
  public function __construct( &$model ) {
18
  // init properties
19
+ $this->object_type = null; // For taxonomies
20
+ $this->type = 'post'; // For capabilities
21
  $this->tax_language = 'language';
22
  $this->tax_translations = 'post_translations';
23
  $this->tax_tt = 'term_taxonomy_id';
147
  $query->query_vars['update_post_term_cache'] = true;
148
  }
149
  }
150
+
151
+ /**
152
+ * Checks if the current user can read the post
153
+ *
154
+ * @since 1.5
155
+ *
156
+ * @param int $post_id Post ID
157
+ * @param string $context Optional, 'edit' or 'view', defaults to 'view'.
158
+ * @return bool
159
+ */
160
+ public function current_user_can_read( $post_id, $context = 'view' ) {
161
+ $post = get_post( $post_id );
162
+
163
+ if ( 'inherit' === $post->post_status && $post->post_parent ) {
164
+ $post = get_post( $post->post_parent );
165
+ }
166
+
167
+ if ( 'inherit' === $post->post_status || in_array( $post->post_status, get_post_stati( array( 'public' => true ) ) ) ) {
168
+ return true;
169
+ }
170
+
171
+ // Follow WP practices, which shows links to private posts ( when readable ), but not for draft posts ( ex: get_adjacent_post_link() )
172
+ if ( in_array( $post->post_status, get_post_stati( array( 'private' => true ) ) ) ) {
173
+ $post_type_object = get_post_type_object( $post->post_type );
174
+ $user = wp_get_current_user();
175
+ return is_user_logged_in() && ( current_user_can( $post_type_object->cap->read_private_posts ) || $user->ID == $post->post_author ); // Comparison must not be strict!
176
+ }
177
+
178
+ if ( 'edit' === $context && 'draft' === $post->post_status ) {
179
+ $user = wp_get_current_user();
180
+ return is_user_logged_in() && ( current_user_can( 'edit_posts' ) || $user->ID == $post->post_author ); // Comparison must not be strict!
181
+ }
182
+
183
+ return false;
184
+ }
185
+
186
+ /**
187
+ * Returns a list of posts in a language ( $lang )
188
+ * not translated in another language ( $untranslated_in )
189
+ *
190
+ * @since 2.6
191
+ *
192
+ * @param string $type Post type
193
+ * @param string $untranslated_in The posts must not be translated in this language
194
+ * @param string $lang Language of the search posts
195
+ * @param string $search Limit results to posts matching this string
196
+ * @return array Array of posts
197
+ */
198
+ public function get_untranslated( $type, $untranslated_in, $lang, $search = '' ) {
199
+ $return = array();
200
+
201
+ // Don't order by title: see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
202
+ $args = array(
203
+ 's' => $search,
204
+ 'suppress_filters' => 0, // To make the post_fields filter work
205
+ 'lang' => 0, // Avoid admin language filter
206
+ 'numberposts' => 20, // Limit to 20 posts
207
+ 'post_status' => 'any',
208
+ 'post_type' => $type,
209
+ 'tax_query' => array(
210
+ array(
211
+ 'taxonomy' => 'language',
212
+ 'field' => 'term_taxonomy_id', // WP 3.5+
213
+ 'terms' => $lang->term_taxonomy_id,
214
+ ),
215
+ ),
216
+ );
217
+
218
+ /**
219
+ * Filter the query args when auto suggesting untranslated posts in the Languages metabox
220
+ * This should help plugins to fix some edge cases
221
+ *
222
+ * @see https://wordpress.org/support/topic/find-translated-post-when-10-is-not-enough
223
+ *
224
+ * @since 1.7
225
+ *
226
+ * @param array $args WP_Query arguments
227
+ */
228
+ $args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
229
+ $posts = get_posts( $args );
230
+
231
+ foreach ( $posts as $post ) {
232
+ if ( ! $this->get_translation( $post->ID, $untranslated_in ) && $this->current_user_can_read( $post->ID, 'edit' ) ) {
233
+ $return[] = $post;
234
+ }
235
+ }
236
+
237
+ return $return;
238
+ }
239
  }
include/translated-term.php CHANGED
@@ -15,7 +15,8 @@ class PLL_Translated_Term extends PLL_Translated_Object {
15
  * @param object $model
16
  */
17
  public function __construct( &$model ) {
18
- $this->object_type = 'term';
 
19
  $this->tax_language = 'term_language';
20
  $this->tax_translations = 'term_translations';
21
  $this->tax_tt = 'tl_term_taxonomy_id';
@@ -128,7 +129,7 @@ class PLL_Translated_Term extends PLL_Translated_Object {
128
  if ( ! doing_action( 'pre_delete_term' ) && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->terms WHERE term_id = %d;", $id ) ) ) {
129
  // Always keep a group for terms to allow relationships remap when importing from a WXR file
130
  $translations[ $slug ] = $id;
131
- wp_insert_term( $group = uniqid( 'pll_' ), 'term_translations', array( 'description' => serialize( $translations ) ) );
132
  wp_set_object_terms( $id, $group, 'term_translations' );
133
  }
134
  }
@@ -137,12 +138,14 @@ class PLL_Translated_Term extends PLL_Translated_Object {
137
  * A join clause to add to sql queries when filtering by language is needed directly in query
138
  *
139
  * @since 1.2
 
140
  *
 
141
  * @return string join clause
142
  */
143
- public function join_clause() {
144
  global $wpdb;
145
- return " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = t.term_id";
146
  }
147
 
148
  /**
15
  * @param object $model
16
  */
17
  public function __construct( &$model ) {
18
+ $this->object_type = 'term'; // For taxonomies
19
+ $this->type = 'term'; // For capabilities
20
  $this->tax_language = 'term_language';
21
  $this->tax_translations = 'term_translations';
22
  $this->tax_tt = 'tl_term_taxonomy_id';
129
  if ( ! doing_action( 'pre_delete_term' ) && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->terms WHERE term_id = %d;", $id ) ) ) {
130
  // Always keep a group for terms to allow relationships remap when importing from a WXR file
131
  $translations[ $slug ] = $id;
132
+ wp_insert_term( $group = uniqid( 'pll_' ), 'term_translations', array( 'description' => maybe_serialize( $translations ) ) );
133
  wp_set_object_terms( $id, $group, 'term_translations' );
134
  }
135
  }
138
  * A join clause to add to sql queries when filtering by language is needed directly in query
139
  *
140
  * @since 1.2
141
+ * @since 2.6 The `$alias` parameter was added.
142
  *
143
+ * @param string $alias Alias for $wpdb->terms table
144
  * @return string join clause
145
  */
146
+ public function join_clause( $alias = 't' ) {
147
  global $wpdb;
148
+ return " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = $alias.term_id";
149
  }
150
 
151
  /**
include/walker-dropdown.php CHANGED
@@ -25,7 +25,7 @@ class PLL_Walker_Dropdown extends Walker {
25
  "\t" . '<option value="%1$s"%2$s%3$s>%4$s</option>' . "\n",
26
  esc_attr( $element->$value ),
27
  method_exists( $element, 'get_locale' ) ? sprintf( ' lang="%s"', esc_attr( $element->get_locale( 'display' ) ) ) : '',
28
- isset( $args['selected'] ) && $args['selected'] === $element->$value ? ' selected="selected"' : '',
29
  esc_html( $element->name )
30
  );
31
  }
@@ -85,7 +85,7 @@ class PLL_Walker_Dropdown extends Walker {
85
  $name = esc_attr( $args['name'] ),
86
  isset( $args['id'] ) && ! $args['id'] ? '' : ' id="' . ( empty( $args['id'] ) ? $name : esc_attr( $args['id'] ) ) . '"',
87
  empty( $args['class'] ) ? '' : ' class="' . esc_attr( $args['class'] ) . '"',
88
- empty( $args['disabled'] ) ? '' : ' disabled="disabled"',
89
  parent::walk( $elements, -1, $args )
90
  );
91
 
25
  "\t" . '<option value="%1$s"%2$s%3$s>%4$s</option>' . "\n",
26
  esc_attr( $element->$value ),
27
  method_exists( $element, 'get_locale' ) ? sprintf( ' lang="%s"', esc_attr( $element->get_locale( 'display' ) ) ) : '',
28
+ selected( isset( $args['selected'] ) && $args['selected'] === $element->$value, true, false ),
29
  esc_html( $element->name )
30
  );
31
  }
85
  $name = esc_attr( $args['name'] ),
86
  isset( $args['id'] ) && ! $args['id'] ? '' : ' id="' . ( empty( $args['id'] ) ? $name : esc_attr( $args['id'] ) ) . '"',
87
  empty( $args['class'] ) ? '' : ' class="' . esc_attr( $args['class'] ) . '"',
88
+ disabled( empty( $args['disabled'] ), false, false ),
89
  parent::walk( $elements, -1, $args )
90
  );
91
 
include/widget-languages.php CHANGED
@@ -15,7 +15,7 @@ class PLL_Widget_Languages extends WP_Widget {
15
  public function __construct() {
16
  parent::__construct(
17
  'polylang',
18
- __( 'Language Switcher', 'polylang' ),
19
  array(
20
  'description' => __( 'Displays a language switcher', 'polylang' ),
21
  'customize_selective_refresh' => true,
@@ -40,17 +40,17 @@ class PLL_Widget_Languages extends WP_Widget {
40
  /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
41
  $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
42
 
43
- echo $args['before_widget'];
44
  if ( $title ) {
45
- echo $args['before_title'] . $title . $args['after_title'];
46
  }
47
  if ( $instance['dropdown'] ) {
48
  echo '<label class="screen-reader-text" for="' . esc_attr( 'lang_choice_' . $instance['dropdown'] ) . '">' . esc_html__( 'Choose a language', 'polylang' ) . '</label>';
49
- echo $list;
50
  } else {
51
- echo "<ul>\n" . $list . "</ul>\n";
52
  }
53
- echo $args['after_widget'];
54
  }
55
  }
56
 
@@ -64,7 +64,7 @@ class PLL_Widget_Languages extends WP_Widget {
64
  * @return array Settings to save or bool false to cancel saving
65
  */
66
  public function update( $new_instance, $old_instance ) {
67
- $instance['title'] = strip_tags( $new_instance['title'] );
68
  foreach ( array_keys( PLL_Switcher::get_switcher_options( 'widget' ) ) as $key ) {
69
  $instance[ $key ] = ! empty( $new_instance[ $key ] ) ? 1 : 0;
70
  }
@@ -86,28 +86,25 @@ class PLL_Widget_Languages extends WP_Widget {
86
  // Title
87
  printf(
88
  '<p><label for="%1$s">%2$s</label><input class="widefat" id="%1$s" name="%3$s" type="text" value="%4$s" /></p>',
89
- $this->get_field_id( 'title' ),
90
  esc_html__( 'Title:', 'polylang' ),
91
- $this->get_field_name( 'title' ),
92
  esc_attr( $instance['title'] )
93
  );
94
 
95
- $fields = '';
96
  foreach ( PLL_Switcher::get_switcher_options( 'widget' ) as $key => $str ) {
97
- $fields .= sprintf(
98
  '<div%5$s%6$s><input type="checkbox" class="checkbox %7$s" id="%1$s" name="%2$s"%3$s /><label for="%1$s">%4$s</label></div>',
99
- $this->get_field_id( $key ),
100
- $this->get_field_name( $key ),
101
- $instance[ $key ] ? ' checked="checked"' : '',
102
  esc_html( $str ),
103
- in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? ' class="no-dropdown-' . $this->id . '"' : '',
104
- ! empty( $instance['dropdown'] ) && in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? ' style="display:none;"' : '',
105
- 'pll-' . $key
106
  );
107
  }
108
 
109
- echo $fields;
110
-
111
  // FIXME echoing script in form is not very clean
112
  // but it does not work if enqueued properly :
113
  // clicking save on a widget makes this code unreachable for the just saved widget ( ?! )
15
  public function __construct() {
16
  parent::__construct(
17
  'polylang',
18
+ __( 'Language switcher', 'polylang' ),
19
  array(
20
  'description' => __( 'Displays a language switcher', 'polylang' ),
21
  'customize_selective_refresh' => true,
40
  /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
41
  $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
42
 
43
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput
44
  if ( $title ) {
45
+ echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput
46
  }
47
  if ( $instance['dropdown'] ) {
48
  echo '<label class="screen-reader-text" for="' . esc_attr( 'lang_choice_' . $instance['dropdown'] ) . '">' . esc_html__( 'Choose a language', 'polylang' ) . '</label>';
49
+ echo $list; // phpcs:ignore WordPress.Security.EscapeOutput
50
  } else {
51
+ echo "<ul>\n" . $list . "</ul>\n"; // phpcs:ignore WordPress.Security.EscapeOutput
52
  }
53
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput
54
  }
55
  }
56
 
64
  * @return array Settings to save or bool false to cancel saving
65
  */
66
  public function update( $new_instance, $old_instance ) {
67
+ $instance['title'] = sanitize_text_field( $new_instance['title'] );
68
  foreach ( array_keys( PLL_Switcher::get_switcher_options( 'widget' ) ) as $key ) {
69
  $instance[ $key ] = ! empty( $new_instance[ $key ] ) ? 1 : 0;
70
  }
86
  // Title
87
  printf(
88
  '<p><label for="%1$s">%2$s</label><input class="widefat" id="%1$s" name="%3$s" type="text" value="%4$s" /></p>',
89
+ esc_attr( $this->get_field_id( 'title' ) ),
90
  esc_html__( 'Title:', 'polylang' ),
91
+ esc_attr( $this->get_field_name( 'title' ) ),
92
  esc_attr( $instance['title'] )
93
  );
94
 
 
95
  foreach ( PLL_Switcher::get_switcher_options( 'widget' ) as $key => $str ) {
96
+ printf(
97
  '<div%5$s%6$s><input type="checkbox" class="checkbox %7$s" id="%1$s" name="%2$s"%3$s /><label for="%1$s">%4$s</label></div>',
98
+ esc_attr( $this->get_field_id( $key ) ),
99
+ esc_attr( $this->get_field_name( $key ) ),
100
+ checked( $instance[ $key ], true, false ),
101
  esc_html( $str ),
102
+ in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? sprintf( ' class="no-dropdown-%s"', esc_attr( $this->id ) ) : '',
103
+ ( ! empty( $instance['dropdown'] ) && in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? ' style="display:none;"' : '' ),
104
+ esc_attr( 'pll-' . $key )
105
  );
106
  }
107
 
 
 
108
  // FIXME echoing script in form is not very clean
109
  // but it does not work if enqueued properly :
110
  // clicking save on a widget makes this code unreachable for the just saved widget ( ?! )
install/install-base.php CHANGED
@@ -34,7 +34,7 @@ class PLL_Install_Base {
34
  * @return bool true if the plugin is currently beeing deactivated
35
  */
36
  public function is_deactivation() {
37
- return isset( $_GET['action'], $_GET['plugin'] ) && 'deactivate' == $_GET['action'] && $this->plugin_basename == $_GET['plugin'];
38
  }
39
 
40
  /**
34
  * @return bool true if the plugin is currently beeing deactivated
35
  */
36
  public function is_deactivation() {
37
+ return isset( $_GET['action'], $_GET['plugin'] ) && 'deactivate' === $_GET['action'] && $this->plugin_basename === $_GET['plugin']; // phpcs:ignore WordPress.Security.NonceVerification
38
  }
39
 
40
  /**
install/install.php CHANGED
@@ -29,7 +29,7 @@ class PLL_Install extends PLL_Install_Base {
29
  /* translators: %1$s and %2$s are WordPress version numbers */
30
  esc_html__( 'You are using WordPress %1$s. Polylang requires at least WordPress %2$s.', 'polylang' ),
31
  esc_html( $wp_version ),
32
- PLL_MIN_WP_VERSION
33
  )
34
  )
35
  );
29
  /* translators: %1$s and %2$s are WordPress version numbers */
30
  esc_html__( 'You are using WordPress %1$s. Polylang requires at least WordPress %2$s.', 'polylang' ),
31
  esc_html( $wp_version ),
32
+ PLL_MIN_WP_VERSION // phpcs:ignore WordPress.Security.EscapeOutput
33
  )
34
  )
35
  );
install/plugin-updater.php CHANGED
@@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
10
  * Modified version with 'polylang' text domain and comments for translators
11
  *
12
  * @author Easy Digital Downloads
13
- * @version 1.6.16
14
  */
15
  class PLL_Plugin_Updater {
16
 
@@ -22,6 +22,8 @@ class PLL_Plugin_Updater {
22
  private $wp_override = false;
23
  private $cache_key = '';
24
 
 
 
25
  /**
26
  * Class constructor.
27
  *
@@ -122,6 +124,9 @@ class PLL_Plugin_Updater {
122
 
123
  $_transient_data->response[ $this->name ] = $version_info;
124
 
 
 
 
125
  }
126
 
127
  $_transient_data->last_checked = time();
@@ -170,6 +175,19 @@ class PLL_Plugin_Updater {
170
  if ( false === $version_info ) {
171
  $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  $this->set_version_info_cache( $version_info );
174
  }
175
 
@@ -265,7 +283,8 @@ class PLL_Plugin_Updater {
265
  'is_ssl' => is_ssl(),
266
  'fields' => array(
267
  'banners' => array(),
268
- 'reviews' => false
 
269
  )
270
  );
271
 
@@ -292,27 +311,47 @@ class PLL_Plugin_Updater {
292
 
293
  // Convert sections into an associative array, since we're getting an object, but Core expects an array.
294
  if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
295
- $new_sections = array();
296
- foreach ( $_data->sections as $key => $value ) {
297
- $new_sections[ $key ] = $value;
298
- }
299
-
300
- $_data->sections = $new_sections;
301
  }
302
 
303
  // Convert banners into an associative array, since we're getting an object, but Core expects an array.
304
  if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
305
- $new_banners = array();
306
- foreach ( $_data->banners as $key => $value ) {
307
- $new_banners[ $key ] = $value;
308
- }
309
 
310
- $_data->banners = $new_banners;
 
 
 
 
 
 
311
  }
312
 
313
  return $_data;
314
  }
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  /**
317
  * Disable SSL verification in order to prevent download update failures
318
  *
@@ -343,7 +382,31 @@ class PLL_Plugin_Updater {
343
  */
344
  private function api_request( $_action, $_data ) {
345
 
346
- global $wp_version;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
  $data = array_merge( $this->api_data, $_data );
349
 
@@ -351,7 +414,7 @@ class PLL_Plugin_Updater {
351
  return;
352
  }
353
 
354
- if( $this->api_url == trailingslashit (home_url() ) ) {
355
  return false; // Don't allow a plugin to ping itself
356
  }
357
 
@@ -367,7 +430,6 @@ class PLL_Plugin_Updater {
367
  'beta' => ! empty( $data['beta'] ),
368
  );
369
 
370
- $verify_ssl = $this->verify_ssl();
371
  $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
372
 
373
  if ( ! is_wp_error( $request ) ) {
@@ -384,6 +446,10 @@ class PLL_Plugin_Updater {
384
  $request->banners = maybe_unserialize( $request->banners );
385
  }
386
 
 
 
 
 
387
  if( ! empty( $request->sections ) ) {
388
  foreach( $request->sections as $key => $section ) {
389
  $request->$key = (array) $section;
@@ -473,7 +539,13 @@ class PLL_Plugin_Updater {
473
  return false; // Cache is expired
474
  }
475
 
476
- return json_decode( $cache['value'] );
 
 
 
 
 
 
477
 
478
  }
479
 
10
  * Modified version with 'polylang' text domain and comments for translators
11
  *
12
  * @author Easy Digital Downloads
13
+ * @version 1.6.18
14
  */
15
  class PLL_Plugin_Updater {
16
 
22
  private $wp_override = false;
23
  private $cache_key = '';
24
 
25
+ private $health_check_timeout = 5;
26
+
27
  /**
28
  * Class constructor.
29
  *
124
 
125
  $_transient_data->response[ $this->name ] = $version_info;
126
 
127
+ // Make sure the plugin property is set to the plugin's name/location. See issue 1463 on Software Licensing's GitHub repo.
128
+ $_transient_data->response[ $this->name ]->plugin = $this->name;
129
+
130
  }
131
 
132
  $_transient_data->last_checked = time();
175
  if ( false === $version_info ) {
176
  $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
177
 
178
+ // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
179
+ if ( isset( $version_info->banners ) && ! is_array( $version_info->banners ) ) {
180
+ $version_info->banners = $this->convert_object_to_array( $version_info->banners );
181
+ }
182
+
183
+ if ( isset( $version_info->sections ) && ! is_array( $version_info->sections ) ) {
184
+ $version_info->sections = $this->convert_object_to_array( $version_info->sections );
185
+ }
186
+
187
+ if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
188
+ $version_info->icons = $this->convert_object_to_array( $version_info->icons );
189
+ }
190
+
191
  $this->set_version_info_cache( $version_info );
192
  }
193
 
283
  'is_ssl' => is_ssl(),
284
  'fields' => array(
285
  'banners' => array(),
286
+ 'reviews' => false,
287
+ 'icons' => array(),
288
  )
289
  );
290
 
311
 
312
  // Convert sections into an associative array, since we're getting an object, but Core expects an array.
313
  if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
314
+ $_data->sections = $this->convert_object_to_array( $_data->sections );
 
 
 
 
 
315
  }
316
 
317
  // Convert banners into an associative array, since we're getting an object, but Core expects an array.
318
  if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
319
+ $_data->banners = $this->convert_object_to_array( $_data->banners );
320
+ }
 
 
321
 
322
+ // Convert icons into an associative array, since we're getting an object, but Core expects an array.
323
+ if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) {
324
+ $_data->icons = $this->convert_object_to_array( $_data->icons );
325
+ }
326
+
327
+ if( ! isset( $_data->plugin ) ) {
328
+ $_data->plugin = $this->name;
329
  }
330
 
331
  return $_data;
332
  }
333
 
334
+ /**
335
+ * Convert some objects to arrays when injecting data into the update API
336
+ *
337
+ * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
338
+ * decoding, they are objects. This method allows us to pass in the object and return an associative array.
339
+ *
340
+ * @since 3.6.5
341
+ *
342
+ * @param stdClass $data
343
+ *
344
+ * @return array
345
+ */
346
+ private function convert_object_to_array( $data ) {
347
+ $new_data = array();
348
+ foreach ( $data as $key => $value ) {
349
+ $new_data[ $key ] = $value;
350
+ }
351
+
352
+ return $new_data;
353
+ }
354
+
355
  /**
356
  * Disable SSL verification in order to prevent download update failures
357
  *
382
  */
383
  private function api_request( $_action, $_data ) {
384
 
385
+ global $wp_version, $edd_plugin_url_available;
386
+
387
+ $verify_ssl = $this->verify_ssl();
388
+
389
+ // Do a quick status check on this domain if we haven't already checked it.
390
+ $store_hash = md5( $this->api_url );
391
+ if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
392
+ $test_url_parts = parse_url( $this->api_url );
393
+
394
+ $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http';
395
+ $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : '';
396
+ $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : '';
397
+
398
+ if ( empty( $host ) ) {
399
+ $edd_plugin_url_available[ $store_hash ] = false;
400
+ } else {
401
+ $test_url = $scheme . '://' . $host . $port;
402
+ $response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl ) );
403
+ $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
404
+ }
405
+ }
406
+
407
+ if ( false === $edd_plugin_url_available[ $store_hash ] ) {
408
+ return;
409
+ }
410
 
411
  $data = array_merge( $this->api_data, $_data );
412
 
414
  return;
415
  }
416
 
417
+ if( $this->api_url == trailingslashit ( home_url() ) ) {
418
  return false; // Don't allow a plugin to ping itself
419
  }
420
 
430
  'beta' => ! empty( $data['beta'] ),
431
  );
432
 
 
433
  $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
434
 
435
  if ( ! is_wp_error( $request ) ) {
446
  $request->banners = maybe_unserialize( $request->banners );
447
  }
448
 
449
+ if ( $request && isset( $request->icons ) ) {
450
+ $request->icons = maybe_unserialize( $request->icons );
451
+ }
452
+
453
  if( ! empty( $request->sections ) ) {
454
  foreach( $request->sections as $key => $section ) {
455
  $request->$key = (array) $section;
539
  return false; // Cache is expired
540
  }
541
 
542
+ // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
543
+ $cache['value'] = json_decode( $cache['value'] );
544
+ if ( ! empty( $cache['value']->icons ) ) {
545
+ $cache['value']->icons = (array) $cache['value']->icons;
546
+ }
547
+
548
+ return $cache['value'];
549
 
550
  }
551
 
install/t15s.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Allows to download translations from TranslationsPress
5
+ * This is a modified version of the library available at https://github.com/WP-Translations/t15s-registry
6
+ * This version aims to be compatible with PHP 5.2, and supports only plugins.
7
+ *
8
+ * @since 2.6
9
+ */
10
+ class PLL_T15S {
11
+
12
+ const TRANSIENT_KEY_PLUGIN = 't15s-registry-plugins';
13
+
14
+ private $type = 'plugin';
15
+ private $slug = '';
16
+ private $api_url = '';
17
+
18
+ /**
19
+ * Adds a new project to load translations for.
20
+ *
21
+ * @since 2.6
22
+ *
23
+ * @param string $slug Project directory slug.
24
+ * @param string $api_url Full GlotPress API URL for the project.
25
+ */
26
+ public function __construct( $slug, $api_url ) {
27
+ $this->slug = $slug;
28
+ $this->api_url = $api_url;
29
+
30
+ add_action( 'init', array( __CLASS__, 'register_clean_translations_cache' ), 9999 );
31
+ add_filter( 'translations_api', array( $this, 'translations_api' ), 10, 3 );
32
+ add_filter( 'site_transient_update_' . $this->type . 's', array( $this, 'site_transient_update_plugins' ) );
33
+ }
34
+
35
+ /**
36
+ * Short-circuits translations API requests for private projects.
37
+ *
38
+ * @since 2.6
39
+ *
40
+ * @param bool|array $result The result object. Default false.
41
+ * @param string $requested_type The type of translations being requested.
42
+ * @param object $args Translation API arguments.
43
+ * @return bool|array
44
+ */
45
+ public function translations_api( $result, $requested_type, $args ) {
46
+ if ( $this->type . 's' === $requested_type && $this->slug === $args['slug'] ) {
47
+ return self::get_translations( $this->type, $args['slug'], $this->api_url );
48
+ }
49
+
50
+ return $result;
51
+ }
52
+
53
+ /**
54
+ * Filters the translations transients to include the private plugin or theme.
55
+ *
56
+ * @see wp_get_translation_updates()
57
+ *
58
+ * @since 2.6
59
+ *
60
+ * @param bool|array $value The transient value.
61
+ */
62
+ public function site_transient_update_plugins( $value ) {
63
+ if ( ! $value ) {
64
+ $value = new stdClass();
65
+ }
66
+
67
+ if ( ! isset( $value->translations ) ) {
68
+ $value->translations = array();
69
+ }
70
+
71
+ $translations = self::get_translations( $this->type, $this->slug, $this->api_url );
72
+
73
+ if ( ! isset( $translations['translations'] ) ) {
74
+ return $value;
75
+ }
76
+
77
+ $installed_translations = wp_get_installed_translations( $this->type . 's' );
78
+
79
+ foreach ( (array) $translations['translations'] as $translation ) {
80
+ if ( in_array( $translation['language'], get_available_languages() ) ) {
81
+ if ( isset( $installed_translations[ $this->slug ][ $translation['language'] ] ) && $translation['updated'] ) {
82
+ $local = new DateTime( $installed_translations[ $this->slug ][ $translation['language'] ]['PO-Revision-Date'] );
83
+ $remote = new DateTime( $translation['updated'] );
84
+
85
+ if ( $local >= $remote ) {
86
+ continue;
87
+ }
88
+ }
89
+
90
+ $translation['type'] = $this->type;
91
+ $translation['slug'] = $this->slug;
92
+
93
+ $value->translations[] = $translation;
94
+ }
95
+ }
96
+
97
+ return $value;
98
+ }
99
+
100
+ /**
101
+ * Registers actions for clearing translation caches.
102
+ *
103
+ * @since 2.6
104
+ */
105
+ public static function register_clean_translations_cache() {
106
+ add_action( 'set_site_transient_update_plugins', array( __CLASS__, 'clean_translations_cache' ) );
107
+ add_action( 'delete_site_transient_update_plugins', array( __CLASS__, 'clean_translations_cache' ) );
108
+ }
109
+
110
+ /**
111
+ * Clears existing translation cache.
112
+ *
113
+ * @since 2.6
114
+ */
115
+ public static function clean_translations_cache() {
116
+ $translations = get_site_transient( self::TRANSIENT_KEY_PLUGIN );
117
+
118
+ if ( ! is_object( $translations ) ) {
119
+ return;
120
+ }
121
+
122
+ /*
123
+ * Don't delete the cache if the transient gets changed multiple times
124
+ * during a single request. Set cache lifetime to maximum 15 seconds.
125
+ */
126
+ $cache_lifespan = 15;
127
+ $time_not_changed = isset( $translations->_last_checked ) && ( time() - $translations->_last_checked ) > $cache_lifespan;
128
+
129
+ if ( ! $time_not_changed ) {
130
+ return;
131
+ }
132
+
133
+ delete_site_transient( self::TRANSIENT_KEY_PLUGIN );
134
+ }
135
+
136
+ /**
137
+ * Gets the translations for a given project.
138
+ *
139
+ * @since 2.6
140
+ *
141
+ * @param string $type Project type. Either plugin or theme.
142
+ * @param string $slug Project directory slug.
143
+ * @param string $url Full GlotPress API URL for the project.
144
+ * @return array Translation data.
145
+ */
146
+ private static function get_translations( $type, $slug, $url ) {
147
+ $translations = get_site_transient( self::TRANSIENT_KEY_PLUGIN );
148
+
149
+ if ( ! is_object( $translations ) ) {
150
+ $translations = new stdClass();
151
+ }
152
+
153
+ if ( isset( $translations->{$slug} ) && is_array( $translations->{$slug} ) ) {
154
+ return $translations->{$slug};
155
+ }
156
+
157
+ $result = json_decode( wp_remote_retrieve_body( wp_remote_get( $url, array( 'timeout' => 2 ) ) ), true );
158
+ if ( is_array( $result ) ) {
159
+ $translations->{$slug} = $result;
160
+ $translations->_last_checked = time();
161
+
162
+ set_site_transient( self::TRANSIENT_KEY_PLUGIN, $translations );
163
+ return $result;
164
+ }
165
+
166
+ // Nothing found.
167
+ return array();
168
+ }
169
+ }
install/upgrade.php CHANGED
@@ -28,7 +28,7 @@ class PLL_Upgrade {
28
  if ( ! $this->can_upgrade() ) {
29
  ob_start();
30
  $this->admin_notices(); // FIXME the error message is displayed two times
31
- die( ob_get_contents() );
32
  }
33
  }
34
 
@@ -76,9 +76,9 @@ class PLL_Upgrade {
76
  esc_html__( 'Polylang has been deactivated because you upgraded from a too old version.', 'polylang' ),
77
  sprintf(
78
  /* translators: %1$s and %2$s are Polylang version numbers */
79
- esc_html__( 'Please upgrade first to %1$s before ugrading to %2$s.', 'polylang' ),
80
  '<strong>0.9.8</strong>',
81
- POLYLANG_VERSION
82
  )
83
  );
84
  }
@@ -184,7 +184,7 @@ class PLL_Upgrade {
184
  foreach ( $languages as $lang ) {
185
  // First update language with new storage for locale and text direction
186
  $text_direction = get_metadata( 'term', $lang->term_id, '_rtl', true );
187
- $desc = serialize( array( 'locale' => $lang->description, 'rtl' => $text_direction ) );
188
  wp_update_term( (int) $lang->term_id, 'language', array( 'description' => $desc ) );
189
 
190
  // Add language to new 'term_language' taxonomy
@@ -212,7 +212,7 @@ class PLL_Upgrade {
212
  $terms = $slugs = $tts = $trs = array();
213
 
214
  // Get all translated objects
215
- // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
216
  $objects = $wpdb->get_col( "SELECT DISTINCT meta_value FROM {$wpdb->$table} WHERE meta_key = '_translations'" );
217
 
218
  if ( empty( $objects ) ) {
@@ -223,8 +223,8 @@ class PLL_Upgrade {
223
  $term = uniqid( 'pll_' ); // The term name
224
  $terms[] = $wpdb->prepare( '( %s, %s )', $term, $term );
225
  $slugs[] = $wpdb->prepare( '%s', $term );
226
- $translations = maybe_unserialize( maybe_unserialize( $obj ) ); // 2 unserialize due to an old storage bug
227
- $description[ $term ] = serialize( $translations );
228
  }
229
 
230
  $terms = array_unique( $terms );
@@ -257,7 +257,7 @@ class PLL_Upgrade {
257
 
258
  // Prepare objects relationships
259
  foreach ( $terms as $term ) {
260
- $translations = unserialize( $term->description );
261
  foreach ( $translations as $object_id ) {
262
  if ( ! empty( $object_id ) ) {
263
  $trs[] = $wpdb->prepare( '( %d, %d )', $object_id, $term->term_taxonomy_id );
@@ -549,7 +549,7 @@ class PLL_Upgrade {
549
  */
550
  protected function upgrade_1_8() {
551
  // Adds the flag code in languages stored in DB
552
- include PLL_SETTINGS_INC . '/languages.php';
553
 
554
  $terms = get_terms( 'language', array( 'hide_empty' => 0 ) );
555
 
@@ -557,7 +557,7 @@ class PLL_Upgrade {
557
  $description = maybe_unserialize( $lang->description );
558
  if ( isset( $languages[ $description['locale'] ] ) ) {
559
  $description['flag_code'] = $languages[ $description['locale'] ]['flag'];
560
- $description = serialize( $description );
561
  wp_update_term( (int) $lang->term_id, 'language', array( 'description' => $description ) );
562
  }
563
  }
@@ -589,7 +589,7 @@ class PLL_Upgrade {
589
 
590
  if ( empty( $meta ) ) {
591
  $post = get_post( $mo_id, OBJECT );
592
- $strings = unserialize( $post->post_content );
593
  if ( is_array( $strings ) ) {
594
  update_post_meta( $mo_id, '_pll_strings_translations', $strings );
595
  }
28
  if ( ! $this->can_upgrade() ) {
29
  ob_start();
30
  $this->admin_notices(); // FIXME the error message is displayed two times
31
+ die( ob_get_contents() ); // phpcs:ignore WordPress.Security.EscapeOutput
32
  }
33
  }
34
 
76
  esc_html__( 'Polylang has been deactivated because you upgraded from a too old version.', 'polylang' ),
77
  sprintf(
78
  /* translators: %1$s and %2$s are Polylang version numbers */
79
+ esc_html__( 'Before upgrading to %2$s, please upgrade to %1$s.', 'polylang' ),
80
  '<strong>0.9.8</strong>',
81
+ POLYLANG_VERSION // phpcs:ignore WordPress.Security.EscapeOutput
82
  )
83
  );
84
  }
184
  foreach ( $languages as $lang ) {
185
  // First update language with new storage for locale and text direction
186
  $text_direction = get_metadata( 'term', $lang->term_id, '_rtl', true );
187
+ $desc = maybe_serialize( array( 'locale' => $lang->description, 'rtl' => $text_direction ) );
188
  wp_update_term( (int) $lang->term_id, 'language', array( 'description' => $desc ) );
189
 
190
  // Add language to new 'term_language' taxonomy
212
  $terms = $slugs = $tts = $trs = array();
213
 
214
  // Get all translated objects
215
+ // PHPCS:ignore WordPress.DB.PreparedSQL
216
  $objects = $wpdb->get_col( "SELECT DISTINCT meta_value FROM {$wpdb->$table} WHERE meta_key = '_translations'" );
217
 
218
  if ( empty( $objects ) ) {
223
  $term = uniqid( 'pll_' ); // The term name
224
  $terms[] = $wpdb->prepare( '( %s, %s )', $term, $term );
225
  $slugs[] = $wpdb->prepare( '%s', $term );
226
+ $translations = maybe_unserialize( maybe_unserialize( $obj ) ); // 2 maybe_unserialize due to an old storage bug
227
+ $description[ $term ] = maybe_serialize( $translations );
228
  }
229
 
230
  $terms = array_unique( $terms );
257
 
258
  // Prepare objects relationships
259
  foreach ( $terms as $term ) {
260
+ $translations = maybe_unserialize( $term->description );
261
  foreach ( $translations as $object_id ) {
262
  if ( ! empty( $object_id ) ) {
263
  $trs[] = $wpdb->prepare( '( %d, %d )', $object_id, $term->term_taxonomy_id );
549
  */
550
  protected function upgrade_1_8() {
551
  // Adds the flag code in languages stored in DB
552
+ $languages = include PLL_SETTINGS_INC . '/languages.php';
553
 
554
  $terms = get_terms( 'language', array( 'hide_empty' => 0 ) );
555
 
557
  $description = maybe_unserialize( $lang->description );
558
  if ( isset( $languages[ $description['locale'] ] ) ) {
559
  $description['flag_code'] = $languages[ $description['locale'] ]['flag'];
560
+ $description = maybe_serialize( $description );
561
  wp_update_term( (int) $lang->term_id, 'language', array( 'description' => $description ) );
562
  }
563
  }
589
 
590
  if ( empty( $meta ) ) {
591
  $post = get_post( $mo_id, OBJECT );
592
+ $strings = maybe_unserialize( $post->post_content );
593
  if ( is_array( $strings ) ) {
594
  update_post_meta( $mo_id, '_pll_strings_translations', $strings );
595
  }
js/block-editor.js CHANGED
@@ -29,7 +29,7 @@ function getCurrentLanguage() {
29
  }
30
 
31
  /**
32
- * save post after lang choice is done and redirect to the same page for refreshing all the data
33
  *
34
  * @since 2.5
35
  */
@@ -90,3 +90,95 @@ jQuery( document ).ready(function( $ ) {
90
  } );
91
  } );
92
  } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  /**
32
+ * Save post after lang choice is done and redirect to the same page for refreshing all the data
33
  *
34
  * @since 2.5
35
  */
90
  } );
91
  } );
92
  } );
93
+
94
+ /**
95
+ * Handles internals of the metabox:
96
+ * Language select, autocomplete input field and buttons.
97
+ *
98
+ * @since 1.5
99
+ */
100
+ jQuery( document ).ready(function( $ ) {
101
+ // Ajax for changing the post's language in the languages metabox
102
+ $( '.post_lang_choice' ).change(function() {
103
+ var data = {
104
+ action: 'post_lang_choice',
105
+ lang: $( this ).val(),
106
+ post_type: $( '#post_type' ).val(),
107
+ post_id: $( '#post_ID' ).val(),
108
+ _pll_nonce: $( '#_pll_nonce' ).val()
109
+ }
110
+
111
+ $.post( ajaxurl, data , function( response ) {
112
+ var res = wpAjax.parseAjaxResponse( response, 'ajax-response' );
113
+ $.each( res.responses, function() {
114
+ switch ( this.what ) {
115
+ case 'translations': // Translations fields
116
+ $( '.translations' ).html( this.data );
117
+ init_translations();
118
+ break;
119
+ case 'flag': // Flag in front of the select dropdown
120
+ $( '.pll-select-flag' ).html( this.data );
121
+ break;
122
+ }
123
+ });
124
+ });
125
+ });
126
+
127
+ // Translations autocomplete input box
128
+ function init_translations() {
129
+ $( '.tr_lang' ).each(function(){
130
+ var tr_lang = $( this ).attr( 'id' ).substring( 8 );
131
+ var td = $( this ).parent().parent().siblings( '.pll-edit-column' );
132
+
133
+ $( this ).autocomplete({
134
+ minLength: 0,
135
+
136
+ source: ajaxurl + '?action=pll_posts_not_translated' +
137
+ '&post_language=' + $( '.post_lang_choice' ).val() +
138
+ '&translation_language=' + tr_lang +
139
+ '&post_type=' + $( '#post_type' ).val() +
140
+ '&_pll_nonce=' + $( '#_pll_nonce' ).val(),
141
+
142
+ select: function( event, ui ) {
143
+ $( '#htr_lang_' + tr_lang ).val( ui.item.id );
144
+ td.html( ui.item.link );
145
+ },
146
+ });
147
+
148
+ // When the input box is emptied
149
+ $( this ).blur(function() {
150
+ if ( ! $( this ).val() ) {
151
+ $( '#htr_lang_' + tr_lang ).val( 0 );
152
+ td.html( td.siblings( '.hidden' ).children().clone() );
153
+ }
154
+ });
155
+ });
156
+ }
157
+
158
+ init_translations();
159
+
160
+ // Handle the response to a click on a Languages metabox button
161
+ $( '#ml_box' ).on( 'click', '.pll-button', function(){
162
+ var value = $( this ).hasClass( 'wp-ui-text-highlight' );
163
+ var id = $( this ).attr( 'id' );
164
+ var post_id = $( '#htr_lang_' + id.replace( 'pll_sync_post[', '' ).replace( ']', '' ) ).val();
165
+
166
+ if ( 'undefined' == typeof( post_id ) || 0 == post_id || value || confirm( confirm_text ) ) {
167
+ var data = {
168
+ action: 'toggle_' + id,
169
+ value: value,
170
+ post_type: $( '#post_type' ).val(),
171
+ _pll_nonce: $( '#_pll_nonce' ).val()
172
+ }
173
+
174
+ $.post( ajaxurl, data , function( response ){
175
+ var res = wpAjax.parseAjaxResponse( response, 'ajax-response' );
176
+ $.each( res.responses, function() {
177
+ id = id.replace( '[', '\\[' ).replace( ']', '\\]' );
178
+ $( '#' + id ).toggleClass( 'wp-ui-text-highlight' ).attr( 'title', this.data ).children( 'span' ).html( this.data );
179
+ $( 'input[name="' + id + '"]' ).val( ! data['value'] );
180
+ });
181
+ });
182
+ }
183
+ });
184
+ });
js/block-editor.min.js CHANGED
@@ -1 +1 @@
1
- wp.apiFetch.use(function(options,next){if('undefined'===typeof options.url){if('undefined'===typeof options.data){options.path+=((options.path.indexOf('?')>=0)?'&lang=':'?lang=')+getCurrentLanguage()}else{options.data.lang=getCurrentLanguage()}}return next(options)});function getCurrentLanguage(){return document.querySelector('[name=post_lang_choice]').value}jQuery(document).ready(function($){$('.post_lang_choice').change(function(){const select=wp.data.select;const dispatch=wp.data.dispatch;const subscribe=wp.data.subscribe;let unsubscribe=null;const savePostIsDone=new Promise(function(resolve,reject){unsubscribe=subscribe(function(){const isSavePostSucceeded=select('core/editor').didPostSaveRequestSucceed();const isSavePostFailed=select('core/editor').didPostSaveRequestFail();if(isSavePostSucceeded||isSavePostFailed){if(isSavePostFailed){reject()}else{resolve()}}})});if(location.pathname.match(/post-new.php/gi)){const title=select('core/editor').getEditedPostAttribute('title');const content=select('core/editor').getEditedPostAttribute('content');const excerpt=select('core/editor').getEditedPostAttribute('excerpt');if(''===title&&''===content&&''===excerpt){if(-1!=location.search.indexOf('new_lang')){window.location.search=window.location.search.replace(/(?:new_lang=[^&]*)(&)?(.*)/,'new_lang='+this.value+'$1$2');}else{window.location.search=window.location.search+((-1!=window.location.search.indexOf('?'))?'&':'?')+'new_lang='+this.value}}}dispatch('core/editor').savePost();savePostIsDone.then(function(){unsubscribe();window.location.reload()},function(){unsubscribe()}).catch(function(){unsubscribe()})})});
1
+ wp.apiFetch.use(function(options,next){if('undefined'===typeof options.url){if('undefined'===typeof options.data){options.path+=((options.path.indexOf('?')>=0)?'&lang=':'?lang=')+getCurrentLanguage()}else{options.data.lang=getCurrentLanguage()}}return next(options)});function getCurrentLanguage(){return document.querySelector('[name=post_lang_choice]').value}jQuery(document).ready(function($){$('.post_lang_choice').change(function(){const select=wp.data.select;const dispatch=wp.data.dispatch;const subscribe=wp.data.subscribe;let unsubscribe=null;const savePostIsDone=new Promise(function(resolve,reject){unsubscribe=subscribe(function(){const isSavePostSucceeded=select('core/editor').didPostSaveRequestSucceed();const isSavePostFailed=select('core/editor').didPostSaveRequestFail();if(isSavePostSucceeded||isSavePostFailed){if(isSavePostFailed){reject()}else{resolve()}}})});if(location.pathname.match(/post-new.php/gi)){const title=select('core/editor').getEditedPostAttribute('title');const content=select('core/editor').getEditedPostAttribute('content');const excerpt=select('core/editor').getEditedPostAttribute('excerpt');if(''===title&&''===content&&''===excerpt){if(-1!=location.search.indexOf('new_lang')){window.location.search=window.location.search.replace(/(?:new_lang=[^&]*)(&)?(.*)/,'new_lang='+this.value+'$1$2');}else{window.location.search=window.location.search+((-1!=window.location.search.indexOf('?'))?'&':'?')+'new_lang='+this.value}}}dispatch('core/editor').savePost();savePostIsDone.then(function(){unsubscribe();window.location.reload()},function(){unsubscribe()}).catch(function(){unsubscribe()})})});jQuery(document).ready(function($){$('.post_lang_choice').change(function(){var data={action:'post_lang_choice',lang:$(this).val(),post_type:$('#post_type').val(),post_id:$('#post_ID').val(),_pll_nonce:$('#_pll_nonce').val()};$.post(ajaxurl,data,function(response){var res=wpAjax.parseAjaxResponse(response,'ajax-response');$.each(res.responses,function(){switch(this.what){case 'translations':$('.translations').html(this.data);init_translations();break;case 'flag':$('.pll-select-flag').html(this.data);break}})})});function init_translations(){$('.tr_lang').each(function(){var tr_lang=$(this).attr('id').substring(8);var td=$(this).parent().parent().siblings('.pll-edit-column');$(this).autocomplete({minLength:0,source:ajaxurl+'?action=pll_posts_not_translated&post_language='+$('.post_lang_choice').val()+'&translation_language='+tr_lang+'&post_type='+$('#post_type').val()+'&_pll_nonce='+$('#_pll_nonce').val(),select:function(event,ui){$('#htr_lang_'+tr_lang).val(ui.item.id);td.html(ui.item.link)}});$(this).blur(function(){if(!$(this).val()){$('#htr_lang_'+tr_lang).val(0);td.html(td.siblings('.hidden').children().clone())}})})}init_translations();$('#ml_box').on('click','.pll-button',function(){var value=$(this).hasClass('wp-ui-text-highlight');var id=$(this).attr('id');var post_id=$('#htr_lang_'+id.replace('pll_sync_post[','').replace(']','')).val();if('undefined'==typeof(post_id)||0==post_id||value||confirm(confirm_text)){var data={action:'toggle_'+id,value:value,post_type:$('#post_type').val(),_pll_nonce:$('#_pll_nonce').val()};$.post(ajaxurl,data,function(response){var res=wpAjax.parseAjaxResponse(response,'ajax-response');$.each(res.responses,function(){id=id.replace('[','\\[').replace(']','\\]');$('#'+id).toggleClass('wp-ui-text-highlight').attr('title',this.data).children('span').html(this.data);$('input[name="'+id+'"]').val(!data['value'])})})}})});
js/classic-editor.js CHANGED
@@ -154,4 +154,29 @@ jQuery( document ).ready(function( $ ) {
154
  }
155
 
156
  init_translations();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  });
154
  }
155
 
156
  init_translations();
157
+
158
+ // Handle the response to a click on a Languages metabox button
159
+ $( '#ml_box' ).on( 'click', '.pll-button', function(){
160
+ var value = $( this ).hasClass( 'wp-ui-text-highlight' );
161
+ var id = $( this ).attr( 'id' );
162
+ var post_id = $( '#htr_lang_' + id.replace( 'pll_sync_post[', '' ).replace( ']', '' ) ).val();
163
+
164
+ if ( 'undefined' == typeof( post_id ) || 0 == post_id || value || confirm( confirm_text ) ) {
165
+ var data = {
166
+ action: 'toggle_' + id,
167
+ value: value,
168
+ post_type: $( '#post_type' ).val(),
169
+ _pll_nonce: $( '#_pll_nonce' ).val()
170
+ }
171
+
172
+ $.post( ajaxurl, data , function( response ){
173
+ var res = wpAjax.parseAjaxResponse( response, 'ajax-response' );
174
+ $.each( res.responses, function() {
175
+ id = id.replace( '[', '\\[' ).replace( ']', '\\]' );
176
+ $( '#' + id ).toggleClass( 'wp-ui-text-highlight' ).attr( 'title', this.data ).children( 'span' ).html( this.data );
177
+ $( 'input[name="' + id + '"]' ).val( ! data['value'] );
178
+ });
179
+ });
180
+ }
181
+ });
182
  });
js/classic-editor.min.js CHANGED
@@ -1 +1 @@
1
- !function(t){t.ajaxPrefilter(function(a){"string"==typeof a.data&&-1!==a.url.indexOf("action=ajax-tag-search")&&(lang=t(".post_lang_choice").val())&&(a.data="lang="+lang+"&"+a.data)})}(jQuery),function(t){tagBox.get=function(a){var l=a.substr(a.indexOf("-")+1),n={action:"get-tagcloud",lang:t(".post_lang_choice").val(),tax:l};t.post(ajaxurl,n,function(n,e){0!=n&&"success"==e||(n=wpAjax.broken),n=t('<div id="tagcloud-'+l+'" class="the-tagcloud">'+n+"</div>"),t("a",n).click(function(){return tagBox.flushTags(t(this).closest(".inside").children(".tagsdiv"),this),!1}),(v=t(".the-tagcloud").css("display"))?(t(".the-tagcloud").replaceWith(n),t(".the-tagcloud").css("display",v)):t("#"+a).after(n)})}}(jQuery),jQuery(document).ready(function(t){function a(){t(".tr_lang").each(function(){var a=t(this).attr("id").substring(8),l=t(this).parent().parent().siblings(".pll-edit-column");t(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+t(".post_lang_choice").val()+"&translation_language="+a+"&post_type="+t("#post_type").val()+"&_pll_nonce="+t("#_pll_nonce").val(),select:function(n,e){t("#htr_lang_"+a).val(e.item.id),l.html(e.item.link)}}),t(this).blur(function(){t(this).val()||(t("#htr_lang_"+a).val(0),l.html(l.siblings(".hidden").children().clone()))})})}var l=new Array;t(".categorydiv").each(function(){var a,n,e=t(this).attr("id");a=e.split("-"),a.shift(),n=a.join("-"),l.push(n),t("#"+n+"-add-submit").before(t("<input />").attr("type","hidden").attr("id",n+"-lang").attr("name","term_lang_choice").attr("value",t(".post_lang_choice").val()))}),t(".post_lang_choice").change(function(){var n=t(this).val(),e=t(this).children('option[value="'+n+'"]').attr("lang"),i=t('.pll-translation-column > span[lang="'+e+'"]').attr("dir"),s={action:"post_lang_choice",lang:n,post_type:t("#post_type").val(),taxonomies:l,post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,s,function(l){var n=wpAjax.parseAjaxResponse(l,"ajax-response");t.each(n.responses,function(){switch(this.what){case"translations":t(".translations").html(this.data),a();break;case"taxonomy":var l=this.data;t("#"+l+"checklist").html(this.supplemental.all),t("#"+l+"checklist-pop").html(this.supplemental.populars),t("#new"+l+"_parent").replaceWith(this.supplemental.dropdown),t("#"+l+"-lang").val(t(".post_lang_choice").val());break;case"pages":t("#parent_id").html(this.data);break;case"flag":t(".pll-select-flag").html(this.data);break;case"permalink":var n=t("#edit-slug-box");"-1"!=this.data&&n.children().length&&n.html(this.data)}}),t(".tagcloud-link").each(function(){var a=t(this).attr("id");tagBox.get(a)}),t("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+i),t("#content_ifr").contents().find("html").attr("lang",e).attr("dir",i),t("#content_ifr").contents().find("body").attr("dir",i)})}),a()});
1
+ !function(t){t.ajaxPrefilter(function(a){"string"==typeof a.data&&-1!==a.url.indexOf("action=ajax-tag-search")&&(lang=t(".post_lang_choice").val())&&(a.data="lang="+lang+"&"+a.data)})}(jQuery),function(t){tagBox.get=function(a){var l=a.substr(a.indexOf("-")+1),n={action:"get-tagcloud",lang:t(".post_lang_choice").val(),tax:l};t.post(ajaxurl,n,function(n,e){0!=n&&"success"==e||(n=wpAjax.broken),n=t('<div id="tagcloud-'+l+'" class="the-tagcloud">'+n+"</div>"),t("a",n).click(function(){return tagBox.flushTags(t(this).closest(".inside").children(".tagsdiv"),this),!1}),(v=t(".the-tagcloud").css("display"))?(t(".the-tagcloud").replaceWith(n),t(".the-tagcloud").css("display",v)):t("#"+a).after(n)})}}(jQuery),jQuery(document).ready(function(t){function a(){t(".tr_lang").each(function(){var a=t(this).attr("id").substring(8),l=t(this).parent().parent().siblings(".pll-edit-column");t(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+t(".post_lang_choice").val()+"&translation_language="+a+"&post_type="+t("#post_type").val()+"&_pll_nonce="+t("#_pll_nonce").val(),select:function(n,e){t("#htr_lang_"+a).val(e.item.id),l.html(e.item.link)}}),t(this).blur(function(){t(this).val()||(t("#htr_lang_"+a).val(0),l.html(l.siblings(".hidden").children().clone()))})})}var l=new Array;t(".categorydiv").each(function(){var a,n,e=t(this).attr("id");a=e.split("-"),a.shift(),n=a.join("-"),l.push(n),t("#"+n+"-add-submit").before(t("<input />").attr("type","hidden").attr("id",n+"-lang").attr("name","term_lang_choice").attr("value",t(".post_lang_choice").val()))}),t(".post_lang_choice").change(function(){var n=t(this).val(),e=t(this).children('option[value="'+n+'"]').attr("lang"),i=t('.pll-translation-column > span[lang="'+e+'"]').attr("dir"),s={action:"post_lang_choice",lang:n,post_type:t("#post_type").val(),taxonomies:l,post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,s,function(l){var n=wpAjax.parseAjaxResponse(l,"ajax-response");t.each(n.responses,function(){switch(this.what){case"translations":t(".translations").html(this.data),a();break;case"taxonomy":var l=this.data;t("#"+l+"checklist").html(this.supplemental.all),t("#"+l+"checklist-pop").html(this.supplemental.populars),t("#new"+l+"_parent").replaceWith(this.supplemental.dropdown),t("#"+l+"-lang").val(t(".post_lang_choice").val());break;case"pages":t("#parent_id").html(this.data);break;case"flag":t(".pll-select-flag").html(this.data);break;case"permalink":var n=t("#edit-slug-box");"-1"!=this.data&&n.children().length&&n.html(this.data)}}),t(".tagcloud-link").each(function(){var a=t(this).attr("id");tagBox.get(a)}),t("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+i),t("#content_ifr").contents().find("html").attr("lang",e).attr("dir",i),t("#content_ifr").contents().find("body").attr("dir",i)})}),a(),t("#ml_box").on("click",".pll-button",function(){var a=t(this).hasClass("wp-ui-text-highlight"),l=t(this).attr("id"),n=t("#htr_lang_"+l.replace("pll_sync_post[","").replace("]","")).val();if("undefined"==typeof n||0==n||a||confirm(confirm_text)){var e={action:"toggle_"+l,value:a,post_type:t("#post_type").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,e,function(a){var n=wpAjax.parseAjaxResponse(a,"ajax-response");t.each(n.responses,function(){l=l.replace("[","\\[").replace("]","\\]"),t("#"+l).toggleClass("wp-ui-text-highlight").attr("title",this.data).children("span").html(this.data),t('input[name="'+l+'"]').val(!e.value)})})}})});
js/media.js DELETED
@@ -1,6 +0,0 @@
1
- // when clicking on attach link, filters find post list per media language
2
- (function( $ ){
3
- $.ajaxPrefilter(function ( options, originalOptions, jqXHR ) {
4
- options.data = 'pll_post_id=' + $( '#affected' ).val() + '&' + options.data;
5
- });
6
- })( jQuery )
 
 
 
 
 
 
js/media.min.js DELETED
@@ -1 +0,0 @@
1
- !function(a){a.ajaxPrefilter(function(t){t.data="pll_post_id="+a("#affected").val()+"&"+t.data})}(jQuery);
 
js/post.js CHANGED
@@ -1,4 +1,6 @@
1
- // tag suggest in quick edit
 
 
2
  (function( $ ){
3
  $.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
4
  if ( 'string' === typeof options.data && -1 !== options.data.indexOf( 'action=ajax-tag-search' ) && ( lang = $( ':input[name="inline_lang_choice"]' ).val() ) ) {
@@ -7,7 +9,9 @@
7
  });
8
  })( jQuery );
9
 
10
- // quick edit
 
 
11
  (function( $ ) {
12
  $( document ).bind( 'DOMNodeInserted', function( e ) {
13
  var t = $( e.target );
@@ -61,8 +65,10 @@
61
  });
62
  })( jQuery );
63
 
64
- // update rows of translated posts when the language is modified in quick edit
65
- // acts on ajaxSuccess event
 
 
66
  (function( $ ) {
67
  $( document ).ajaxSuccess(function( event, xhr, settings ) {
68
  function update_rows( post_id ) {
@@ -94,7 +100,7 @@
94
  });
95
  }
96
 
97
- if ( 'string' == typeof( settings.data ) ) { // Need to check the type due to Gutenberg sometime sending FormData objects
98
  var data = wpAjax.unserialize( settings.data ); // what were the data sent by the ajax request?
99
  if ( 'undefined' != typeof( data['action'] ) && 'inline-save' == data['action'] ) {
100
  update_rows( data['post_ID'] );
@@ -102,3 +108,67 @@
102
  }
103
  });
104
  })( jQuery );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Tag suggest in quick edit
3
+ */
4
  (function( $ ){
5
  $.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
6
  if ( 'string' === typeof options.data && -1 !== options.data.indexOf( 'action=ajax-tag-search' ) && ( lang = $( ':input[name="inline_lang_choice"]' ).val() ) ) {
9
  });
10
  })( jQuery );
11
 
12
+ /**
13
+ * Quick edit
14
+ */
15
  (function( $ ) {
16
  $( document ).bind( 'DOMNodeInserted', function( e ) {
17
  var t = $( e.target );
65
  });
66
  })( jQuery );
67
 
68
+ /**
69
+ * Update rows of translated posts when the language is modified in quick edit
70
+ * Acts on ajaxSuccess event
71
+ */
72
  (function( $ ) {
73
  $( document ).ajaxSuccess(function( event, xhr, settings ) {
74
  function update_rows( post_id ) {
100
  });
101
  }
102
 
103
+ if ( 'string' == typeof( settings.data ) ) { // Need to check the type due to block editor sometime sending FormData objects
104
  var data = wpAjax.unserialize( settings.data ); // what were the data sent by the ajax request?
105
  if ( 'undefined' != typeof( data['action'] ) && 'inline-save' == data['action'] ) {
106
  update_rows( data['post_ID'] );
108
  }
109
  });
110
  })( jQuery );
111
+
112
+ /**
113
+ * Media list table
114
+ * When clicking on attach link, filters find post list per media language
115
+ */
116
+ (function( $ ){
117
+ $.ajaxPrefilter( function ( options, originalOptions, jqXHR ) {
118
+ if ( 'string' === typeof options.data && -1 !== options.data.indexOf( 'action=find_posts' ) ) {
119
+ options.data = 'pll_post_id=' + $( '#affected' ).val() + '&' + options.data;
120
+ }
121
+ });
122
+ })( jQuery )
123
+
124
+
125
+ /**
126
+ * Bulk translate
127
+ */
128
+ jQuery( document ).ready( function( $ ) {
129
+ var t = this;
130
+
131
+ $( '.editinline' ).click( function(){
132
+ $( '#pll-translate' ).find( '.cancel' ).click(); // Close the form on quick edit
133
+ } );
134
+
135
+ $( '#doaction, #doaction2' ).click( function( e ){
136
+ t.whichBulkButtonId = $( this ).attr( 'id' );
137
+ var n = t.whichBulkButtonId.substr( 2 );
138
+
139
+ if ( 'pll_translate' === $( 'select[name="' + n + '"]' ).val() ) {
140
+ e.preventDefault();
141
+
142
+ if ( typeof inlineEditPost !== 'undefined' ) { // Not available for media.
143
+ inlineEditPost.revert(); // Close Bulk edit and Quick edit if open.
144
+ }
145
+
146
+ $( '#pll-translate td' ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length );
147
+ $( 'table.widefat tbody' ).prepend( $( '#pll-translate' ) ).prepend( '<tr class="hidden"></tr>' ); // The hidden tr allows to keep the background color
148
+ } else {
149
+ $( '#pll-translate' ).find( '.cancel' ).click(); // Close the form on any other bulk action
150
+ }
151
+ } );
152
+
153
+ // Cancel
154
+ $( '#pll-translate' ).on( 'click', '.cancel', function(){
155
+ $( '#pll-translate' ).siblings( '.hidden' ).remove();
156
+ $( '#pll-bulk-translate' ).append( $( '#pll-translate' ) );
157
+
158
+ // Move focus back to the Bulk Action button that was activated.
159
+ $( '#' + t.whichBulkButtonId ).focus();
160
+ } );
161
+
162
+ // Act when pressing enter or esc
163
+ $( '#pll-translate' ).keydown( function( event ){
164
+ if ( 13 === event.keyCode && ! $( event.target ).hasClass( 'cancel' ) ) {
165
+ event.preventDefault();
166
+ $( this ).find( 'input[type=submit]' ).click();
167
+ }
168
+
169
+ if ( 27 === event.keyCode ) {
170
+ event.preventDefault();
171
+ $( this ).find( '.cancel' ).click();
172
+ }
173
+ } );
174
+ } );
js/post.min.js CHANGED
@@ -1 +1 @@
1
- !function(n){n.ajaxPrefilter(function(a){"string"==typeof a.data&&-1!==a.data.indexOf("action=ajax-tag-search")&&(lang=n(':input[name="inline_lang_choice"]').val())&&(a.data="lang="+lang+"&"+a.data)})}(jQuery),function(n){n(document).bind("DOMNodeInserted",function(a){function e(a){"undefined"!=typeof pll_term_languages&&n.each(pll_term_languages,function(e,t){n.each(t,function(t,i){n.each(i,function(i){id="#"+t+"-"+pll_term_languages[e][t][i],a==e?n(id).show():n(id).hide()})})})}function t(a){"undefined"!=typeof pll_page_languages&&n.each(pll_page_languages,function(e,t){n.each(t,function(t){v=n('#post_parent option[value="'+pll_page_languages[e][t]+'"]'),a==e?v.show():v.hide()})})}var i=n(a.target);if("inline-edit"==i.attr("id")){var o=i.prev().attr("id").replace("post-","");if(o>0){var l=i.find(':input[name="inline_lang_choice"]'),p=n("#lang_"+o).html();l.val(p),e(p),t(p),l.change(function(){e(n(this).val()),t(n(this).val())})}}})}(jQuery),function(n){n(document).ajaxSuccess(function(a,e,t){function i(a){var e=new Array;n(".translation_"+a).each(function(){e.push(n(this).parent().parent().attr("id").substring(5))});var t={action:"pll_update_post_rows",post_id:a,translations:e.join(","),post_type:n("input[name='post_type']").val(),screen:n("input[name='screen']").val(),_pll_nonce:n("input[name='_inline_edit']").val()};n.post(ajaxurl,t,function(a){if(a){var e=wpAjax.parseAjaxResponse(a,"ajax-response");n.each(e.responses,function(){"row"==this.what&&n("#post-"+this.supplemental.post_id).replaceWith(this.data)})}})}if("string"==typeof t.data){var o=wpAjax.unserialize(t.data);"undefined"!=typeof o.action&&"inline-save"==o.action&&i(o.post_ID)}})}(jQuery);
1
+ !function(t){t.ajaxPrefilter(function(n){"string"==typeof n.data&&-1!==n.data.indexOf("action=ajax-tag-search")&&(lang=t(':input[name="inline_lang_choice"]').val())&&(n.data="lang="+lang+"&"+n.data)})}(jQuery),function(t){t(document).bind("DOMNodeInserted",function(n){function a(n){"undefined"!=typeof pll_term_languages&&t.each(pll_term_languages,function(a,e){t.each(e,function(e,i){t.each(i,function(i){id="#"+e+"-"+pll_term_languages[a][e][i],n==a?t(id).show():t(id).hide()})})})}function e(n){"undefined"!=typeof pll_page_languages&&t.each(pll_page_languages,function(a,e){t.each(e,function(e){v=t('#post_parent option[value="'+pll_page_languages[a][e]+'"]'),n==a?v.show():v.hide()})})}var i=t(n.target);if("inline-edit"==i.attr("id")){var l=i.prev().attr("id").replace("post-","");if(l>0){var c=i.find(':input[name="inline_lang_choice"]'),s=t("#lang_"+l).html();c.val(s),a(s),e(s),c.change(function(){a(t(this).val()),e(t(this).val())})}}})}(jQuery),function(t){t(document).ajaxSuccess(function(n,a,e){function i(n){var a=new Array;t(".translation_"+n).each(function(){a.push(t(this).parent().parent().attr("id").substring(5))});var e={action:"pll_update_post_rows",post_id:n,translations:a.join(","),post_type:t("input[name='post_type']").val(),screen:t("input[name='screen']").val(),_pll_nonce:t("input[name='_inline_edit']").val()};t.post(ajaxurl,e,function(n){if(n){var a=wpAjax.parseAjaxResponse(n,"ajax-response");t.each(a.responses,function(){"row"==this.what&&t("#post-"+this.supplemental.post_id).replaceWith(this.data)})}})}if("string"==typeof e.data){var l=wpAjax.unserialize(e.data);"undefined"!=typeof l.action&&"inline-save"==l.action&&i(l.post_ID)}})}(jQuery),function(t){t.ajaxPrefilter(function(n){"string"==typeof n.data&&-1!==n.data.indexOf("action=find_posts")&&(n.data="pll_post_id="+t("#affected").val()+"&"+n.data)})}(jQuery),jQuery(document).ready(function(t){var n=this;t(".editinline").click(function(){t("#pll-translate").find(".cancel").click()}),t("#doaction, #doaction2").click(function(a){n.whichBulkButtonId=t(this).attr("id");var e=n.whichBulkButtonId.substr(2);"pll_translate"===t('select[name="'+e+'"]').val()?(a.preventDefault(),"undefined"!=typeof inlineEditPost&&inlineEditPost.revert(),t("#pll-translate td").attr("colspan",t("th:visible, td:visible",".widefat:first thead").length),t("table.widefat tbody").prepend(t("#pll-translate")).prepend('<tr class="hidden"></tr>')):t("#pll-translate").find(".cancel").click()}),t("#pll-translate").on("click",".cancel",function(){t("#pll-translate").siblings(".hidden").remove(),t("#pll-bulk-translate").append(t("#pll-translate")),t("#"+n.whichBulkButtonId).focus()}),t("#pll-translate").keydown(function(n){13!==n.keyCode||t(n.target).hasClass("cancel")||(n.preventDefault(),t(this).find("input[type=submit]").click()),27===n.keyCode&&(n.preventDefault(),t(this).find(".cancel").click())})});
lingotek/lingotek.php CHANGED
@@ -1,9 +1,5 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Don't access directly
5
- };
6
-
7
  /**
8
  * Class to manage Lingotek ads
9
  *
@@ -24,7 +20,7 @@ class PLL_Lingotek {
24
  add_filter( 'pll_settings_tabs', array( $this, 'add_tab' ) );
25
  add_action( 'pll_settings_active_tab_lingotek', array( $this, 'display_tab' ) );
26
 
27
- if ( PLL_SETTINGS && isset( $_GET['page'] ) && 'mlang_lingotek' == $_GET['page'] ) {
28
  add_action( 'admin_print_styles', array( $this, 'print_css' ) );
29
  }
30
 
@@ -103,11 +99,11 @@ class PLL_Lingotek {
103
  printf( '<p>%s</p>', esc_html__( 'Polylang is now fully integrated with Lingotek, a professional translation management system!', 'polylang' ) );
104
 
105
  $this->box(
106
- __( 'Automatically Translate My Site', 'polylang' ),
107
  __( 'Polylang is now fully integrated with Lingotek!', 'polylang' ),
108
  array(
109
  __( 'Access free machine translation for your site for up to 100,000 characters.', 'polylang' ),
110
- __( 'Machine translation is an excellent option if you\'re on a tight budget, looking for near-instant results, and are okay with less-than-perfect quality.', 'polylang' ),
111
  ),
112
  array_intersect_key( $links, array_flip( array( 'activate' ) ) ),
113
  'image01.gif'
@@ -127,19 +123,19 @@ class PLL_Lingotek {
127
  );
128
 
129
  $this->box(
130
- __( 'Professionally Translate My Site', 'polylang' ),
131
- __( 'Do you need to professionally translate your site?', 'polylang' ),
132
  array(
133
  __( 'Start the process of getting a professional translation bid.', 'polylang' ),
134
- __( 'Activate account so Lingotek can get an accurate count of how many words you have on your site and which languages you wish to translate into.', 'polylang' ),
135
- __( 'Once activated click on the request translation bid and a certified translation project manager will contact you to give a no obligations translation bid.', 'polylang' ),
136
  ),
137
  array_intersect_key( $links, array_flip( array( 'activate', 'translation' ) ) ),
138
  'image03.png'
139
  );
140
 
141
  $this->box(
142
- __( 'Need Extra Services?', 'polylang' ),
143
  __( 'Do you need help translating your site?', 'polylang' ),
144
  array(
145
  __( 'Start the process of getting extra services.', 'polylang' ),
@@ -169,7 +165,7 @@ class PLL_Lingotek {
169
  border: 1px solid #ddd;
170
  margin-right: 3px;
171
  margin-bottom: 3px;
172
- height: 630px;
173
  background: #fafafa;
174
  }
175
  .rtl .ltk-feature {
@@ -300,5 +296,3 @@ class PLL_Lingotek {
300
  return '';
301
  }
302
  }
303
-
304
- add_action( 'wp_loaded', array( new PLL_Lingotek(), 'init' ) );
1
  <?php
2
 
 
 
 
 
3
  /**
4
  * Class to manage Lingotek ads
5
  *
20
  add_filter( 'pll_settings_tabs', array( $this, 'add_tab' ) );
21
  add_action( 'pll_settings_active_tab_lingotek', array( $this, 'display_tab' ) );
22
 
23
+ if ( PLL_SETTINGS && isset( $_GET['page'] ) && 'mlang_lingotek' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification
24
  add_action( 'admin_print_styles', array( $this, 'print_css' ) );
25
  }
26
 
99
  printf( '<p>%s</p>', esc_html__( 'Polylang is now fully integrated with Lingotek, a professional translation management system!', 'polylang' ) );
100
 
101
  $this->box(
102
+ __( 'Automatically translate my site', 'polylang' ),
103
  __( 'Polylang is now fully integrated with Lingotek!', 'polylang' ),
104
  array(
105
  __( 'Access free machine translation for your site for up to 100,000 characters.', 'polylang' ),
106
+ __( 'If you\'re on a tight budget, looking for near-instant results, and are okay with less-than-perfect quality, machine translation is an excellent option.', 'polylang' ),
107
  ),
108
  array_intersect_key( $links, array_flip( array( 'activate' ) ) ),
109
  'image01.gif'
123
  );
124
 
125
  $this->box(
126
+ __( 'Professionally translate my site', 'polylang' ),
127
+ __( 'Do you need professional translations for your site?', 'polylang' ),
128
  array(
129
  __( 'Start the process of getting a professional translation bid.', 'polylang' ),
130
+ __( 'When you activate your account, Lingotek will be able to get an accurate count of the number of words in your site. Then tell them which languages you wish to have them translated into.', 'polylang' ),
131
+ __( 'Once activated, click on the "request translation bid" and a certified translation project manager will contact you for a no-obligations translation bid.', 'polylang' ),
132
  ),
133
  array_intersect_key( $links, array_flip( array( 'activate', 'translation' ) ) ),
134
  'image03.png'
135
  );
136
 
137
  $this->box(
138
+ __( 'Need extra services?', 'polylang' ),
139
  __( 'Do you need help translating your site?', 'polylang' ),
140
  array(
141
  __( 'Start the process of getting extra services.', 'polylang' ),
165
  border: 1px solid #ddd;
166
  margin-right: 3px;
167
  margin-bottom: 3px;
168
+ height: 650px;
169
  background: #fafafa;
170
  }
171
  .rtl .ltk-feature {
296
  return '';
297
  }
298
  }
 
 
modules/plugins/as3cf.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A class to manage the integration with WP Offload Media Lite.
5
+ * Version tested: 2.1.1
6
+ *
7
+ * @since 2.6
8
+ */
9
+ class PLL_AS3CF {
10
+ private $is_media_translated;
11
+
12
+ /**
13
+ * Initializes filters and actions.
14
+ *
15
+ * @since 2.6
16
+ */
17
+ public function init() {
18
+ add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ) );
19
+ add_action( 'delete_attachment', array( $this, 'check_translated_media' ), 5 ); // Before Polylang deletes the translations information.
20
+ add_action( 'delete_attachment', array( $this, 'prevent_file_deletion' ), 15 ); // Between Polylang and WP Offload Media.
21
+ }
22
+
23
+ /**
24
+ * Synchronizes post metas
25
+ *
26
+ * @since 2.6
27
+ *
28
+ * @param array $metas List of custom fields names.
29
+ * @return array
30
+ */
31
+ public function copy_post_metas( $metas ) {
32
+ $metas[] = 'amazonS3_info';
33
+ $metas[] = 'as3cf_filesize_total';
34
+ return $metas;
35
+ }
36
+
37
+ /**
38
+ * Checks if the deleted attachment was translated and stores the information.
39
+ *
40
+ * @since 2.6
41
+ *
42
+ * @param int $post_id Id of the attachment being deleted.
43
+ */
44
+ public function check_translated_media( $post_id ) {
45
+ $this->is_media_translated[ $post_id ] = ( count( pll_get_post_translations( $post_id ) ) > 1 );
46
+ }
47
+
48
+ /**
49
+ * Deletes the WP Offload Media information from the attachment being deleted.
50
+ * That way WP Offload Media won't delete the file stored in the cloud.
51
+ * Done after Polylang has deleted the translations informations, to avoid the synchronization of the deletion
52
+ * and of course before WP Offload Media deletes the file, normally at priority 20.
53
+ *
54
+ * @since 2.6
55
+ *
56
+ * @param int $post_id Id of the attachment being deleted.
57
+ */
58
+ public function prevent_file_deletion( $post_id ) {
59
+ if ( ! empty( $this->is_media_translated[ $post_id ] ) ) {
60
+ delete_post_meta( $post_id, 'amazonS3_info' );
61
+ delete_post_meta( $post_id, 'as3cf_filesize_total' );
62
+ }
63
+ }
64
+ }
modules/plugins/cache-compat.php CHANGED
@@ -31,7 +31,7 @@ class PLL_Cache_Compat {
31
  * @since 2.3
32
  */
33
  public function add_cookie_script() {
34
- $domain = ( 2 == PLL()->options['force_lang'] ) ? parse_url( PLL()->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN;
35
  $js = sprintf(
36
  '(function() {
37
  var expirationDate = new Date();
@@ -44,7 +44,7 @@ class PLL_Cache_Compat {
44
  esc_js( COOKIEPATH ),
45
  $domain ? '; domain=' . esc_js( $domain ) : ''
46
  );
47
- echo '<script type="text/javascript">' . $js . '</script>';
48
  }
49
 
50
  /**
31
  * @since 2.3
32
  */
33
  public function add_cookie_script() {
34
+ $domain = ( 2 == PLL()->options['force_lang'] ) ? wp_parse_url( PLL()->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN;
35
  $js = sprintf(
36
  '(function() {
37
  var expirationDate = new Date();
44
  esc_js( COOKIEPATH ),
45
  $domain ? '; domain=' . esc_js( $domain ) : ''
46
  );
47
+ echo '<script type="text/javascript">' . $js . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput
48
  }
49
 
50
  /**
modules/plugins/featured-content.php CHANGED
@@ -9,9 +9,9 @@ class PLL_Featured_Content {
9
  /**
10
  * Constructor
11
  *
12
- * @since 2.4
13
  */
14
- public function __construct() {
15
  add_filter( 'transient_featured_content_ids', array( $this, 'featured_content_ids' ) );
16
  add_filter( 'pll_filter_query_excluded_query_vars', array( $this, 'fix_featured_posts' ) );
17
  add_filter( 'option_featured-content', array( $this, 'option_featured_content' ) );
@@ -55,7 +55,7 @@ class PLL_Featured_Content {
55
  * @return array modified featured posts ids ( include all languages )
56
  */
57
  public function featured_content_ids( $featured_ids ) {
58
- if ( ! $this->is_active() || ! did_action( 'pll_init' ) || false !== $featured_ids ) {
59
  return $featured_ids;
60
  }
61
 
@@ -72,22 +72,25 @@ class PLL_Featured_Content {
72
  // Query for featured posts in all languages
73
  // One query per language to get the correct number of posts per language
74
  foreach ( $tags as $tag ) {
75
- $_ids = get_posts(
76
- array(
77
- 'lang' => 0, // avoid language filters
78
- 'fields' => 'ids',
79
- 'numberposts' => Featured_Content::$max_posts,
80
- 'post_type' => Featured_Content::$post_types,
81
- 'tax_query' => array(
82
- array(
83
- 'taxonomy' => 'post_tag',
84
- 'terms' => (int) $tag,
85
- ),
86
  ),
87
- )
88
  );
89
 
90
- $ids = array_merge( $ids, $_ids );
 
 
 
 
 
 
91
  }
92
 
93
  $ids = array_map( 'absint', $ids );
9
  /**
10
  * Constructor
11
  *
12
+ * @since 2.6
13
  */
14
+ public function init() {
15
  add_filter( 'transient_featured_content_ids', array( $this, 'featured_content_ids' ) );
16
  add_filter( 'pll_filter_query_excluded_query_vars', array( $this, 'fix_featured_posts' ) );
17
  add_filter( 'option_featured-content', array( $this, 'option_featured_content' ) );
55
  * @return array modified featured posts ids ( include all languages )
56
  */
57
  public function featured_content_ids( $featured_ids ) {
58
+ if ( ! $this->is_active() || false !== $featured_ids ) {
59
  return $featured_ids;
60
  }
61
 
72
  // Query for featured posts in all languages
73
  // One query per language to get the correct number of posts per language
74
  foreach ( $tags as $tag ) {
75
+ $args = array(
76
+ 'lang' => 0, // Avoid language filters.
77
+ 'fields' => 'ids',
78
+ 'numberposts' => Featured_Content::$max_posts,
79
+ 'tax_query' => array(
80
+ array(
81
+ 'taxonomy' => 'post_tag',
82
+ 'terms' => (int) $tag,
 
 
 
83
  ),
84
+ ),
85
  );
86
 
87
+ // Available in Jetpack, but not in Twenty Fourteen.
88
+ if ( isset( Featured_Content::$post_types ) ) {
89
+ $args['post_type'] = Featured_Content::$post_types;
90
+ }
91
+
92
+ $_ids = get_posts( $args );
93
+ $ids = array_merge( $ids, $_ids );
94
  }
95
 
96
  $ids = array_map( 'absint', $ids );
modules/plugins/jetpack.php CHANGED
@@ -19,7 +19,7 @@ class PLL_Jetpack {
19
  add_filter( 'jetpack_relatedposts_filter_filters', array( $this, 'jetpack_relatedposts_filter_filters' ), 10, 2 );
20
 
21
  // Jetpack infinite scroll
22
- if ( isset( $_GET['infinity'], $_POST['action'] ) && 'infinite_scroll' == $_POST['action'] ) {
23
  add_filter( 'pll_is_ajax_on_front', '__return_true' );
24
  }
25
  }
@@ -131,7 +131,7 @@ class PLL_Jetpack {
131
  * @return array
132
  */
133
  public function jetpack_infinite_scroll_js_settings( $settings ) {
134
- $settings['history']['host'] = parse_url( pll_home_url(), PHP_URL_HOST ); // Jetpack uses get_option( 'home' )
135
  return $settings;
136
  }
137
  }
19
  add_filter( 'jetpack_relatedposts_filter_filters', array( $this, 'jetpack_relatedposts_filter_filters' ), 10, 2 );
20
 
21
  // Jetpack infinite scroll
22
+ if ( isset( $_GET['infinity'], $_POST['action'] ) && 'infinite_scroll' == $_POST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification
23
  add_filter( 'pll_is_ajax_on_front', '__return_true' );
24
  }
25
  }
131
  * @return array
132
  */
133
  public function jetpack_infinite_scroll_js_settings( $settings ) {
134
+ $settings['history']['host'] = wp_parse_url( pll_home_url(), PHP_URL_HOST ); // Jetpack uses get_option( 'home' )
135
  return $settings;
136
  }
137
  }
modules/plugins/plugins-compat.php CHANGED
@@ -7,7 +7,7 @@
7
  * @since 1.0
8
  */
9
  class PLL_Plugins_Compat {
10
- static protected $instance; // for singleton
11
 
12
  /**
13
  * Constructor
@@ -35,8 +35,8 @@ class PLL_Plugins_Compat {
35
  add_filter( 'option_duplicate_post_taxonomies_blacklist', array( $this, 'duplicate_post_taxonomies_blacklist' ) );
36
 
37
  // Jetpack
38
- $this->jetpack = new PLL_Jetpack(); // Must be loaded before the plugin is active
39
- $this->featured_content = new PLL_Featured_Content();
40
 
41
  // WP Sweep
42
  add_filter( 'wp_sweep_excluded_taxonomies', array( $this, 'wp_sweep_excluded_taxonomies' ) );
@@ -83,7 +83,7 @@ class PLL_Plugins_Compat {
83
  public function plugins_loaded() {
84
  // Yoast SEO
85
  if ( defined( 'WPSEO_VERSION' ) ) {
86
- add_action( 'pll_language_defined', array( $this->wpseo = new PLL_WPSEO(), 'init' ) );
87
  }
88
 
89
  if ( pll_is_cache_active() ) {
@@ -114,6 +114,16 @@ class PLL_Plugins_Compat {
114
  if ( ( defined( 'AC_FILE' ) || defined( 'ACP_FILE' ) ) && class_exists( 'PLL_CPAC' ) ) {
115
  add_action( 'admin_init', array( $this->cpac = new PLL_CPAC(), 'init' ) );
116
  }
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  /**
@@ -153,7 +163,7 @@ class PLL_Plugins_Compat {
153
  load_plugin_textdomain( 'wordpress-importer', false, basename( dirname( $class->getFileName() ) ) . '/languages' );
154
 
155
  $GLOBALS['wp_import'] = new PLL_WP_Import();
156
- register_importer( 'wordpress', 'WordPress', __( 'Import <strong>posts, pages, comments, custom fields, categories, and tags</strong> from a WordPress export file.', 'wordpress-importer' ), array( $GLOBALS['wp_import'], 'dispatch' ) ); // WPCS: spelling ok.
157
  }
158
 
159
  /**
@@ -167,14 +177,14 @@ class PLL_Plugins_Compat {
167
  * @return array
168
  */
169
  public function wp_import_terms( $terms ) {
170
- include PLL_SETTINGS_INC . '/languages.php';
171
 
172
  foreach ( $terms as $key => $term ) {
173
  if ( 'language' === $term['term_taxonomy'] ) {
174
  $description = maybe_unserialize( $term['term_description'] );
175
  if ( empty( $description['flag_code'] ) && isset( $languages[ $description['locale'] ] ) ) {
176
  $description['flag_code'] = $languages[ $description['locale'] ]['flag'];
177
- $terms[ $key ]['term_description'] = serialize( $description );
178
  }
179
  }
180
  }
@@ -214,7 +224,7 @@ class PLL_Plugins_Compat {
214
  */
215
  public function cft_copy( $post_type, $post ) {
216
  global $custom_field_template;
217
- if ( isset( $custom_field_template, $_REQUEST['from_post'], $_REQUEST['new_lang'] ) && ! empty( $post ) ) {
218
  $_REQUEST['post'] = $post->ID;
219
  }
220
  }
@@ -298,23 +308,26 @@ class PLL_Plugins_Compat {
298
  }
299
 
300
  // Don't redirect post previews
301
- if ( isset( $_GET['preview'] ) && 'true' === $_GET['preview'] ) {
302
  return;
303
  }
304
 
305
  // Don't redirect theme customizer
306
- if ( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && 'on' === $_POST['customize'] ) {
307
  return;
308
  }
309
 
310
  // If we can't associate the requested domain to a language, redirect to the default domain
 
 
 
311
  $hosts = PLL()->links_model->get_hosts();
312
- $lang = array_search( $_SERVER['HTTP_HOST'], $hosts );
313
 
314
  if ( empty( $lang ) ) {
315
- $status = get_site_option( 'dm_301_redirect' ) ? '301' : '302'; // Honor status redirect option
316
- $redirect = ( is_ssl() ? 'https://' : 'http://' ) . $hosts[ $options['default_lang'] ] . $_SERVER['REQUEST_URI'];
317
- wp_redirect( $redirect, $status );
318
  exit;
319
  }
320
  } else {
7
  * @since 1.0
8
  */
9
  class PLL_Plugins_Compat {
10
+ protected static $instance; // for singleton
11
 
12
  /**
13
  * Constructor
35
  add_filter( 'option_duplicate_post_taxonomies_blacklist', array( $this, 'duplicate_post_taxonomies_blacklist' ) );
36
 
37
  // Jetpack
38
+ $this->jetpack = new PLL_Jetpack(); // Must be loaded before the plugin is active
39
+ add_action( 'pll_init', array( $this->featured_content = new PLL_Featured_Content(), 'init' ) );
40
 
41
  // WP Sweep
42
  add_filter( 'wp_sweep_excluded_taxonomies', array( $this, 'wp_sweep_excluded_taxonomies' ) );
83
  public function plugins_loaded() {
84
  // Yoast SEO
85
  if ( defined( 'WPSEO_VERSION' ) ) {
86
+ add_action( 'pll_init', array( $this->wpseo = new PLL_WPSEO(), 'init' ) );
87
  }
88
 
89
  if ( pll_is_cache_active() ) {
114
  if ( ( defined( 'AC_FILE' ) || defined( 'ACP_FILE' ) ) && class_exists( 'PLL_CPAC' ) ) {
115
  add_action( 'admin_init', array( $this->cpac = new PLL_CPAC(), 'init' ) );
116
  }
117
+
118
+ // WP Offload Media Lite
119
+ if ( function_exists( 'as3cf_init' ) && class_exists( 'PLL_AS3CF' ) ) {
120
+ add_action( 'pll_init', array( $this->as3cf = new PLL_AS3CF(), 'init' ) );
121
+ }
122
+
123
+ // Content Blocks (Custom Post Widget)
124
+ if ( function_exists( 'custom_post_widget_plugin_init' ) && class_exists( 'PLL_Content_Blocks' ) ) {
125
+ add_action( 'pll_init', array( $this->content_blocks = new PLL_Content_Blocks(), 'init' ) );
126
+ }
127
  }
128
 
129
  /**
163
  load_plugin_textdomain( 'wordpress-importer', false, basename( dirname( $class->getFileName() ) ) . '/languages' );
164
 
165
  $GLOBALS['wp_import'] = new PLL_WP_Import();
166
+ register_importer( 'wordpress', 'WordPress', __( 'Import <strong>posts, pages, comments, custom fields, categories, and tags</strong> from a WordPress export file.', 'polylang' ), array( $GLOBALS['wp_import'], 'dispatch' ) ); // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled
167
  }
168
 
169
  /**
177
  * @return array
178
  */
179
  public function wp_import_terms( $terms ) {
180
+ $languages = include PLL_SETTINGS_INC . '/languages.php';
181
 
182
  foreach ( $terms as $key => $term ) {
183
  if ( 'language' === $term['term_taxonomy'] ) {
184
  $description = maybe_unserialize( $term['term_description'] );
185
  if ( empty( $description['flag_code'] ) && isset( $languages[ $description['locale'] ] ) ) {
186
  $description['flag_code'] = $languages[ $description['locale'] ]['flag'];
187
+ $terms[ $key ]['term_description'] = maybe_serialize( $description );
188
  }
189
  }
190
  }
224
  */
225
  public function cft_copy( $post_type, $post ) {
226
  global $custom_field_template;
227
+ if ( isset( $custom_field_template, $_REQUEST['from_post'], $_REQUEST['new_lang'] ) && ! empty( $post ) ) { // phpcs:ignore WordPress.Security.NonceVerification
228
  $_REQUEST['post'] = $post->ID;
229
  }
230
  }
308
  }
309
 
310
  // Don't redirect post previews
311
+ if ( isset( $_GET['preview'] ) && 'true' === $_GET['preview'] ) { // phpcs:ignore WordPress.Security.NonceVerification
312
  return;
313
  }
314
 
315
  // Don't redirect theme customizer
316
+ if ( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && 'on' === $_POST['customize'] ) { // phpcs:ignore WordPress.Security.NonceVerification
317
  return;
318
  }
319
 
320
  // If we can't associate the requested domain to a language, redirect to the default domain
321
+ $requested_url = pll_get_requested_url();
322
+ $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST );
323
+
324
  $hosts = PLL()->links_model->get_hosts();
325
+ $lang = array_search( $requested_host, $hosts );
326
 
327
  if ( empty( $lang ) ) {
328
+ $status = get_site_option( 'dm_301_redirect' ) ? '301' : '302'; // Honor status redirect option
329
+ $redirect = str_replace( '://' . $requested_host, '://' . $hosts[ $options['default_lang'] ], $requested_url );
330
+ wp_safe_redirect( $redirect, $status );
331
  exit;
332
  }
333
  } else {
modules/plugins/wp-import.php CHANGED
@@ -89,7 +89,7 @@ class PLL_WP_Import extends WP_Import {
89
  $lang_id = (int) substr( $post['post_title'], 12 );
90
 
91
  if ( ! empty( $this->processed_terms[ $lang_id ] ) ) {
92
- if ( $strings = unserialize( $post['post_content'] ) ) {
93
  $mo = new PLL_MO();
94
  $mo->import_from_db( $this->processed_terms[ $lang_id ] );
95
  foreach ( $strings as $msg ) {
@@ -114,7 +114,7 @@ class PLL_WP_Import extends WP_Import {
114
  global $wpdb;
115
 
116
  foreach ( $terms as $term ) {
117
- $translations = unserialize( $term['term_description'] );
118
  foreach ( $translations as $slug => $old_id ) {
119
  if ( $old_id && ! empty( $this->processed_terms[ $old_id ] ) && $lang = PLL()->model->get_language( $slug ) ) {
120
  // Language relationship
@@ -162,7 +162,7 @@ class PLL_WP_Import extends WP_Import {
162
  global $wpdb;
163
 
164
  foreach ( $terms as $term ) {
165
- $translations = unserialize( $term['term_description'] );
166
  $new_translations = array();
167
 
168
  foreach ( $translations as $slug => $old_id ) {
@@ -172,7 +172,7 @@ class PLL_WP_Import extends WP_Import {
172
  }
173
 
174
  if ( ! empty( $new_translations ) ) {
175
- $u['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $this->processed_terms[ $term['term_id'] ], serialize( $new_translations ) );
176
  $u['in'][] = (int) $this->processed_terms[ $term['term_id'] ];
177
  }
178
  }
89
  $lang_id = (int) substr( $post['post_title'], 12 );
90
 
91
  if ( ! empty( $this->processed_terms[ $lang_id ] ) ) {
92
+ if ( $strings = maybe_unserialize( $post['post_content'] ) ) {
93
  $mo = new PLL_MO();
94
  $mo->import_from_db( $this->processed_terms[ $lang_id ] );
95
  foreach ( $strings as $msg ) {
114
  global $wpdb;
115
 
116
  foreach ( $terms as $term ) {
117
+ $translations = maybe_unserialize( $term['term_description'] );
118
  foreach ( $translations as $slug => $old_id ) {
119
  if ( $old_id && ! empty( $this->processed_terms[ $old_id ] ) && $lang = PLL()->model->get_language( $slug ) ) {
120
  // Language relationship
162
  global $wpdb;
163
 
164
  foreach ( $terms as $term ) {
165
+ $translations = maybe_unserialize( $term['term_description'] );
166
  $new_translations = array();
167
 
168
  foreach ( $translations as $slug => $old_id ) {
172
  }
173
 
174
  if ( ! empty( $new_translations ) ) {
175
+ $u['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $this->processed_terms[ $term['term_id'] ], maybe_serialize( $new_translations ) );
176
  $u['in'][] = (int) $this->processed_terms[ $term['term_id'] ];
177
  }
178
  }
modules/plugins/wpseo.php CHANGED
@@ -12,23 +12,9 @@ class PLL_WPSEO {
12
  * @since 1.6.4
13
  */
14
  public function init() {
15
- if ( ! defined( 'WPSEO_VERSION' ) ) {
16
- return;
17
- }
18
-
19
  if ( PLL() instanceof PLL_Frontend ) {
20
  add_filter( 'option_wpseo_titles', array( $this, 'wpseo_translate_titles' ) );
21
 
22
- // Reloads options once the language has been defined to enable translations
23
- // Useful only when the language is set from content
24
- if ( version_compare( WPSEO_VERSION, '7.0', '<' ) && did_action( 'wp_loaded' ) ) {
25
- $wpseo_front = WPSEO_Frontend::get_instance();
26
- $options = WPSEO_Options::get_option_names();
27
- foreach ( $options as $opt ) {
28
- $wpseo_front->options = array_merge( $wpseo_front->options, (array) get_option( $opt ) );
29
- }
30
- }
31
-
32
  // Filters sitemap queries to remove inactive language or to get
33
  // one sitemap per language when using multiple domains or subdomains
34
  // because WPSEO does not accept several domains or subdomains in one sitemap
@@ -43,13 +29,10 @@ class PLL_WPSEO {
43
  } else {
44
  // Get all terms in all languages when the language is set from the content or directory name
45
  add_filter( 'get_terms_args', array( $this, 'wpseo_remove_terms_filter' ) );
46
-
47
- // Add the homepages for all languages to the sitemap when the front page displays posts
48
- if ( ! get_option( 'page_on_front' ) ) {
49
- add_filter( 'wpseo_sitemap_post_content', array( $this, 'add_language_home_urls' ) );
50
- }
51
  }
52
 
 
 
53
  add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) );
54
  add_action( 'wpseo_opengraph', array( $this, 'wpseo_ogp' ), 2 );
55
  add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) );
@@ -144,9 +127,11 @@ class PLL_WPSEO {
144
  * @return $url
145
  */
146
  public function wpseo_home_url( $url, $path ) {
147
- $uri = empty( $path ) ? ltrim( $_SERVER['REQUEST_URI'], '/' ) : $path;
 
 
148
 
149
- if ( 'sitemap_index.xml' === $uri || preg_match( '#([^/]+?)-sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $uri ) ) {
150
  $url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );
151
  }
152
 
@@ -222,30 +207,55 @@ class PLL_WPSEO {
222
  }
223
 
224
  /**
225
- * Adds the home urls for all (active) languages to the sitemap
226
  *
227
- * @since 1.9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  *
229
  * @param string $str additional urls to sitemap post
230
  * @return string
231
  */
232
- public function add_language_home_urls( $str ) {
233
  global $wpseo_sitemaps;
234
- $renderer = version_compare( WPSEO_VERSION, '3.2', '<' ) ? $wpseo_sitemaps : $wpseo_sitemaps->renderer;
235
 
236
- $languages = wp_list_pluck( wp_list_filter( PLL()->model->get_languages_list(), array( 'active' => false ), 'NOT' ), 'slug' );
 
 
 
 
 
 
 
 
 
 
237
 
238
  foreach ( $languages as $lang ) {
239
- if ( empty( PLL()->options['hide_default'] ) || pll_default_language() !== $lang ) {
240
- $str .= $renderer->sitemap_url(
241
- array(
242
- 'loc' => pll_home_url( $lang ),
243
- 'pri' => 1,
244
- 'chf' => apply_filters( 'wpseo_sitemap_homepage_change_freq', 'daily', pll_home_url( $lang ) ),
245
- )
246
- );
247
- }
248
  }
 
249
  return $str;
250
  }
251
 
12
  * @since 1.6.4
13
  */
14
  public function init() {
 
 
 
 
15
  if ( PLL() instanceof PLL_Frontend ) {
16
  add_filter( 'option_wpseo_titles', array( $this, 'wpseo_translate_titles' ) );
17
 
 
 
 
 
 
 
 
 
 
 
18
  // Filters sitemap queries to remove inactive language or to get
19
  // one sitemap per language when using multiple domains or subdomains
20
  // because WPSEO does not accept several domains or subdomains in one sitemap
29
  } else {
30
  // Get all terms in all languages when the language is set from the content or directory name
31
  add_filter( 'get_terms_args', array( $this, 'wpseo_remove_terms_filter' ) );
 
 
 
 
 
32
  }
33
 
34
+ add_action( 'pre_get_posts', array( $this, 'before_sitemap' ), 0 ); // Needs to be fired before WPSEO_Sitemaps::redirect()
35
+
36
  add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) );
37
  add_action( 'wpseo_opengraph', array( $this, 'wpseo_ogp' ), 2 );
38
  add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) );
127
  * @return $url
128
  */
129
  public function wpseo_home_url( $url, $path ) {
130
+ if ( empty( $path ) ) {
131
+ $path = ltrim( wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
132
+ }
133
 
134
+ if ( 'sitemap_index.xml' === $path || preg_match( '#([^/]+?)-sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $path ) ) {
135
  $url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );
136
  }
137
 
207
  }
208
 
209
  /**
210
+ * Add filters before the sitemap is evaluated and outputed
211
  *
212
+ * @since 2.6
213
+ */
214
+ public function before_sitemap() {
215
+ $type = get_query_var( 'sitemap' );
216
+
217
+ // Add the post post type archives in all languages to the sitemap
218
+ // Add the homepages for all languages to the sitemap when the front page displays posts
219
+ if ( $type && pll_is_translated_post_type( $type ) && ( 'post' !== $type || ( PLL()->options['force_lang'] < 2 && ! get_option( 'page_on_front' ) ) ) ) {
220
+ add_filter( "wpseo_sitemap_{$type}_content", array( $this, 'add_post_type_archive' ) );
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Adds the home and post type archives urls for all (active) languages to the sitemap
226
+ *
227
+ * @since 2.6
228
  *
229
  * @param string $str additional urls to sitemap post
230
  * @return string
231
  */
232
+ public function add_post_type_archive( $str ) {
233
  global $wpseo_sitemaps;
 
234
 
235
+ $post_type = substr( substr( current_filter(), 14 ), 0, -8 );
236
+
237
+ $languages = wp_list_filter( PLL()->model->get_languages_list(), array( 'active' => false ), 'NOT' );
238
+
239
+ if ( 'post' !== $post_type ) {
240
+ // The post type archive in the current language is already added by WPSEO
241
+ $languages = wp_list_filter( PLL()->model->get_languages_list(), array( 'slug' => pll_current_language() ), 'NOT' );
242
+ } elseif ( ! empty( PLL()->options['hide_default'] ) ) {
243
+ // The home url is of course already added by WPSEO
244
+ $languages = wp_list_filter( PLL()->model->get_languages_list(), array( 'slug' => pll_default_language() ), 'NOT' );
245
+ }
246
 
247
  foreach ( $languages as $lang ) {
248
+ $link = 'post' === $post_type ? pll_home_url( $lang->slug ) : PLL()->links_model->switch_language_in_link( get_post_type_archive_link( $post_type ), $lang );
249
+ $str .= $wpseo_sitemaps->renderer->sitemap_url(
250
+ array(
251
+ 'loc' => $link,
252
+ 'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ),
253
+ 'pri' => 1,
254
+ 'chf' => 'daily',
255
+ )
256
+ );
257
  }
258
+
259
  return $str;
260
  }
261
 
modules/share-slug/settings-share-slug.php CHANGED
@@ -70,10 +70,10 @@ class PLL_Settings_Share_Slug extends PLL_Settings_Module {
70
  $( "input[name='force_lang']" ).change( function() {
71
  var value = $( this ).val();
72
  if ( value > 0 ) {
73
- $( "#pll-module-share-slugs" ).removeClass( "inactive" ).addClass( "active" ).children( "td" ).children( ".row-actions" ).html( '<?php echo $activated; ?>' );
74
  }
75
  else {
76
- $( "#pll-module-share-slugs" ).removeClass( "active" ).addClass( "inactive" ).children( "td" ).children( ".row-actions" ).html( '<?php echo $deactivated; ?>' );
77
  }
78
  } );
79
  } )( jQuery );
70
  $( "input[name='force_lang']" ).change( function() {
71
  var value = $( this ).val();
72
  if ( value > 0 ) {
73
+ $( "#pll-module-share-slugs" ).removeClass( "inactive" ).addClass( "active" ).children( "td" ).children( ".row-actions" ).html( '<?php echo $activated; // phpcs:ignore WordPress.Security.EscapeOutput ?>' );
74
  }
75
  else {
76
+ $( "#pll-module-share-slugs" ).removeClass( "active" ).addClass( "inactive" ).children( "td" ).children( ".row-actions" ).html( '<?php echo $deactivated; // phpcs:ignore WordPress.Security.EscapeOutput ?>' );
77
  }
78
  } );
79
  } )( jQuery );
modules/sync/admin-sync.php CHANGED
@@ -164,7 +164,7 @@ class PLL_Admin_Sync extends PLL_Sync {
164
  // Sticky posts
165
  if ( in_array( 'sticky_posts', $this->options['sync'] ) ) {
166
  $stickies = get_option( 'sticky_posts' );
167
- if ( isset( $_REQUEST['sticky'] ) && 'sticky' === $_REQUEST['sticky'] ) {
168
  $stickies = array_merge( $stickies, array_values( $translations ) );
169
  } else {
170
  $stickies = array_diff( $stickies, array_values( $translations ) );
@@ -189,23 +189,31 @@ class PLL_Admin_Sync extends PLL_Sync {
189
 
190
  if ( is_object( $this->$obj ) && method_exists( $this->$obj, 'copy' ) ) {
191
  if ( WP_DEBUG ) {
192
- $debug = debug_backtrace();
193
  $i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func
194
 
195
- trigger_error(
196
  sprintf(
197
  '%1$s was called incorrectly in %3$s on line %4$s: the call to PLL()->sync->%1$s() has been deprecated in Polylang 2.3, use PLL()->sync->%2$s->copy() instead.' . "\nError handler",
198
- $func,
199
- $obj,
200
- $debug[ $i ]['file'],
201
- $debug[ $i ]['line']
202
  )
203
  );
204
  }
205
  return call_user_func_array( array( $this->$obj, 'copy' ), $args );
206
  }
207
 
208
- $debug = debug_backtrace();
209
- trigger_error( sprintf( 'Call to undefined function PLL()->sync->%1$s() in %2$s on line %3$s' . "\nError handler", $func, $debug[0]['file'], $debug[0]['line'] ), E_USER_ERROR );
 
 
 
 
 
 
 
 
210
  }
211
  }
164
  // Sticky posts
165
  if ( in_array( 'sticky_posts', $this->options['sync'] ) ) {
166
  $stickies = get_option( 'sticky_posts' );
167
+ if ( isset( $_REQUEST['sticky'] ) && 'sticky' === $_REQUEST['sticky'] ) { // phpcs:ignore WordPress.Security.NonceVerification
168
  $stickies = array_merge( $stickies, array_values( $translations ) );
169
  } else {
170
  $stickies = array_diff( $stickies, array_values( $translations ) );
189
 
190
  if ( is_object( $this->$obj ) && method_exists( $this->$obj, 'copy' ) ) {
191
  if ( WP_DEBUG ) {
192
+ $debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
193
  $i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func
194
 
195
+ trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
196
  sprintf(
197
  '%1$s was called incorrectly in %3$s on line %4$s: the call to PLL()->sync->%1$s() has been deprecated in Polylang 2.3, use PLL()->sync->%2$s->copy() instead.' . "\nError handler",
198
+ esc_html( $func ),
199
+ esc_html( $obj ),
200
+ esc_html( $debug[ $i ]['file'] ),
201
+ absint( $debug[ $i ]['line'] )
202
  )
203
  );
204
  }
205
  return call_user_func_array( array( $this->$obj, 'copy' ), $args );
206
  }
207
 
208
+ $debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
209
+ trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
210
+ sprintf(
211
+ 'Call to undefined function PLL()->sync->%1$s() in %2$s on line %3$s' . "\nError handler",
212
+ esc_html( $func ),
213
+ esc_html( $debug[0]['file'] ),
214
+ absint( $debug[0]['line'] )
215
+ ),
216
+ E_USER_ERROR
217
+ );
218
  }
219
  }
modules/sync/settings-sync.php CHANGED
@@ -48,7 +48,7 @@ class PLL_Settings_Sync extends PLL_Settings_Module {
48
  printf(
49
  '<li><label><input name="sync[%s]" type="checkbox" value="1" %s /> %s</label></li>',
50
  esc_attr( $key ),
51
- in_array( $key, $this->options['sync'] ) ? 'checked="checked"' : '',
52
  esc_html( $str )
53
  );
54
  }
48
  printf(
49
  '<li><label><input name="sync[%s]" type="checkbox" value="1" %s /> %s</label></li>',
50
  esc_attr( $key ),
51
+ checked( in_array( $key, $this->options['sync'] ), true, false ),
52
  esc_html( $str )
53
  );
54
  }
modules/sync/sync-metas.php CHANGED
@@ -19,6 +19,10 @@ abstract class PLL_Sync_Metas {
19
  public function __construct( &$polylang ) {
20
  $this->model = &$polylang->model;
21
 
 
 
 
 
22
  $this->add_all_meta_actions();
23
 
24
  add_action( "pll_save_{$this->meta_type}", array( $this, 'save_object' ), 10, 3 );
@@ -126,6 +130,32 @@ abstract class PLL_Sync_Metas {
126
  return array_unique( apply_filters( "pll_copy_{$this->meta_type}_metas", array(), $sync, $from, $to, $lang ) );
127
  }
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  /**
130
  * Synchronize added metas across translations
131
  *
@@ -148,7 +178,7 @@ abstract class PLL_Sync_Metas {
148
  $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
149
  if ( in_array( $meta_key, $to_copy ) ) {
150
  $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
151
- add_metadata( $this->meta_type, $tr_id, $meta_key, $meta_value );
152
  }
153
  }
154
  }
@@ -194,22 +224,22 @@ abstract class PLL_Sync_Metas {
194
  $avoid_recursion = true;
195
  $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) );
196
 
197
- $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
198
 
199
- foreach ( $tr_ids as $lang => $tr_id ) {
200
- if ( $tr_id != $id ) {
201
- $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
202
- if ( in_array( $meta_key, $to_copy ) ) {
203
- $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
204
- $prev_meta = get_metadata_by_mid( $this->meta_type, $mid );
205
  if ( empty( $this->prev_value[ $hash ] ) || $this->prev_value[ $hash ] === $prev_meta->meta_value ) {
206
  $prev_value = $this->maybe_translate_value( $prev_meta->meta_value, $meta_key, $id, $tr_id, $lang );
207
- $this->remove_add_meta_action(); // We don't want to sync back the new metas
208
- update_metadata( $this->meta_type, $tr_id, $meta_key, $meta_value, $prev_value );
209
- $this->restore_add_meta_action();
210
  }
211
  }
212
  }
 
213
  }
214
 
215
  unset( $this->prev_value[ $hash ] );
@@ -257,7 +287,7 @@ abstract class PLL_Sync_Metas {
257
  if ( '' !== $value && null !== $value && false !== $value ) { // Same test as WP
258
  $value = $this->maybe_translate_value( $value, $key, $id, $tr_id, $lang );
259
  }
260
- delete_metadata( $this->meta_type, $tr_id, $key, $value );
261
  }
262
  }
263
  }
@@ -298,7 +328,7 @@ abstract class PLL_Sync_Metas {
298
  $value = reset( $metas[ $key ] );
299
  $value = maybe_unserialize( $value );
300
  $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
301
- update_metadata( $this->meta_type, $to, $key, $to_value );
302
  } else {
303
  // Multiple custom fields, either in the source or the target
304
  if ( ! empty( $tr_metas[ $key ] ) ) {
@@ -309,7 +339,7 @@ abstract class PLL_Sync_Metas {
309
  foreach ( $metas[ $key ] as $value ) {
310
  $value = maybe_unserialize( $value );
311
  $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
312
- add_metadata( $this->meta_type, $to, $key, $to_value );
313
  }
314
  }
315
  }
19
  public function __construct( &$polylang ) {
20
  $this->model = &$polylang->model;
21
 
22
+ add_filter( "add_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
23
+ add_filter( "update_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
24
+ add_filter( "delete_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
25
+
26
  $this->add_all_meta_actions();
27
 
28
  add_action( "pll_save_{$this->meta_type}", array( $this, 'save_object' ), 10, 3 );
130
  return array_unique( apply_filters( "pll_copy_{$this->meta_type}_metas", array(), $sync, $from, $to, $lang ) );
131
  }
132
 
133
+ /**
134
+ * Disallow modifying synchronized meta if the current user can not modify translations
135
+ *
136
+ * @since 2.6
137
+ *
138
+ * @param null|bool $check Whether to allow adding/updating/deleting metadata.
139
+ * @param int $id Object ID.
140
+ * @param string $meta_key Meta key.
141
+ * @return null|bool
142
+ */
143
+ public function can_synchronize_metadata( $check, $id, $meta_key ) {
144
+ if ( ! $this->model->{$this->meta_type}->current_user_can_synchronize( $id ) ) {
145
+ $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
146
+
147
+ foreach ( $tr_ids as $lang => $tr_id ) {
148
+ if ( $tr_id != $id ) {
149
+ $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
150
+ if ( in_array( $meta_key, $to_copy ) ) {
151
+ return false;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ return $check;
157
+ }
158
+
159
  /**
160
  * Synchronize added metas across translations
161
  *
178
  $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
179
  if ( in_array( $meta_key, $to_copy ) ) {
180
  $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
181
+ add_metadata( $this->meta_type, $tr_id, $meta_key, wp_slash( $meta_value ) );
182
  }
183
  }
184
  }
224
  $avoid_recursion = true;
225
  $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) );
226
 
227
+ $prev_meta = get_metadata_by_mid( $this->meta_type, $mid );
228
 
229
+ if ( $prev_meta ) {
230
+ $this->remove_add_meta_action(); // We don't want to sync back the new metas
231
+ $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
232
+
233
+ foreach ( $tr_ids as $lang => $tr_id ) {
234
+ if ( $tr_id != $id && in_array( $meta_key, $this->get_metas_to_copy( $id, $tr_id, $lang, true ) ) ) {
235
  if ( empty( $this->prev_value[ $hash ] ) || $this->prev_value[ $hash ] === $prev_meta->meta_value ) {
236
  $prev_value = $this->maybe_translate_value( $prev_meta->meta_value, $meta_key, $id, $tr_id, $lang );
237
+ $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
238
+ update_metadata( $this->meta_type, $tr_id, $meta_key, wp_slash( $meta_value ), $prev_value );
 
239
  }
240
  }
241
  }
242
+ $this->restore_add_meta_action();
243
  }
244
 
245
  unset( $this->prev_value[ $hash ] );
287
  if ( '' !== $value && null !== $value && false !== $value ) { // Same test as WP
288
  $value = $this->maybe_translate_value( $value, $key, $id, $tr_id, $lang );
289
  }
290
+ delete_metadata( $this->meta_type, $tr_id, $key, wp_slash( $value ) );
291
  }
292
  }
293
  }
328
  $value = reset( $metas[ $key ] );
329
  $value = maybe_unserialize( $value );
330
  $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
331
+ update_metadata( $this->meta_type, $to, $key, wp_slash( $to_value ) );
332
  } else {
333
  // Multiple custom fields, either in the source or the target
334
  if ( ! empty( $tr_metas[ $key ] ) ) {
339
  foreach ( $metas[ $key ] as $value ) {
340
  $value = maybe_unserialize( $value );
341
  $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
342
+ add_metadata( $this->meta_type, $to, $key, wp_slash( $to_value ) );
343
  }
344
  }
345
  }
modules/sync/sync-post-metas.php CHANGED
@@ -57,9 +57,11 @@ class PLL_Sync_Post_Metas extends PLL_Sync_Metas {
57
  }
58
  }
59
 
60
- // Random header image
61
  if ( $this->options['media_support'] ) {
62
- $keys[] = '_wp_attachment_is_custom_header';
 
 
 
63
  }
64
 
65
  /** This filter is documented in modules/sync/sync-metas.php */
57
  }
58
  }
59
 
 
60
  if ( $this->options['media_support'] ) {
61
+ $keys[] = '_wp_attached_file';
62
+ $keys[] = '_wp_attachment_metadata';
63
+ $keys[] = '_wp_attachment_backup_sizes';
64
+ $keys[] = '_wp_attachment_is_custom_header'; // Random header image
65
  }
66
 
67
  /** This filter is documented in modules/sync/sync-metas.php */
modules/sync/sync-tax.php CHANGED
@@ -100,6 +100,41 @@ class PLL_Sync_Tax {
100
  return $terms; // Empty $terms or untranslated taxonomy
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  /**
104
  * When assigning terms to a post, assign translated terms to the translated posts (synchronisation)
105
  *
@@ -123,24 +158,17 @@ class PLL_Sync_Tax {
123
 
124
  foreach ( $tr_ids as $lang => $tr_id ) {
125
  if ( $tr_id !== $object_id ) {
126
- $to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang );
127
-
128
- if ( in_array( $taxonomy, $to_copy ) ) {
129
- $newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang );
130
-
131
- // For some reasons, the user may have untranslated terms in the translation. Don't forget them.
132
- if ( $this->model->is_translated_taxonomy( $taxonomy ) ) {
133
- $tr_terms = get_the_terms( $tr_id, $taxonomy );
134
- if ( is_array( $tr_terms ) ) {
135
- foreach ( $tr_terms as $term ) {
136
- if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) {
137
- $newterms[] = (int) $term->term_id;
138
- }
139
- }
140
- }
141
  }
142
-
143
- wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append );
144
  }
145
  }
146
  }
@@ -226,7 +254,9 @@ class PLL_Sync_Tax {
226
  $posts = array_unique( $posts );
227
 
228
  foreach ( $posts as $post_id ) {
229
- wp_set_object_terms( $post_id, $term_id, $taxonomy, true );
 
 
230
  }
231
  }
232
  }
100
  return $terms; // Empty $terms or untranslated taxonomy
101
  }
102
 
103
+ /**
104
+ * Maybe copy taxonomy terms from one post to the other
105
+ *
106
+ * @since 2.6
107
+ *
108
+ * @param int $object_id Source object ID.
109
+ * @param int $tr_id Target object ID.
110
+ * @param string $lang Target language.
111
+ * @param array $terms An array of object terms.
112
+ * @param string $taxonomy Taxonomy slug.
113
+ * @param bool $append Whether to append new terms to the old terms.
114
+ */
115
+ protected function copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ) {
116
+ $to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang );
117
+
118
+ if ( in_array( $taxonomy, $to_copy ) ) {
119
+ $newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang );
120
+
121
+ // For some reasons, the user may have untranslated terms in the translation. Don't forget them.
122
+ if ( $this->model->is_translated_taxonomy( $taxonomy ) ) {
123
+ $tr_terms = get_the_terms( $tr_id, $taxonomy );
124
+ if ( is_array( $tr_terms ) ) {
125
+ foreach ( $tr_terms as $term ) {
126
+ if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) {
127
+ $newterms[] = (int) $term->term_id;
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append );
134
+ }
135
+
136
+ }
137
+
138
  /**
139
  * When assigning terms to a post, assign translated terms to the translated posts (synchronisation)
140
  *
158
 
159
  foreach ( $tr_ids as $lang => $tr_id ) {
160
  if ( $tr_id !== $object_id ) {
161
+ if ( $this->model->post->current_user_can_synchronize( $object_id ) ) {
162
+ $this->copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append );
163
+ } else {
164
+ // No permission to synchronize, so let's synchronize in reverse order
165
+ $orig_lang = array_search( $object_id, $tr_ids );
166
+ $tr_terms = (array) get_the_terms( $tr_id, $taxonomy );
167
+ if ( is_array( $tr_terms ) ) {
168
+ $tr_terms = wp_list_pluck( $tr_terms, 'term_id' );
169
+ $this->copy_object_terms( $tr_id, $object_id, $orig_lang, $tr_terms, $taxonomy, $append );
 
 
 
 
 
 
170
  }
171
+ break;
 
172
  }
173
  }
174
  }
254
  $posts = array_unique( $posts );
255
 
256
  foreach ( $posts as $post_id ) {
257
+ if ( current_user_can( 'assign_term', $term_id ) ) {
258
+ wp_set_object_terms( $post_id, $term_id, $taxonomy, true );
259
+ }
260
  }
261
  }
262
  }
modules/sync/sync.php CHANGED
@@ -23,6 +23,9 @@ class PLL_Sync {
23
  $this->post_metas = new PLL_Sync_Post_Metas( $polylang );
24
  $this->term_metas = new PLL_Sync_Term_Metas( $polylang );
25
 
 
 
 
26
  add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
27
  add_action( 'created_term', array( $this, 'sync_term_parent' ), 10, 3 );
28
  add_action( 'edited_term', array( $this, 'sync_term_parent' ), 10, 3 );
@@ -67,6 +70,52 @@ class PLL_Sync {
67
  return $postarr;
68
  }
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  /**
71
  * Synchronizes post fields in translations
72
  *
@@ -79,29 +128,31 @@ class PLL_Sync {
79
  public function pll_save_post( $post_id, $post, $translations ) {
80
  global $wpdb;
81
 
82
- $postarr = $this->get_fields_to_sync( $post );
 
83
 
84
- if ( ! empty( $postarr ) ) {
85
- foreach ( $translations as $lang => $tr_id ) {
86
- if ( ! $tr_id || $tr_id === $post_id ) {
87
- continue;
88
- }
89
 
90
- $tr_arr = $postarr;
91
- unset( $tr_arr['post_parent'] );
92
 
93
- // Do not udpate the translation parent if the user set a parent with no translation
94
- if ( isset( $postarr['post_parent'] ) ) {
95
- $post_parent = $postarr['post_parent'] ? $this->model->post->get_translation( $postarr['post_parent'], $lang ) : 0;
96
- if ( ! ( $postarr['post_parent'] && ! $post_parent ) ) {
97
- $tr_arr['post_parent'] = $post_parent;
 
98
  }
99
- }
100
 
101
- // Update all the row at once
102
- // Don't use wp_update_post to avoid infinite loop
103
- $wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) );
104
- clean_post_cache( $tr_id );
 
105
  }
106
  }
107
  }
23
  $this->post_metas = new PLL_Sync_Post_Metas( $polylang );
24
  $this->term_metas = new PLL_Sync_Term_Metas( $polylang );
25
 
26
+ add_filter( 'wp_insert_post_parent', array( $this, 'can_sync_post_parent' ), 10, 3 );
27
+ add_filter( 'wp_insert_post_data', array( $this, 'can_sync_post_data' ), 10, 2 );
28
+
29
  add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
30
  add_action( 'created_term', array( $this, 'sync_term_parent' ), 10, 3 );
31
  add_action( 'edited_term', array( $this, 'sync_term_parent' ), 10, 3 );
70
  return $postarr;
71
  }
72
 
73
+ /**
74
+ * Prevents synchronized post parent modification if the current user hasn't enough rights
75
+ *
76
+ * @since 2.6
77
+ *
78
+ * @param int $post_parent Post parent ID
79
+ * @param int $post_id Post ID, unused
80
+ * @param array $postarr Array of parsed post data
81
+ * @return int
82
+ */
83
+ public function can_sync_post_parent( $post_parent, $post_id, $postarr ) {
84
+ if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) {
85
+ $tr_ids = $this->model->post->get_translations( $postarr['ID'] );
86
+ $orig_lang = array_search( $postarr['ID'], $tr_ids );
87
+ foreach ( $tr_ids as $tr_id ) {
88
+ if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) {
89
+ $post_parent = $post->post_parent;
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ return $post_parent;
95
+ }
96
+
97
+ /**
98
+ * Prevents synchronized post data modification if the current user hasn't enough rights
99
+ *
100
+ * @since 2.6
101
+ *
102
+ * @param array $data An array of slashed post data.
103
+ * @param array $postarr An array of sanitized, but otherwise unmodified post data.
104
+ * @return array
105
+ */
106
+ public function can_sync_post_data( $data, $postarr ) {
107
+ if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) {
108
+ foreach ( $this->model->post->get_translations( $postarr['ID'] ) as $tr_id ) {
109
+ if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) {
110
+ $to_sync = $this->get_fields_to_sync( $post );
111
+ $data = array_merge( $data, $to_sync );
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ return $data;
117
+ }
118
+
119
  /**
120
  * Synchronizes post fields in translations
121
  *
128
  public function pll_save_post( $post_id, $post, $translations ) {
129
  global $wpdb;
130
 
131
+ if ( $this->model->post->current_user_can_synchronize( $post_id ) ) {
132
+ $postarr = $this->get_fields_to_sync( $post );
133
 
134
+ if ( ! empty( $postarr ) ) {
135
+ foreach ( $translations as $lang => $tr_id ) {
136
+ if ( ! $tr_id || $tr_id === $post_id ) {
137
+ continue;
138
+ }
139
 
140
+ $tr_arr = $postarr;
141
+ unset( $tr_arr['post_parent'] );
142
 
143
+ // Do not udpate the translation parent if the user set a parent with no translation
144
+ if ( isset( $postarr['post_parent'] ) ) {
145
+ $post_parent = $postarr['post_parent'] ? $this->model->post->get_translation( $postarr['post_parent'], $lang ) : 0;
146
+ if ( ! ( $postarr['post_parent'] && ! $post_parent ) ) {
147
+ $tr_arr['post_parent'] = $post_parent;
148
+ }
149
  }
 
150
 
151
+ // Update all the row at once
152
+ // Don't use wp_update_post to avoid infinite loop
153
+ $wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) );
154
+ clean_post_cache( $tr_id );
155
+ }
156
  }
157
  }
158
  }
modules/wpml/settings-wpml.php CHANGED
@@ -19,8 +19,8 @@ class PLL_Settings_WPML extends PLL_Settings_Module {
19
  $polylang,
20
  array(
21
  'module' => 'wpml',
22
- 'title' => __( 'WPML Compatibility', 'polylang' ),
23
- 'description' => __( 'WPML compatibility mode of Polylang', 'polylang' ),
24
  )
25
  );
26
  }
19
  $polylang,
20
  array(
21
  'module' => 'wpml',
22
+ 'title' => __( 'WPML compatibility', 'polylang' ),
23
+ 'description' => __( 'Polylang\'s WPML compatibility mode', 'polylang' ),
24
  )
25
  );
26
  }
modules/wpml/wpml-api.php CHANGED
@@ -146,7 +146,7 @@ class PLL_WPML_API {
146
  $lang = pll_current_language();
147
  $field = sprintf( '<input type="hidden" name="lang" value="%s" />', esc_attr( $lang ) );
148
  $field = apply_filters( 'wpml_language_form_input_field', $field, $lang );
149
- echo $field;
150
  }
151
 
152
  /**
146
  $lang = pll_current_language();
147
  $field = sprintf( '<input type="hidden" name="lang" value="%s" />', esc_attr( $lang ) );
148
  $field = apply_filters( 'wpml_language_form_input_field', $field, $lang );
149
+ echo $field; // phpcs:ignore WordPress.Security.EscapeOutput
150
  }
151
 
152
  /**
modules/wpml/wpml-compat.php CHANGED
@@ -77,12 +77,27 @@ class PLL_WPML_Compat {
77
  *
78
  * @since 1.0.2
79
  *
80
- * @param string $context the group in which the string is registered, defaults to 'polylang'
81
- * @param string $name a unique name for the string
82
- * @param string $string the string to register
83
  */
84
  public function register_string( $context, $name, $string ) {
85
- // Registers the string if it does not exist yet (multiline as in WPML)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  $to_register = array( 'context' => $context, 'name' => $name, 'string' => $string, 'multiline' => true, 'icl' => true );
87
  if ( ! in_array( $to_register, self::$strings ) && $to_register['string'] ) {
88
  self::$strings[] = $to_register;
77
  *
78
  * @since 1.0.2
79
  *
80
+ * @param string $context The group in which the string is registered.
81
+ * @param string $name A unique name for the string.
82
+ * @param string $string The string to register.
83
  */
84
  public function register_string( $context, $name, $string ) {
85
+ // If a string has already been registered with the same name and context, let's replace it.
86
+ $exist_string = $this->get_string_by_context_and_name( $context, $name );
87
+ if ( $exist_string && $exist_string !== $string ) {
88
+ $languages = PLL()->model->get_languages_list();
89
+
90
+ // Assign translations of the old string to the new string.
91
+ foreach ( $languages as $language ) {
92
+ $mo = new PLL_MO();
93
+ $mo->import_from_db( $language );
94
+ $mo->add_entry( $mo->make_entry( $string, $mo->translate( $exist_string ) ) );
95
+ $mo->export_to_db( $language );
96
+ }
97
+ $this->unregister_string( $context, $name );
98
+ }
99
+
100
+ // Registers the string if it does not exist yet (multiline as in WPML).
101
  $to_register = array( 'context' => $context, 'name' => $name, 'string' => $string, 'multiline' => true, 'icl' => true );
102
  if ( ! in_array( $to_register, self::$strings ) && $to_register['string'] ) {
103
  self::$strings[] = $to_register;
modules/wpml/wpml-config.php CHANGED
@@ -71,7 +71,8 @@ class PLL_WPML_Config {
71
  }
72
 
73
  if ( ! empty( $this->xmls ) ) {
74
- add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 10, 2 );
 
75
  add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 );
76
  add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 );
77
 
@@ -113,6 +114,29 @@ class PLL_WPML_Config {
113
  return $metas;
114
  }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  /**
117
  * Language and translation management for custom post types
118
  *
71
  }
72
 
73
  if ( ! empty( $this->xmls ) ) {
74
+ add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 20, 2 );
75
+ add_filter( 'pll_copy_term_metas', array( $this, 'copy_term_metas' ), 20, 2 );
76
  add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 );
77
  add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 );
78
 
114
  return $metas;
115
  }
116
 
117
+ /**
118
+ * Adds term metas to the list of metas to copy when creating a new translation
119
+ *
120
+ * @since 2.6
121
+ *
122
+ * @param array $metas The list of term metas to copy or synchronize.
123
+ * @param bool $sync True for sync, false for copy.
124
+ * @return array The list of term metas to copy or synchronize.
125
+ */
126
+ public function copy_term_metas( $metas, $sync ) {
127
+ foreach ( $this->xmls as $xml ) {
128
+ foreach ( $xml->xpath( 'custom-term-fields/custom-term-field' ) as $cf ) {
129
+ $attributes = $cf->attributes();
130
+ if ( 'copy' == $attributes['action'] || ( ! $sync && in_array( $attributes['action'], array( 'translate', 'copy-once' ) ) ) ) {
131
+ $metas[] = (string) $cf;
132
+ } else {
133
+ $metas = array_diff( $metas, array( (string) $cf ) );
134
+ }
135
+ }
136
+ }
137
+ return $metas;
138
+ }
139
+
140
  /**
141
  * Language and translation management for custom post types
142
  *
modules/wpml/wpml-legacy-api.php CHANGED
@@ -116,7 +116,7 @@ if ( ! function_exists( 'icl_link_to_element' ) ) {
116
  }
117
 
118
  $pll_type = ( 'post' == $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' == $type || pll_is_translated_taxonomy( $type ) ? 'term' : false );
119
- if ( $pll_type && ( $lang = pll_current_language() ) && ( $tr_id = PLL()->model->$pll_type->get_translation( $id, $lang ) ) && ( 'term' === $pll_type || PLL()->links->current_user_can_read( $tr_id ) ) ) {
120
  $id = $tr_id;
121
  } elseif ( ! $return_original_if_missing ) {
122
  return '';
@@ -149,7 +149,7 @@ if ( ! function_exists( 'icl_link_to_element' ) ) {
149
  $link = sprintf( '<a href="%s">%s</a>', esc_url( $link ), esc_html( $text ) );
150
 
151
  if ( $echo ) {
152
- echo $link;
153
  }
154
 
155
  return $link;
@@ -325,12 +325,14 @@ if ( ! function_exists( 'wpml_get_copied_fields_for_post_edit' ) ) {
325
  * @return array
326
  */
327
  function wpml_get_copied_fields_for_post_edit() {
328
- if ( empty( $_GET['from_post'] ) ) {
329
  return array();
330
  }
331
 
 
 
332
  // Don't know what WPML does but Polylang does copy all public meta keys by default
333
- foreach ( $keys = array_unique( array_keys( get_post_custom( (int) $_GET['from_post'] ) ) ) as $k => $meta_key ) {
334
  if ( is_protected_meta( $meta_key ) ) {
335
  unset( $keys[ $k ] );
336
  }
@@ -339,7 +341,6 @@ if ( ! function_exists( 'wpml_get_copied_fields_for_post_edit' ) ) {
339
  // Apply our filter and fill the expected output ( see /types/embedded/includes/fields-post.php )
340
  /** This filter is documented in modules/sync/admin-sync.php */
341
  $arr['fields'] = array_unique( apply_filters( 'pll_copy_post_metas', empty( $keys ) ? array() : $keys, false ) );
342
- $arr['original_post_id'] = (int) $_GET['from_post'];
343
  return $arr;
344
  }
345
  }
116
  }
117
 
118
  $pll_type = ( 'post' == $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' == $type || pll_is_translated_taxonomy( $type ) ? 'term' : false );
119
+ if ( $pll_type && ( $lang = pll_current_language() ) && ( $tr_id = PLL()->model->$pll_type->get_translation( $id, $lang ) ) && ( 'term' === $pll_type || PLL()->model->post->current_user_can_read( $tr_id ) ) ) {
120
  $id = $tr_id;
121
  } elseif ( ! $return_original_if_missing ) {
122
  return '';
149
  $link = sprintf( '<a href="%s">%s</a>', esc_url( $link ), esc_html( $text ) );
150
 
151
  if ( $echo ) {
152
+ echo $link; // phpcs:ignore WordPress.Security.EscapeOutput
153
  }
154
 
155
  return $link;
325
  * @return array
326
  */
327
  function wpml_get_copied_fields_for_post_edit() {
328
+ if ( empty( $_GET['from_post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
329
  return array();
330
  }
331
 
332
+ $arr['original_post_id'] = (int) $_GET['from_post']; // phpcs:ignore WordPress.Security.NonceVerification
333
+
334
  // Don't know what WPML does but Polylang does copy all public meta keys by default
335
+ foreach ( $keys = array_unique( array_keys( get_post_custom( $arr['original_post_id'] ) ) ) as $k => $meta_key ) {
336
  if ( is_protected_meta( $meta_key ) ) {
337
  unset( $keys[ $k ] );
338
  }
341
  // Apply our filter and fill the expected output ( see /types/embedded/includes/fields-post.php )
342
  /** This filter is documented in modules/sync/admin-sync.php */
343
  $arr['fields'] = array_unique( apply_filters( 'pll_copy_post_metas', empty( $keys ) ? array() : $keys, false ) );
 
344
  return $arr;
345
  }
346
  }
polylang.php CHANGED
@@ -3,8 +3,8 @@
3
  /**
4
  Plugin Name: Polylang
5
  Plugin URI: https://polylang.pro
6
- Version: 2.5.4
7
- Author: Frédéric Demarle
8
  Author uri: https://polylang.pro
9
  Description: Adds multilingual capability to WordPress
10
  Text Domain: polylang
@@ -13,11 +13,12 @@ Domain Path: /languages
13
 
14
  /*
15
  * Copyright 2011-2019 Frédéric Demarle
 
16
  *
17
- * This program is free software; you can redistribute it and/or modify
18
  * it under the terms of the GNU General Public License as published by
19
- * the Free Software Foundation; either version 2 of the License, or
20
- * ( at your option ) any later version.
21
  *
22
  * This program is distributed in the hope that it will be useful,
23
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -25,10 +26,7 @@ Domain Path: /languages
25
  * GNU General Public License for more details.
26
  *
27
  * You should have received a copy of the GNU General Public License
28
- * along with this program; if not, write to the Free Software
29
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
30
- * MA 02110-1301, USA.
31
- *
32
  */
33
 
34
  if ( ! defined( 'ABSPATH' ) ) {
@@ -53,7 +51,7 @@ if ( defined( 'POLYLANG_BASENAME' ) ) {
53
  }
54
  } else {
55
  // Go on loading the plugin
56
- define( 'POLYLANG_VERSION', '2.5.4' );
57
  define( 'PLL_MIN_WP_VERSION', '4.7' );
58
 
59
  define( 'POLYLANG_FILE', __FILE__ ); // this file
@@ -67,11 +65,12 @@ if ( defined( 'POLYLANG_BASENAME' ) ) {
67
  define( 'PLL_INSTALL_INC', POLYLANG_DIR . '/install' );
68
  define( 'PLL_MODULES_INC', POLYLANG_DIR . '/modules' );
69
  define( 'PLL_SETTINGS_INC', POLYLANG_DIR . '/settings' );
 
70
 
71
- require_once PLL_INC . '/class-polylang.php';
72
-
73
- if ( file_exists( PLL_INC . '/class-polylang-pro.php' ) ) {
74
  define( 'POLYLANG_PRO', true );
75
- require_once PLL_INC . '/class-polylang-pro.php';
76
  }
 
 
 
77
  }
3
  /**
4
  Plugin Name: Polylang
5
  Plugin URI: https://polylang.pro
6
+ Version: 2.6
7
+ Author: WP SYNTEX
8
  Author uri: https://polylang.pro
9
  Description: Adds multilingual capability to WordPress
10
  Text Domain: polylang
13
 
14
  /*
15
  * Copyright 2011-2019 Frédéric Demarle
16
+ * Copyright 2019 WP SYNTEX
17
  *
18
+ * This program is free software: you can redistribute it and/or modify
19
  * it under the terms of the GNU General Public License as published by
20
+ * the Free Software Foundation, either version 3 of the License, or
21
+ * (at your option) any later version.
22
  *
23
  * This program is distributed in the hope that it will be useful,
24
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
  * GNU General Public License for more details.
27
  *
28
  * You should have received a copy of the GNU General Public License
29
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 
 
30
  */
31
 
32
  if ( ! defined( 'ABSPATH' ) ) {
51
  }
52
  } else {
53
  // Go on loading the plugin
54
+ define( 'POLYLANG_VERSION', '2.6' );
55
  define( 'PLL_MIN_WP_VERSION', '4.7' );
56
 
57
  define( 'POLYLANG_FILE', __FILE__ ); // this file
65
  define( 'PLL_INSTALL_INC', POLYLANG_DIR . '/install' );
66
  define( 'PLL_MODULES_INC', POLYLANG_DIR . '/modules' );
67
  define( 'PLL_SETTINGS_INC', POLYLANG_DIR . '/settings' );
68
+ define( 'PLL_PREFIX', 'pll_' );
69
 
70
+ if ( file_exists( PLL_MODULES_INC . '/pro.php' ) ) {
 
 
71
  define( 'POLYLANG_PRO', true );
 
72
  }
73
+
74
+ require_once PLL_INC . '/class-polylang.php';
75
+ new Polylang();
76
  }
readme.txt CHANGED
@@ -1,11 +1,11 @@
1
  === Polylang ===
2
- Contributors: Chouby
3
  Donate link: https://polylang.pro
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
  Requires at least: 4.7
6
  Tested up to: 5.2
7
- Stable tag: 2.5.4
8
- License: GPLv2 or later
9
 
10
  Making WordPress multilingual
11
 
@@ -76,6 +76,43 @@ Don't hesitate to [give your feedback](http://wordpress.org/support/view/plugin-
76
 
77
  == Changelog ==
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  = 2.5.4 (2019-05-28) =
80
 
81
  * Add Kannada to the predefined languages list
1
  === Polylang ===
2
+ Contributors: Chouby, manooweb
3
  Donate link: https://polylang.pro
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
  Requires at least: 4.7
6
  Tested up to: 5.2
7
+ Stable tag: 2.6
8
+ License: GPLv3 or later
9
 
10
  Making WordPress multilingual
11
 
76
 
77
  == Changelog ==
78
 
79
+ = 2.6 (2019-06-26) =
80
+
81
+ * Pro: Remove all languages files. All translations are now maintained on TranslationsPress
82
+ * Pro: Move the languages metabox to a block editor plugin
83
+ * Pro: Better management of user capabilities when synchronizing posts
84
+ * Pro: Separate REST requests from the frontend
85
+ * Pro: Copy the post slug when duplicating a post
86
+ * Pro: Duplicate ACF term metas when terms are automatically duplicated when creating a new post translation
87
+ * Pro: Fix hierarchy lost when duplicating terms
88
+ * Pro: Fix page shared slugs with special characters
89
+ * Pro: Fix synchronized posts sharing their slug when the language is set from the content
90
+ * Pro: Fix PHP warning with ACF Pro 5.8.1
91
+ * Pro: Fix ACF clone fields not translated in repeaters
92
+ * Better management of user capablities when synchronizing taxonomies terms and custom fields
93
+ * Extend string translations search to translated strings #207
94
+ * Update plugin updater to 1.6.18
95
+ * Honor the filter `pll_flag` when performing the flag validation when creating a new language
96
+ * Modify the title and the label for the language switcher menu items #307
97
+ * Add support for international domain names
98
+ * Add a title to the link icon used to add a translation #325
99
+ * Add a notice when a static front page is not translated in a language
100
+ * Add support for custom term fields in wpml-config.xml
101
+ * Add filter `pll_admin_languages_filter` for the list of items the admin bar language filter
102
+ * Add compatibility with WP Offload Media Lite. Props Daniel Berkman
103
+ * Yoast SEO: Add post type archive url in all languages to the sitemap
104
+ * Fix www. not redirected to not www. for the home page in multiple domains #311
105
+ * Fix cropped images not being synchronized
106
+ * Fix auto added page to menus when the page is created with the block editor
107
+ * Fix embed of translated static front page #318
108
+ * Fix a possible infinite redirect if the static front page is not translated
109
+ * Fix incorrect behavior of action 'wpml_register_single_string' when updating the string source
110
+ * Fix fatal error with Jetpack when no languages has been defined yet #330
111
+ * Fix a conflict with Laravel Valet. Props @chesio. #250
112
+ * Fix a conflict with Thesis.
113
+ * Fix a conflict with Pods in the block editor. Props Jory Hogeveen. #369
114
+ * Fix fatal error with Twenty Fourteen introduced in version 2.5.4. #374
115
+
116
  = 2.5.4 (2019-05-28) =
117
 
118
  * Add Kannada to the predefined languages list
settings/flags.php CHANGED
@@ -153,7 +153,7 @@ $flags = array(
153
  'me' => __( 'Montenegro', 'polylang' ),
154
  'mg' => __( 'Madagascar', 'polylang' ),
155
  'mh' => __( 'Marshall Islands', 'polylang' ),
156
- 'mk' => __( 'Macedonia', 'polylang' ),
157
  'ml' => __( 'Mali', 'polylang' ),
158
  'mm' => __( 'Myanmar', 'polylang' ),
159
  'mn' => __( 'Mongolia', 'polylang' ),
@@ -270,4 +270,4 @@ $flags = array(
270
  *
271
  * @param array $flags
272
  */
273
- $flags = apply_filters( 'pll_predefined_flags', $flags );
153
  'me' => __( 'Montenegro', 'polylang' ),
154
  'mg' => __( 'Madagascar', 'polylang' ),
155
  'mh' => __( 'Marshall Islands', 'polylang' ),
156
+ 'mk' => __( 'North Macedonia', 'polylang' ),
157
  'ml' => __( 'Mali', 'polylang' ),
158
  'mm' => __( 'Myanmar', 'polylang' ),
159
  'mn' => __( 'Mongolia', 'polylang' ),
270
  *
271
  * @param array $flags
272
  */
273
+ return apply_filters( 'pll_predefined_flags', $flags );
settings/languages.php CHANGED
@@ -38,7 +38,7 @@ if ( ! defined( 'ABSPATH' ) ) {
38
  * 'zu_ZA' (Zulu)
39
  * 'zz_TR' (Zazaki)
40
  */
41
- $languages = array(
42
  'af' => array(
43
  'code' => 'af',
44
  'locale' => 'af',
38
  * 'zu_ZA' (Zulu)
39
  * 'zz_TR' (Zazaki)
40
  */
41
+ return array(
42
  'af' => array(
43
  'code' => 'af',
44
  'locale' => 'af',
settings/settings-browser.php CHANGED
@@ -79,10 +79,10 @@ class PLL_Settings_Browser extends PLL_Settings_Module {
79
  $( "input[name='force_lang']" ).change( function() {
80
  var value = $( this ).val();
81
  if ( 3 > value ) {
82
- $( "#pll-module-browser" ).<?php echo $func; ?>.children( "td" ).children( ".row-actions" ).html( '<?php echo $link; ?>' );
83
  }
84
  else {
85
- $( "#pll-module-browser" ).removeClass( "active" ).addClass( "inactive" ).children( "td" ).children( ".row-actions" ).html( '<?php echo $deactivated; ?>' );
86
  }
87
  } );
88
  } )( jQuery );
79
  $( "input[name='force_lang']" ).change( function() {
80
  var value = $( this ).val();
81
  if ( 3 > value ) {
82
+ $( "#pll-module-browser" ).<?php echo $func; // phpcs:ignore WordPress.Security.EscapeOutput ?>.children( "td" ).children( ".row-actions" ).html( '<?php echo $link; // phpcs:ignore WordPress.Security.EscapeOutput ?>' );
83
  }
84
  else {
85
+ $( "#pll-module-browser" ).removeClass( "active" ).addClass( "inactive" ).children( "td" ).children( ".row-actions" ).html( '<?php echo $deactivated; // phpcs:ignore WordPress.Security.EscapeOutput ?>' );
86
  }
87
  } );
88
  } )( jQuery );
settings/settings-cpt.php CHANGED
@@ -21,7 +21,7 @@ class PLL_Settings_CPT extends PLL_Settings_Module {
21
  array(
22
  'module' => 'cpt',
23
  'title' => __( 'Custom post types and Taxonomies', 'polylang' ),
24
- 'description' => __( 'Activate the languages and translations management for the custom post types and taxonomies.', 'polylang' ),
25
  )
26
  );
27
 
@@ -71,8 +71,8 @@ class PLL_Settings_CPT extends PLL_Settings_Module {
71
  printf(
72
  '<li><label><input name="post_types[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
73
  esc_attr( $post_type ),
74
- in_array( $post_type, $this->options['post_types'] ) || $disabled ? 'checked="checked"' : '',
75
- $disabled ? 'disabled="disabled"' : '',
76
  esc_html( $pt->labels->name )
77
  );
78
  }
@@ -95,8 +95,8 @@ class PLL_Settings_CPT extends PLL_Settings_Module {
95
  printf(
96
  '<li><label><input name="taxonomies[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
97
  esc_attr( $taxonomy ),
98
- in_array( $taxonomy, $this->options['taxonomies'] ) || $disabled ? 'checked="checked"' : '',
99
- $disabled ? 'disabled="disabled"' : '',
100
  esc_html( $tax->labels->name )
101
  );
102
  }
21
  array(
22
  'module' => 'cpt',
23
  'title' => __( 'Custom post types and Taxonomies', 'polylang' ),
24
+ 'description' => __( 'Activate languages and translations management for the custom post types and the taxonomies.', 'polylang' ),
25
  )
26
  );
27
 
71
  printf(
72
  '<li><label><input name="post_types[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
73
  esc_attr( $post_type ),
74
+ checked( in_array( $post_type, $this->options['post_types'] ) || $disabled, true, false ),
75
+ disabled( $disabled, true, false ),
76
  esc_html( $pt->labels->name )
77
  );
78
  }
95
  printf(
96
  '<li><label><input name="taxonomies[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
97
  esc_attr( $taxonomy ),
98
+ checked( in_array( $taxonomy, $this->options['taxonomies'] ) || $disabled, true, false ),
99
+ disabled( $disabled, true, false ),
100
  esc_html( $tax->labels->name )
101
  );
102
  }
settings/settings-licenses.php CHANGED
@@ -21,11 +21,11 @@ class PLL_Settings_Licenses extends PLL_Settings_Module {
21
  array(
22
  'module' => 'licenses',
23
  'title' => __( 'License keys', 'polylang' ),
24
- 'description' => __( 'Manage licenses for Polylang Pro or addons.', 'polylang' ),
25
  )
26
  );
27
 
28
- $this->buttons['cancel'] = sprintf( '<button type="button" class="button button-secondary cancel">%s</button>', __( 'Close' ) );
29
 
30
  $this->items = apply_filters( 'pll_settings_licenses', array() );
31
 
@@ -53,7 +53,7 @@ class PLL_Settings_Licenses extends PLL_Settings_Module {
53
  <table id="pll-licenses-table" class="form-table">
54
  <?php
55
  foreach ( $this->items as $item ) {
56
- echo $this->get_row( $item );
57
  }
58
  ?>
59
  </table>
@@ -153,7 +153,7 @@ class PLL_Settings_Licenses extends PLL_Settings_Module {
153
  $class = 'notice-warning notice-alt';
154
  $message = sprintf(
155
  /* translators: %1$s is a date, %2$s is link start tag, %3$s is link end tag. */
156
- __( 'Your license key expires soon! It expires on %1$s. %2$sRenew your license key%3$s.', 'polylang' ),
157
  date_i18n( get_option( 'date_format' ), strtotime( $license->expires, $now ) ),
158
  sprintf( '<a href="%s" target="_blank">', 'https://polylang.pro/checkout/?edd_license_key=' . $item->license_key ),
159
  '</a>'
@@ -187,15 +187,17 @@ class PLL_Settings_Licenses extends PLL_Settings_Module {
187
  wp_die( -1 );
188
  }
189
 
190
- if ( $this->module === $_POST['module'] && ! empty( $_POST['licenses'] ) ) {
191
  $x = new WP_Ajax_Response();
192
  foreach ( $this->items as $item ) {
193
- $updated_item = $item->activate_license( sanitize_text_field( $_POST['licenses'][ $item->id ] ) );
194
- $x->Add( array( 'what' => 'license-update', 'data' => $item->id, 'supplemental' => array( 'html' => $this->get_row( $updated_item ) ) ) );
 
 
195
  }
196
 
197
  // Updated message
198
- add_settings_error( 'general', 'settings_updated', __( 'Settings saved.' ), 'updated' );
199
  ob_start();
200
  settings_errors();
201
  $x->Add( array( 'what' => 'success', 'data' => ob_get_clean() ) );
@@ -215,7 +217,11 @@ class PLL_Settings_Licenses extends PLL_Settings_Module {
215
  wp_die( -1 );
216
  }
217
 
218
- $id = sanitize_text_field( substr( $_POST['id'], 11 ) );
 
 
 
 
219
  wp_send_json(
220
  array(
221
  'id' => $id,
21
  array(
22
  'module' => 'licenses',
23
  'title' => __( 'License keys', 'polylang' ),
24
+ 'description' => __( 'Manage licenses for Polylang Pro and add-ons.', 'polylang' ),
25
  )
26
  );
27
 
28
+ $this->buttons['cancel'] = sprintf( '<button type="button" class="button button-secondary cancel">%s</button>', __( 'Close', 'polylang' ) );
29
 
30
  $this->items = apply_filters( 'pll_settings_licenses', array() );
31
 
53
  <table id="pll-licenses-table" class="form-table">
54
  <?php
55
  foreach ( $this->items as $item ) {
56
+ echo $this->get_row( $item ); // phpcs:ignore WordPress.Security.EscapeOutput
57
  }
58
  ?>
59
  </table>
153
  $class = 'notice-warning notice-alt';
154
  $message = sprintf(
155
  /* translators: %1$s is a date, %2$s is link start tag, %3$s is link end tag. */
156
+ __( 'Your license key will expire soon! Precisely, it will expire on %1$s. %2$sRenew your license key today!%3$s.', 'polylang' ),
157
  date_i18n( get_option( 'date_format' ), strtotime( $license->expires, $now ) ),
158
  sprintf( '<a href="%s" target="_blank">', 'https://polylang.pro/checkout/?edd_license_key=' . $item->license_key ),
159
  '</a>'
187
  wp_die( -1 );
188
  }
189
 
190
+ if ( isset( $_POST['module'] ) && $this->module === $_POST['module'] && ! empty( $_POST['licenses'] ) ) {
191
  $x = new WP_Ajax_Response();
192
  foreach ( $this->items as $item ) {
193
+ if ( ! empty( $_POST['licenses'][ $item->id ] ) ) {
194
+ $updated_item = $item->activate_license( sanitize_key( $_POST['licenses'][ $item->id ] ) );
195
+ $x->Add( array( 'what' => 'license-update', 'data' => $item->id, 'supplemental' => array( 'html' => $this->get_row( $updated_item ) ) ) );
196
+ }
197
  }
198
 
199
  // Updated message
200
+ add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'polylang' ), 'updated' );
201
  ob_start();
202
  settings_errors();
203
  $x->Add( array( 'what' => 'success', 'data' => ob_get_clean() ) );
217
  wp_die( -1 );
218
  }
219
 
220
+ if ( ! isset( $_POST['id'] ) ) {
221
+ wp_die( 0 );
222
+ }
223
+
224
+ $id = substr( sanitize_text_field( wp_unslash( $_POST['id'] ) ), 11 );
225
  wp_send_json(
226
  array(
227
  'id' => $id,
settings/settings-media.php CHANGED
@@ -19,7 +19,7 @@ class PLL_Settings_Media extends PLL_Settings_Module {
19
  $polylang,
20
  array(
21
  'module' => 'media',
22
- 'title' => __( 'Media' ),
23
  'description' => __( 'Activate languages and translations for media', 'polylang' ),
24
  'active_option' => 'media_support',
25
  )
19
  $polylang,
20
  array(
21
  'module' => 'media',
22
+ 'title' => __( 'Media', 'polylang' ),
23
  'description' => __( 'Activate languages and translations for media', 'polylang' ),
24
  'active_option' => 'media_support',
25
  )
settings/settings-module.php CHANGED
@@ -48,13 +48,13 @@ class PLL_Settings_Module {
48
  'deactivate' => sprintf(
49
  '<a title="%s" href="%s">%s</a>',
50
  esc_attr__( 'Deactivate this module', 'polylang' ),
51
- wp_nonce_url( '?page=mlang&amp;tab=modules&amp;pll_action=deactivate&amp;noheader=true&amp;module=' . $this->module, 'pll_deactivate' ),
52
  esc_html__( 'Deactivate', 'polylang' )
53
  ),
54
  'activate' => sprintf(
55
  '<a title="%s" href="%s">%s</a>',
56
  esc_attr__( 'Activate this module', 'polylang' ),
57
- wp_nonce_url( '?page=mlang&amp;tab=modules&amp;pll_action=activate&amp;noheader=true&amp;module=' . $this->module, 'pll_activate' ),
58
  esc_html__( 'Activate', 'polylang' )
59
  ),
60
  'activated' => esc_html__( 'Activated', 'polylang' ),
@@ -62,8 +62,8 @@ class PLL_Settings_Module {
62
  );
63
 
64
  $this->buttons = array(
65
- 'cancel' => sprintf( '<button type="button" class="button button-secondary cancel">%s</button>', esc_html__( 'Cancel' ) ),
66
- 'save' => sprintf( '<button type="button" class="button button-primary save">%s</button>', esc_html__( 'Save Changes' ) ),
67
  );
68
 
69
  // Ajax action to save options
@@ -155,7 +155,7 @@ class PLL_Settings_Module {
155
  wp_die( -1 );
156
  }
157
 
158
- if ( $this->module == $_POST['module'] ) {
159
  // It's up to the child class to decide which options are saved, whether there are errors or not
160
  $post = array_diff_key( $_POST, array_flip( array( 'action', 'module', 'pll_ajax_backend', '_pll_nonce' ) ) );
161
  $options = $this->update( $post );
@@ -173,7 +173,7 @@ class PLL_Settings_Module {
173
 
174
  if ( ! get_settings_errors() ) {
175
  // Send update message
176
- add_settings_error( 'general', 'settings_updated', __( 'Settings saved.' ), 'updated' );
177
  settings_errors();
178
  $x = new WP_Ajax_Response( array( 'what' => 'success', 'data' => ob_get_clean() ) );
179
  $x->send();
@@ -230,7 +230,7 @@ class PLL_Settings_Module {
230
  protected function default_upgrade_message() {
231
  return sprintf(
232
  '%s <a href="%s">%s</a>',
233
- __( 'You need Polylang Pro to enable this feature.', 'polylang' ),
234
  'https://polylang.pro',
235
  __( 'Upgrade now.', 'polylang' )
236
  );
48
  'deactivate' => sprintf(
49
  '<a title="%s" href="%s">%s</a>',
50
  esc_attr__( 'Deactivate this module', 'polylang' ),
51
+ esc_url( wp_nonce_url( '?page=mlang&tab=modules&pll_action=deactivate&noheader=true&module=' . $this->module, 'pll_deactivate' ) ),
52
  esc_html__( 'Deactivate', 'polylang' )
53
  ),
54
  'activate' => sprintf(
55
  '<a title="%s" href="%s">%s</a>',
56
  esc_attr__( 'Activate this module', 'polylang' ),
57
+ esc_url( wp_nonce_url( '?page=mlang&tab=modules&pll_action=activate&noheader=true&module=' . $this->module, 'pll_activate' ) ),
58
  esc_html__( 'Activate', 'polylang' )
59
  ),
60
  'activated' => esc_html__( 'Activated', 'polylang' ),
62
  );
63
 
64
  $this->buttons = array(
65
+ 'cancel' => sprintf( '<button type="button" class="button button-secondary cancel">%s</button>', esc_html__( 'Cancel', 'polylang' ) ),
66
+ 'save' => sprintf( '<button type="button" class="button button-primary save">%s</button>', esc_html__( 'Save Changes', 'polylang' ) ),
67
  );
68
 
69
  // Ajax action to save options
155
  wp_die( -1 );
156
  }
157
 
158
+ if ( isset( $_POST['module'] ) && $this->module === $_POST['module'] ) {
159
  // It's up to the child class to decide which options are saved, whether there are errors or not
160
  $post = array_diff_key( $_POST, array_flip( array( 'action', 'module', 'pll_ajax_backend', '_pll_nonce' ) ) );
161
  $options = $this->update( $post );
173
 
174
  if ( ! get_settings_errors() ) {
175
  // Send update message
176
+ add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'polylang' ), 'updated' );
177
  settings_errors();
178
  $x = new WP_Ajax_Response( array( 'what' => 'success', 'data' => ob_get_clean() ) );
179
  $x->send();
230
  protected function default_upgrade_message() {
231
  return sprintf(
232
  '%s <a href="%s">%s</a>',
233
+ __( 'To enable this feature, you need Polylang Pro.', 'polylang' ),
234
  'https://polylang.pro',
235
  __( 'Upgrade now.', 'polylang' )
236
  );
settings/settings-tools.php CHANGED
@@ -33,8 +33,8 @@ class PLL_Settings_Tools extends PLL_Settings_Module {
33
  protected function form() {
34
  printf(
35
  '<label for="uninstall"><input id="uninstall" name="uninstall" type="checkbox" value="1" %s /> %s</label>',
36
- empty( $this->options['uninstall'] ) ? '' : 'checked="checked"',
37
- esc_html__( 'Remove all Polylang data when using the "Delete" link on the plugins screen.', 'polylang' )
38
  );
39
  }
40
 
33
  protected function form() {
34
  printf(
35
  '<label for="uninstall"><input id="uninstall" name="uninstall" type="checkbox" value="1" %s /> %s</label>',
36
+ checked( empty( $this->options['uninstall'] ), false, false ),
37
+ esc_html__( 'Remove all Polylang data upon using the "Delete" action in the "Plugins" admin page.', 'polylang' )
38
  );
39
  }
40
 
settings/settings-url.php CHANGED
@@ -40,18 +40,18 @@ class PLL_Settings_Url extends PLL_Settings_Module {
40
  <?php
41
  printf(
42
  '<input name="force_lang" type="radio" value="0" %s /> %s',
43
- $this->options['force_lang'] ? '' : 'checked="checked"',
44
  esc_html__( 'The language is set from content', 'polylang' )
45
  );
46
  ?>
47
  </label>
48
- <p class="description"><?php esc_html_e( 'Posts, pages, categories and tags urls are not modified.', 'polylang' ); ?></p>
49
  <label>
50
  <?php
51
  printf(
52
  '<input name="force_lang" type="radio" value="1" %s/> %s',
53
- 1 == $this->options['force_lang'] ? 'checked="checked"' : '',
54
- $this->links_model->using_permalinks ? esc_html__( 'The language is set from the directory name in pretty permalinks', 'polylang' ) : esc_html__( 'The language is set from the code in the URL', 'polylang' )
55
  );
56
  ?>
57
  </label>
@@ -60,8 +60,8 @@ class PLL_Settings_Url extends PLL_Settings_Module {
60
  <?php
61
  printf(
62
  '<input name="force_lang" type="radio" value="2" %s %s/> %s',
63
- $this->links_model->using_permalinks ? '' : 'disabled="disabled"',
64
- 2 == $this->options['force_lang'] ? 'checked="checked"' : '',
65
  esc_html__( 'The language is set from the subdomain name in pretty permalinks', 'polylang' )
66
  );
67
  ?>
@@ -71,8 +71,8 @@ class PLL_Settings_Url extends PLL_Settings_Module {
71
  <?php
72
  printf(
73
  '<input name="force_lang" type="radio" value="3" %s %s/> %s',
74
- $this->links_model->using_permalinks ? '' : 'disabled="disabled"',
75
- 3 == $this->options['force_lang'] ? 'checked="checked"' : '',
76
  esc_html__( 'The language is set from different domains', 'polylang' )
77
  );
78
  ?>
@@ -80,12 +80,13 @@ class PLL_Settings_Url extends PLL_Settings_Module {
80
  <table id="pll-domains-table" class="form-table" <?php echo 3 == $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>>
81
  <?php
82
  foreach ( $this->model->get_languages_list() as $lg ) {
 
83
  printf(
84
  '<tr><td><label for="pll-domain[%1$s]">%2$s</label></td>' .
85
  '<td><input name="domains[%1$s]" id="pll-domain[%1$s]" type="text" value="%3$s" class="regular-text code" aria-required="true" /></td></tr>',
86
  esc_attr( $lg->slug ),
87
  esc_attr( $lg->name ),
88
- esc_url( isset( $this->options['domains'][ $lg->slug ] ) ? $this->options['domains'][ $lg->slug ] : ( $lg->slug == $this->options['default_lang'] ? $this->links_model->home : '' ) )
89
  );
90
  }
91
  ?>
@@ -104,7 +105,7 @@ class PLL_Settings_Url extends PLL_Settings_Module {
104
  <?php
105
  printf(
106
  '<input name="hide_default" type="checkbox" value="1" %s /> %s',
107
- $this->options['hide_default'] ? 'checked="checked"' : '',
108
  esc_html__( 'Hide URL language information for default language', 'polylang' )
109
  );
110
  ?>
@@ -123,8 +124,8 @@ class PLL_Settings_Url extends PLL_Settings_Module {
123
  <?php
124
  printf(
125
  '<input name="rewrite" type="radio" value="1" %s %s/> %s',
126
- $this->links_model->using_permalinks ? '' : 'disabled="disabled"',
127
- $this->options['rewrite'] ? 'checked="checked"' : '',
128
  esc_html__( 'Remove /language/ in pretty permalinks', 'polylang' )
129
  );
130
  ?>
@@ -134,8 +135,8 @@ class PLL_Settings_Url extends PLL_Settings_Module {
134
  <?php
135
  printf(
136
  '<input name="rewrite" type="radio" value="0" %s %s/> %s',
137
- $this->links_model->using_permalinks ? '' : 'disabled="disabled"',
138
- $this->options['rewrite'] ? '' : 'checked="checked"',
139
  esc_html__( 'Keep /language/ in pretty permalinks', 'polylang' )
140
  );
141
  ?>
@@ -155,7 +156,7 @@ class PLL_Settings_Url extends PLL_Settings_Module {
155
  <?php
156
  printf(
157
  '<input name="redirect_lang" type="checkbox" value="1" %s/> %s',
158
- $this->options['redirect_lang'] ? 'checked="checked"' : '',
159
  esc_html__( 'The front page url contains the language code instead of the page name or page id', 'polylang' )
160
  );
161
  ?>
@@ -291,7 +292,7 @@ class PLL_Settings_Url extends PLL_Settings_Module {
291
  esc_html(
292
  sprintf(
293
  /* translators: %s is an url */
294
- __( 'Polylang was unable to access the URL %s. Please check that the URL is valid.', 'polylang' ),
295
  $url
296
  )
297
  )
40
  <?php
41
  printf(
42
  '<input name="force_lang" type="radio" value="0" %s /> %s',
43
+ checked( $this->options['force_lang'], 0, false ),
44
  esc_html__( 'The language is set from content', 'polylang' )
45
  );
46
  ?>
47
  </label>
48
+ <p class="description"><?php esc_html_e( 'Posts, pages, categories and tags URLs will not be modified.', 'polylang' ); ?></p>
49
  <label>
50
  <?php
51
  printf(
52
  '<input name="force_lang" type="radio" value="1" %s/> %s',
53
+ checked( $this->options['force_lang'], 1, false ),
54
+ ( $this->links_model->using_permalinks ? esc_html__( 'The language is set from the directory name in pretty permalinks', 'polylang' ) : esc_html__( 'The language is set from the code in the URL', 'polylang' ) )
55
  );
56
  ?>
57
  </label>
60
  <?php
61
  printf(
62
  '<input name="force_lang" type="radio" value="2" %s %s/> %s',
63
+ disabled( $this->links_model->using_permalinks, false, false ),
64
+ checked( $this->options['force_lang'], 2, false ),
65
  esc_html__( 'The language is set from the subdomain name in pretty permalinks', 'polylang' )
66
  );
67
  ?>
71
  <?php
72
  printf(
73
  '<input name="force_lang" type="radio" value="3" %s %s/> %s',
74
+ disabled( $this->links_model->using_permalinks, false, false ),
75
+ checked( $this->options['force_lang'], 3, false ),
76
  esc_html__( 'The language is set from different domains', 'polylang' )
77
  );
78
  ?>
80
  <table id="pll-domains-table" class="form-table" <?php echo 3 == $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>>
81
  <?php
82
  foreach ( $this->model->get_languages_list() as $lg ) {
83
+ $url = isset( $this->options['domains'][ $lg->slug ] ) ? $this->options['domains'][ $lg->slug ] : ( $lg->slug == $this->options['default_lang'] ? $this->links_model->home : '' );
84
  printf(
85
  '<tr><td><label for="pll-domain[%1$s]">%2$s</label></td>' .
86
  '<td><input name="domains[%1$s]" id="pll-domain[%1$s]" type="text" value="%3$s" class="regular-text code" aria-required="true" /></td></tr>',
87
  esc_attr( $lg->slug ),
88
  esc_attr( $lg->name ),
89
+ esc_url( $url )
90
  );
91
  }
92
  ?>
105
  <?php
106
  printf(
107
  '<input name="hide_default" type="checkbox" value="1" %s /> %s',
108
+ checked( $this->options['hide_default'], 1, false ),
109
  esc_html__( 'Hide URL language information for default language', 'polylang' )
110
  );
111
  ?>
124
  <?php
125
  printf(
126
  '<input name="rewrite" type="radio" value="1" %s %s/> %s',
127
+ disabled( $this->links_model->using_permalinks, false, false ),
128
+ checked( $this->options['rewrite'], 1, false ),
129
  esc_html__( 'Remove /language/ in pretty permalinks', 'polylang' )
130
  );
131
  ?>
135
  <?php
136
  printf(
137
  '<input name="rewrite" type="radio" value="0" %s %s/> %s',
138
+ disabled( $this->links_model->using_permalinks, false, false ),
139
+ checked( $this->options['rewrite'], 0, false ),
140
  esc_html__( 'Keep /language/ in pretty permalinks', 'polylang' )
141
  );
142
  ?>
156
  <?php
157
  printf(
158
  '<input name="redirect_lang" type="checkbox" value="1" %s/> %s',
159
+ checked( $this->options['redirect_lang'], 1, false ),
160
  esc_html__( 'The front page url contains the language code instead of the page name or page id', 'polylang' )
161
  );
162
  ?>
292
  esc_html(
293
  sprintf(
294
  /* translators: %s is an url */
295
+ __( 'Polylang was unable to access the %s URL. Please check that the URL is valid.', 'polylang' ),
296
  $url
297
  )
298
  )
settings/settings.php CHANGED
@@ -29,8 +29,8 @@ class PLL_Settings extends PLL_Admin_Base {
29
  public function __construct( &$links_model ) {
30
  parent::__construct( $links_model );
31
 
32
- if ( isset( $_GET['page'] ) ) {
33
- $this->active_tab = 'mlang' === $_GET['page'] ? 'lang' : substr( $_GET['page'], 6 );
34
  }
35
 
36
  PLL_Admin_Strings::init();
@@ -167,17 +167,27 @@ class PLL_Settings extends PLL_Admin_Base {
167
  switch ( $action ) {
168
  case 'add':
169
  check_admin_referer( 'add-lang', '_wpnonce_add-lang' );
 
170
 
171
- if ( $this->model->add_language( $_POST ) && 'en_US' !== $_POST['locale'] ) {
172
- // Attempts to install the language pack
173
- require_once ABSPATH . 'wp-admin/includes/translation-install.php';
174
- if ( ! wp_download_language_pack( $_POST['locale'] ) ) {
175
- add_settings_error( 'general', 'pll_download_mo', __( 'The language was created, but the WordPress language file was not downloaded. Please install it manually.', 'polylang' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
-
178
- // Force checking for themes and plugins translations updates
179
- wp_clean_themes_cache();
180
- wp_clean_plugins_cache();
181
  }
182
  self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true )
183
  break;
@@ -185,8 +195,8 @@ class PLL_Settings extends PLL_Admin_Base {
185
  case 'delete':
186
  check_admin_referer( 'delete-lang' );
187
 
188
- if ( ! empty( $_GET['lang'] ) ) {
189
- $this->model->delete_language( (int) $_GET['lang'] );
190
  }
191
 
192
  self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true )
@@ -194,7 +204,16 @@ class PLL_Settings extends PLL_Admin_Base {
194
 
195
  case 'update':
196
  check_admin_referer( 'add-lang', '_wpnonce_add-lang' );
197
- $error = $this->model->update_language( $_POST );
 
 
 
 
 
 
 
 
 
198
  self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true )
199
  break;
200
 
@@ -225,13 +244,23 @@ class PLL_Settings extends PLL_Admin_Base {
225
 
226
  case 'activate':
227
  check_admin_referer( 'pll_activate' );
228
- $this->modules[ $_GET['module'] ]->activate();
 
 
 
 
 
229
  self::redirect();
230
  break;
231
 
232
  case 'deactivate':
233
  check_admin_referer( 'pll_deactivate' );
234
- $this->modules[ $_GET['module'] ]->deactivate();
 
 
 
 
 
235
  self::redirect();
236
  break;
237
 
@@ -267,9 +296,9 @@ class PLL_Settings extends PLL_Admin_Base {
267
  }
268
 
269
  // Handle user input
270
- $action = isset( $_REQUEST['pll_action'] ) ? $_REQUEST['pll_action'] : '';
271
- if ( 'edit' === $action && ! empty( $_GET['lang'] ) ) {
272
- $edit_lang = $this->model->get_language( (int) $_GET['lang'] );
273
  } else {
274
  $this->handle_actions( $action );
275
  }
@@ -302,7 +331,7 @@ class PLL_Settings extends PLL_Admin_Base {
302
  printf(
303
  '<div class="error"><p>%s <a href="%s">%s</a></p></div>',
304
  esc_html__( 'There are posts, pages, categories or tags without language.', 'polylang' ),
305
- wp_nonce_url( '?page=mlang&amp;pll_action=content-default-lang&amp;noheader=true', 'content-default-lang' ),
306
  esc_html__( 'You can set them all to the default language.', 'polylang' )
307
  );
308
  }
@@ -333,8 +362,8 @@ class PLL_Settings extends PLL_Admin_Base {
333
  */
334
  public function get_predefined_languages() {
335
  require_once ABSPATH . 'wp-admin/includes/translation-install.php';
336
- include PLL_SETTINGS_INC . '/languages.php';
337
 
 
338
  $translations = wp_get_available_translations();
339
 
340
  // Keep only languages with existing WP language pack
29
  public function __construct( &$links_model ) {
30
  parent::__construct( $links_model );
31
 
32
+ if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
33
+ $this->active_tab = 'mlang' === $_GET['page'] ? 'lang' : substr( sanitize_key( $_GET['page'] ), 6 ); // phpcs:ignore WordPress.Security.NonceVerification
34
  }
35
 
36
  PLL_Admin_Strings::init();
167
  switch ( $action ) {
168
  case 'add':
169
  check_admin_referer( 'add-lang', '_wpnonce_add-lang' );
170
+ $errors = $this->model->add_language( $_POST );
171
 
172
+ if ( is_wp_error( $errors ) ) {
173
+ foreach ( $errors->get_error_messages() as $message ) {
174
+ add_settings_error( 'general', 'pll_add_language', $message );
175
+ }
176
+ } else {
177
+ add_settings_error( 'general', 'pll_languages_created', __( 'Language added.', 'polylang' ), 'updated' );
178
+ $locale = sanitize_text_field( wp_unslash( $_POST['locale'] ) ); // phpcs:ignore WordPress.Security
179
+
180
+ if ( 'en_US' !== $locale ) {
181
+ // Attempts to install the language pack
182
+ require_once ABSPATH . 'wp-admin/includes/translation-install.php';
183
+ if ( ! wp_download_language_pack( $locale ) ) {
184
+ add_settings_error( 'general', 'pll_download_mo', __( 'The language was created, but the WordPress language file was not downloaded. Please install it manually.', 'polylang' ) );
185
+ }
186
+
187
+ // Force checking for themes and plugins translations updates
188
+ wp_clean_themes_cache();
189
+ wp_clean_plugins_cache();
190
  }
 
 
 
 
191
  }
192
  self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true )
193
  break;
195
  case 'delete':
196
  check_admin_referer( 'delete-lang' );
197
 
198
+ if ( ! empty( $_GET['lang'] ) && $this->model->delete_language( (int) $_GET['lang'] ) ) {
199
+ add_settings_error( 'general', 'pll_languages_deleted', __( 'Language deleted.', 'polylang' ), 'updated' );
200
  }
201
 
202
  self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true )
204
 
205
  case 'update':
206
  check_admin_referer( 'add-lang', '_wpnonce_add-lang' );
207
+ $errors = $this->model->update_language( $_POST );
208
+
209
+ if ( is_wp_error( $errors ) ) {
210
+ foreach ( $errors->get_error_messages() as $message ) {
211
+ add_settings_error( 'general', 'pll_update_language', $message );
212
+ }
213
+ } else {
214
+ add_settings_error( 'general', 'pll_languages_updated', __( 'Language updated.', 'polylang' ), 'updated' );
215
+ }
216
+
217
  self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true )
218
  break;
219
 
244
 
245
  case 'activate':
246
  check_admin_referer( 'pll_activate' );
247
+ if ( isset( $_GET['module'] ) ) {
248
+ $module = sanitize_key( $_GET['module'] );
249
+ if ( isset( $this->modules[ $module ] ) ) {
250
+ $this->modules[ $module ]->activate();
251
+ }
252
+ }
253
  self::redirect();
254
  break;
255
 
256
  case 'deactivate':
257
  check_admin_referer( 'pll_deactivate' );
258
+ if ( isset( $_GET['module'] ) ) {
259
+ $module = sanitize_key( $_GET['module'] );
260
+ if ( isset( $this->modules[ $module ] ) ) {
261
+ $this->modules[ $module ]->deactivate();
262
+ }
263
+ }
264
  self::redirect();
265
  break;
266
 
296
  }
297
 
298
  // Handle user input
299
+ $action = isset( $_REQUEST['pll_action'] ) ? sanitize_key( $_REQUEST['pll_action'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
300
+ if ( 'edit' === $action && ! empty( $_GET['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
301
+ $edit_lang = $this->model->get_language( (int) $_GET['lang'] ); // phpcs:ignore WordPress.Security.NonceVerification
302
  } else {
303
  $this->handle_actions( $action );
304
  }
331
  printf(
332
  '<div class="error"><p>%s <a href="%s">%s</a></p></div>',
333
  esc_html__( 'There are posts, pages, categories or tags without language.', 'polylang' ),
334
+ esc_url( wp_nonce_url( '?page=mlang&pll_action=content-default-lang&noheader=true', 'content-default-lang' ) ),
335
  esc_html__( 'You can set them all to the default language.', 'polylang' )
336
  );
337
  }
362
  */
363
  public function get_predefined_languages() {
364
  require_once ABSPATH . 'wp-admin/includes/translation-install.php';
 
365
 
366
+ $languages = include PLL_SETTINGS_INC . '/languages.php';
367
  $translations = wp_get_available_translations();
368
 
369
  // Keep only languages with existing WP language pack
settings/table-languages.php CHANGED
@@ -235,7 +235,7 @@ class PLL_Table_Languages extends WP_List_Table {
235
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
236
  */
237
  protected function usort_reorder( $a, $b ) {
238
- $orderby = ! empty( $_GET['orderby'] ) ? $_GET['orderby'] : 'name';
239
  // Determine sort order
240
  if ( is_numeric( $a->$orderby ) ) {
241
  $result = $a->$orderby > $b->$orderby ? 1 : -1;
@@ -243,7 +243,7 @@ class PLL_Table_Languages extends WP_List_Table {
243
  $result = strcmp( $a->$orderby, $b->$orderby );
244
  }
245
  // Send final sort direction to usort
246
- return ( empty( $_GET['order'] ) || 'asc' == $_GET['order'] ) ? $result : -$result;
247
  }
248
 
249
  /**
235
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
236
  */
237
  protected function usort_reorder( $a, $b ) {
238
+ $orderby = ! empty( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'name'; // phpcs:ignore WordPress.Security.NonceVerification
239
  // Determine sort order
240
  if ( is_numeric( $a->$orderby ) ) {
241
  $result = $a->$orderby > $b->$orderby ? 1 : -1;
243
  $result = strcmp( $a->$orderby, $b->$orderby );
244
  }
245
  // Send final sort direction to usort
246
+ return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification
247
  }
248
 
249
  /**
settings/table-settings.php CHANGED
@@ -59,7 +59,7 @@ class PLL_Table_Settings extends WP_List_Table {
59
  '<tr class="plugin-update-tr">
60
  <td colspan="3" class="plugin-update colspanchange">%s</td>
61
  </tr>',
62
- sprintf( '<div class="update-message notice inline notice-warning notice-alt"><p>%s</p></div>', $message )
63
  );
64
  }
65
 
@@ -78,8 +78,8 @@ class PLL_Table_Settings extends WP_List_Table {
78
  </tr>',
79
  esc_attr( $item->module ),
80
  esc_html( $item->title ),
81
- $form,
82
- implode( $item->get_buttons() )
83
  );
84
  }
85
  }
@@ -106,12 +106,12 @@ class PLL_Table_Settings extends WP_List_Table {
106
 
107
  if ( 'cb' == $column_name ) {
108
  echo '<th scope="row" class="check-column">';
109
- echo $this->column_cb( $item );
110
  echo '</th>';
111
  }
112
  else {
113
  printf( '<td class="%s">', esc_attr( $classes ) );
114
- echo $this->column_default( $item, $column_name );
115
  echo '</td>';
116
  }
117
  }
59
  '<tr class="plugin-update-tr">
60
  <td colspan="3" class="plugin-update colspanchange">%s</td>
61
  </tr>',
62
+ sprintf( '<div class="update-message notice inline notice-warning notice-alt"><p>%s</p></div>', $message ) // phpcs:ignore WordPress.Security.EscapeOutput
63
  );
64
  }
65
 
78
  </tr>',
79
  esc_attr( $item->module ),
80
  esc_html( $item->title ),
81
+ $form, // phpcs:ignore
82
+ implode( $item->get_buttons() ) // phpcs:ignore
83
  );
84
  }
85
  }
106
 
107
  if ( 'cb' == $column_name ) {
108
  echo '<th scope="row" class="check-column">';
109
+ echo $this->column_cb( $item ); // phpcs:ignore WordPress.Security.EscapeOutput
110
  echo '</th>';
111
  }
112
  else {
113
  printf( '<td class="%s">', esc_attr( $classes ) );
114
+ echo $this->column_default( $item, $column_name ); // phpcs:ignore WordPress.Security.EscapeOutput
115
  echo '</td>';
116
  }
117
  }
settings/table-string.php CHANGED
@@ -31,7 +31,15 @@ class PLL_Table_String extends WP_List_Table {
31
  $this->languages = $languages;
32
  $this->strings = PLL_Admin_Strings::get_strings();
33
  $this->groups = array_unique( wp_list_pluck( $this->strings, 'context' ) );
34
- $this->selected_group = empty( $_GET['group'] ) || ! in_array( $_GET['group'], $this->groups ) ? -1 : $_GET['group'];
 
 
 
 
 
 
 
 
35
 
36
  add_action( 'mlang_action_string-translation', array( $this, 'save_translations' ) );
37
  }
@@ -62,7 +70,7 @@ class PLL_Table_String extends WP_List_Table {
62
  '<label class="screen-reader-text" for="cb-select-%1$s">%2$s</label><input id="cb-select-%1$s" type="checkbox" name="strings[]" value="%1$s" %3$s />',
63
  esc_attr( $item['row'] ),
64
  /* translators: accessibility text, %s is a string potentially in any language */
65
- sprintf( __( 'Select %s' ), format_to_edit( $item['string'] ) ),
66
  empty( $item['icl'] ) ? 'disabled' : '' // Only strings registered with WPML API can be removed
67
  );
68
  }
@@ -150,6 +158,29 @@ class PLL_Table_String extends WP_List_Table {
150
  return 'string';
151
  }
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  /**
154
  * Sort items
155
  *
@@ -160,8 +191,15 @@ class PLL_Table_String extends WP_List_Table {
160
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
161
  */
162
  protected function usort_reorder( $a, $b ) {
163
- $result = strcmp( $a[ $_GET['orderby'] ], $b[ $_GET['orderby'] ] ); // determine sort order
164
- return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // send final sort direction to usort
 
 
 
 
 
 
 
165
  }
166
 
167
  /**
@@ -170,38 +208,47 @@ class PLL_Table_String extends WP_List_Table {
170
  * @since 0.6
171
  */
172
  public function prepare_items() {
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  $data = $this->strings;
174
 
175
- // Filter for search string
176
- $s = empty( $_GET['s'] ) ? '' : wp_unslash( $_GET['s'] );
177
- foreach ( $data as $key => $row ) {
178
- if ( ( -1 !== $this->selected_group && $row['context'] !== $this->selected_group ) || ( ! empty( $s ) && stripos( $row['name'], $s ) === false && stripos( $row['string'], $s ) === false ) ) {
179
- unset( $data[ $key ] );
180
- }
181
  }
182
 
183
- // Load translations
184
- foreach ( $this->languages as $language ) {
185
- // Filters by language if requested
186
- if ( ( $lg = get_user_meta( get_current_user_id(), 'pll_filter_content', true ) ) && $language->slug !== $lg ) {
187
- continue;
188
- }
189
 
190
- $mo = new PLL_MO();
191
- $mo->import_from_db( $language );
192
  foreach ( $data as $key => $row ) {
193
- $data[ $key ]['translations'][ $language->slug ] = $mo->translate( $row['string'] );
194
- $data[ $key ]['row'] = $key; // Store the row number for convenience
 
195
  }
196
  }
197
 
 
 
 
 
198
  $per_page = $this->get_items_per_page( 'pll_strings_per_page' );
199
  $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
200
 
201
- if ( ! empty( $_GET['orderby'] ) ) { // No sort by default
202
- usort( $data, array( $this, 'usort_reorder' ) );
203
- }
204
-
205
  $total_items = count( $data );
206
  $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page );
207
 
@@ -212,6 +259,15 @@ class PLL_Table_String extends WP_List_Table {
212
  'total_pages' => ceil( $total_items / $per_page ),
213
  )
214
  );
 
 
 
 
 
 
 
 
 
215
  }
216
 
217
  /**
@@ -234,7 +290,7 @@ class PLL_Table_String extends WP_List_Table {
234
  * @return string|false The action name or False if no action was selected
235
  */
236
  public function current_action() {
237
- return empty( $_POST['submit'] ) ? parent::current_action() : false;
238
  }
239
 
240
  /**
@@ -258,7 +314,7 @@ class PLL_Table_String extends WP_List_Table {
258
  echo '<select id="select-group" name="group">' . "\n";
259
  printf(
260
  '<option value="-1"%s>%s</option>' . "\n",
261
- -1 === $this->group_selected ? ' selected="selected"' : '',
262
  esc_html__( 'View all groups', 'polylang' )
263
  );
264
 
@@ -266,13 +322,13 @@ class PLL_Table_String extends WP_List_Table {
266
  printf(
267
  '<option value="%s"%s>%s</option>' . "\n",
268
  esc_attr( urlencode( $group ) ),
269
- $this->selected_group === $group ? ' selected="selected"' : '',
270
  esc_html( $group )
271
  );
272
  }
273
  echo '</select>' . "\n";
274
 
275
- submit_button( __( 'Filter' ), 'button', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
276
  echo '</div>';
277
  }
278
 
@@ -294,7 +350,7 @@ class PLL_Table_String extends WP_List_Table {
294
  $mo = new PLL_MO();
295
  $mo->import_from_db( $language );
296
 
297
- foreach ( $_POST['translation'][ $language->slug ] as $key => $translation ) {
298
  /**
299
  * Filter the string translation before it is saved in DB
300
  * Allows to sanitize strings registered with pll_register_string
@@ -333,7 +389,7 @@ class PLL_Table_String extends WP_List_Table {
333
 
334
  // Unregisters strings registered through WPML API
335
  if ( $this->current_action() === 'delete' && ! empty( $_POST['strings'] ) && function_exists( 'icl_unregister_string' ) ) {
336
- foreach ( $_POST['strings'] as $key ) {
337
  icl_unregister_string( $this->strings[ $key ]['context'], $this->strings[ $key ]['name'] );
338
  }
339
  }
31
  $this->languages = $languages;
32
  $this->strings = PLL_Admin_Strings::get_strings();
33
  $this->groups = array_unique( wp_list_pluck( $this->strings, 'context' ) );
34
+
35
+ $this->selected_group = -1;
36
+
37
+ if ( ! empty( $_GET['group'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
38
+ $group = sanitize_text_field( wp_unslash( $_GET['group'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
39
+ if ( in_array( $group, $this->groups ) ) {
40
+ $this->selected_group = $group;
41
+ }
42
+ }
43
 
44
  add_action( 'mlang_action_string-translation', array( $this, 'save_translations' ) );
45
  }
70
  '<label class="screen-reader-text" for="cb-select-%1$s">%2$s</label><input id="cb-select-%1$s" type="checkbox" name="strings[]" value="%1$s" %3$s />',
71
  esc_attr( $item['row'] ),
72
  /* translators: accessibility text, %s is a string potentially in any language */
73
+ sprintf( __( 'Select %s', 'polylang' ), format_to_edit( $item['string'] ) ),
74
  empty( $item['icl'] ) ? 'disabled' : '' // Only strings registered with WPML API can be removed
75
  );
76
  }
158
  return 'string';
159
  }
160
 
161
+ /**
162
+ * Search for a string in translations. Case insensitive.
163
+ *
164
+ * @since 2.6
165
+ *
166
+ * @param array $mos An array of PLL_MO objects
167
+ * @param string $s Searched string
168
+ * @return array Found strings
169
+ */
170
+ protected function search_in_translations( $mos, $s ) {
171
+ $founds = array();
172
+
173
+ foreach ( $mos as $mo ) {
174
+ foreach ( wp_list_pluck( $mo->entries, 'translations' ) as $string => $translation ) {
175
+ if ( false !== stripos( $translation[0], $s ) ) {
176
+ $founds[] = $string;
177
+ }
178
+ }
179
+ }
180
+
181
+ return array_unique( $founds );
182
+ }
183
+
184
  /**
185
  * Sort items
186
  *
191
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
192
  */
193
  protected function usort_reorder( $a, $b ) {
194
+ if ( ! empty( $_GET['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
195
+ $orderby = sanitize_key( $_GET['orderby'] ); // phpcs:ignore WordPress.Security.NonceVerification
196
+ if ( isset( $a[ $orderby ], $b[ $orderby ] ) ) {
197
+ $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order
198
+ return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification
199
+ }
200
+ }
201
+
202
+ return 0;
203
  }
204
 
205
  /**
208
  * @since 0.6
209
  */
210
  public function prepare_items() {
211
+ // Is admin language filter active?
212
+ if ( $lg = get_user_meta( get_current_user_id(), 'pll_filter_content', true ) ) {
213
+ $languages = wp_list_filter( $this->languages, array( 'slug' => $lg ) );
214
+ } else {
215
+ $languages = $this->languages;
216
+ }
217
+
218
+ // Load translations
219
+ foreach ( $languages as $language ) {
220
+ $mo[ $language->slug ] = new PLL_MO();
221
+ $mo[ $language->slug ]->import_from_db( $language );
222
+ }
223
+
224
  $data = $this->strings;
225
 
226
+ // Filter by selected group
227
+ if ( -1 !== $this->selected_group ) {
228
+ $data = wp_list_filter( $data, array( 'context' => $this->selected_group ) );
 
 
 
229
  }
230
 
231
+ // Filter by searched string
232
+ $s = empty( $_GET['s'] ) ? '' : wp_unslash( $_GET['s'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
233
+
234
+ if ( ! empty( $s ) ) {
235
+ // Search in translations
236
+ $in_translations = $this->search_in_translations( $mo, $s );
237
 
 
 
238
  foreach ( $data as $key => $row ) {
239
+ if ( stripos( $row['name'], $s ) === false && stripos( $row['string'], $s ) === false && ! in_array( $row['string'], $in_translations ) ) {
240
+ unset( $data[ $key ] );
241
+ }
242
  }
243
  }
244
 
245
+ // Sorting
246
+ uasort( $data, array( $this, 'usort_reorder' ) );
247
+
248
+ // Paging
249
  $per_page = $this->get_items_per_page( 'pll_strings_per_page' );
250
  $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
251
 
 
 
 
 
252
  $total_items = count( $data );
253
  $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page );
254
 
259
  'total_pages' => ceil( $total_items / $per_page ),
260
  )
261
  );
262
+
263
+ // Translate strings
264
+ // Kept for the end as it is a slow process
265
+ foreach ( $languages as $language ) {
266
+ foreach ( $this->items as $key => $row ) {
267
+ $this->items[ $key ]['translations'][ $language->slug ] = $mo[ $language->slug ]->translate( $row['string'] );
268
+ $this->items[ $key ]['row'] = $key; // Store the row number for convenience
269
+ }
270
+ }
271
  }
272
 
273
  /**
290
  * @return string|false The action name or False if no action was selected
291
  */
292
  public function current_action() {
293
+ return empty( $_POST['submit'] ) ? parent::current_action() : false; // phpcs:ignore WordPress.Security.NonceVerification
294
  }
295
 
296
  /**
314
  echo '<select id="select-group" name="group">' . "\n";
315
  printf(
316
  '<option value="-1"%s>%s</option>' . "\n",
317
+ selected( $this->group_selected, -1, false ),
318
  esc_html__( 'View all groups', 'polylang' )
319
  );
320
 
322
  printf(
323
  '<option value="%s"%s>%s</option>' . "\n",
324
  esc_attr( urlencode( $group ) ),
325
+ selected( $this->selected_group, $group, false ),
326
  esc_html( $group )
327
  );
328
  }
329
  echo '</select>' . "\n";
330
 
331
+ submit_button( __( 'Filter', 'polylang' ), 'button', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
332
  echo '</div>';
333
  }
334
 
350
  $mo = new PLL_MO();
351
  $mo->import_from_db( $language );
352
 
353
+ foreach ( $_POST['translation'][ $language->slug ] as $key => $translation ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
354
  /**
355
  * Filter the string translation before it is saved in DB
356
  * Allows to sanitize strings registered with pll_register_string
389
 
390
  // Unregisters strings registered through WPML API
391
  if ( $this->current_action() === 'delete' && ! empty( $_POST['strings'] ) && function_exists( 'icl_unregister_string' ) ) {
392
+ foreach ( array_map( 'sanitize_key', $_POST['strings'] ) as $key ) {
393
  icl_unregister_string( $this->strings[ $key ]['context'], $this->strings[ $key ]['name'] );
394
  }
395
  }
settings/view-about.php CHANGED
@@ -12,7 +12,7 @@ if ( ! defined( 'ABSPATH' ) ) {
12
  <?php
13
  printf(
14
  /* translators: %1$s is link start tag, %2$s is link end tag. */
15
- esc_html__( 'Polylang is provided with an extensive %1$sdocumentation%2$s (in English only). It includes information on how to set up your multilingual site and use it on a daily basis, a FAQ, as well as a documentation for developers to adapt their plugins and themes.', 'polylang' ),
16
  '<a href="https://polylang.pro/doc/">',
17
  '</a>'
18
  );
@@ -27,14 +27,3 @@ if ( ! defined( 'ABSPATH' ) ) {
27
  }
28
  ?>
29
  </p>
30
- <p>
31
- <?php
32
- printf(
33
- /* translators: %1$s is link start tag, %2$s is link end tag. */
34
- esc_html__( 'Polylang is released under the same license as WordPress, the %1$sGPL%2$s.', 'polylang' ),
35
- '<a href="http://wordpress.org/about/gpl/">',
36
- '</a>'
37
- );
38
- ?>
39
- </p>
40
-
12
  <?php
13
  printf(
14
  /* translators: %1$s is link start tag, %2$s is link end tag. */
15
+ esc_html__( 'Polylang is provided with an extensive %1$sdocumentation%2$s (in English). It includes information on how to set up your multilingual site and use it on a daily basis; FAQs, and documentation for developers to adapt their plugins and themes.', 'polylang' ),
16
  '<a href="https://polylang.pro/doc/">',
17
  '</a>'
18
  );
27
  }
28
  ?>
29
  </p>
 
 
 
 
 
 
 
 
 
 
 
settings/view-tab-lang.php CHANGED
@@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
28
  <div class="col-wrap">
29
 
30
  <div class="form-wrap">
31
- <h3><?php echo ! empty( $edit_lang ) ? esc_html__( 'Edit language', 'polylang' ) : esc_html__( 'Add new language', 'polylang' ); ?></h3>
32
  <?php
33
  // Displays the add ( or edit ) language form
34
  // Adds noheader=true in the action url to allow using wp_redirect when processing the form
@@ -106,12 +106,12 @@ if ( ! defined( 'ABSPATH' ) ) {
106
  <?php
107
  printf(
108
  '<label><input name="rtl" type="radio" value="0" %s /> %s</label>',
109
- ! empty( $edit_lang ) && $edit_lang->is_rtl ? '' : 'checked="checked"',
110
  esc_html__( 'left to right', 'polylang' )
111
  );
112
  printf(
113
  '<label><input name="rtl" type="radio" value="1" %s /> %s</label>',
114
- ! empty( $edit_lang ) && $edit_lang->is_rtl ? 'checked="checked"' : '',
115
  esc_html__( 'right to left', 'polylang' )
116
  );
117
  ?>
@@ -123,7 +123,7 @@ if ( ! defined( 'ABSPATH' ) ) {
123
  <select name="flag" id="flag_list">
124
  <option value=""></option>
125
  <?php
126
- include PLL_SETTINGS_INC . '/flags.php';
127
  foreach ( $flags as $code => $label ) {
128
  /** This filter is documented in include/language.php */
129
  $flag = apply_filters( 'pll_flag', array( 'url' => plugins_url( "/flags/{$code}.png", POLYLANG_FILE ) ), $code );
@@ -132,9 +132,9 @@ if ( ! defined( 'ABSPATH' ) ) {
132
  '<option value="%s" data-url="%s"%s%s%s>%s</option>' . "\n",
133
  esc_attr( $code ),
134
  esc_url( $flag['url'] ),
135
- empty( $flag['width'] ) ? '' : sprintf( ' data-width="%s"', (int) $flag['width'] ),
136
- empty( $flag['height'] ) ? '' : sprintf( ' data-height="%s"', (int) $flag['height'] ),
137
- isset( $edit_lang->flag_code ) && $edit_lang->flag_code == $code ? ' selected="selected"' : '',
138
  esc_html( $label )
139
  );
140
  }
@@ -172,7 +172,7 @@ if ( ! defined( 'ABSPATH' ) ) {
172
  do_action( 'pll_language_add_form_fields' );
173
  }
174
 
175
- submit_button( ! empty( $edit_lang ) ? __( 'Update' ) : __( 'Add new language', 'polylang' ) ); // since WP 3.1
176
  ?>
177
  </form>
178
  </div><!-- form-wrap -->
28
  <div class="col-wrap">
29
 
30
  <div class="form-wrap">
31
+ <h2><?php echo ! empty( $edit_lang ) ? esc_html__( 'Edit language', 'polylang' ) : esc_html__( 'Add new language', 'polylang' ); ?></h2>
32
  <?php
33
  // Displays the add ( or edit ) language form
34
  // Adds noheader=true in the action url to allow using wp_redirect when processing the form
106
  <?php
107
  printf(
108
  '<label><input name="rtl" type="radio" value="0" %s /> %s</label>',
109
+ checked( ! empty( $edit_lang ) && $edit_lang->is_rtl, false, false ),
110
  esc_html__( 'left to right', 'polylang' )
111
  );
112
  printf(
113
  '<label><input name="rtl" type="radio" value="1" %s /> %s</label>',
114
+ checked( ! empty( $edit_lang ) && $edit_lang->is_rtl, true, false ),
115
  esc_html__( 'right to left', 'polylang' )
116
  );
117
  ?>
123
  <select name="flag" id="flag_list">
124
  <option value=""></option>
125
  <?php
126
+ $flags = include PLL_SETTINGS_INC . '/flags.php';
127
  foreach ( $flags as $code => $label ) {
128
  /** This filter is documented in include/language.php */
129
  $flag = apply_filters( 'pll_flag', array( 'url' => plugins_url( "/flags/{$code}.png", POLYLANG_FILE ) ), $code );
132
  '<option value="%s" data-url="%s"%s%s%s>%s</option>' . "\n",
133
  esc_attr( $code ),
134
  esc_url( $flag['url'] ),
135
+ ( empty( $flag['width'] ) ? '' : sprintf( ' data-width="%s"', (int) $flag['width'] ) ),
136
+ ( empty( $flag['height'] ) ? '' : sprintf( ' data-height="%s"', (int) $flag['height'] ) ),
137
+ selected( isset( $edit_lang->flag_code ) && $edit_lang->flag_code === $code, true, false ),
138
  esc_html( $label )
139
  );
140
  }
172
  do_action( 'pll_language_add_form_fields' );
173
  }
174
 
175
+ submit_button( ! empty( $edit_lang ) ? __( 'Update', 'polylang' ) : __( 'Add new language', 'polylang' ) ); // since WP 3.1
176
  ?>
177
  </form>
178
  </div><!-- form-wrap -->
uninstall.php CHANGED
@@ -46,9 +46,23 @@ class PLL_Uninstall {
46
  return;
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  // Suppress data of the old model < 1.2
50
  // FIXME: to remove when support for v1.1.6 will be dropped
51
- global $wpdb;
52
  $wpdb->termmeta = $wpdb->prefix . 'termmeta'; // registers the termmeta table in wpdb
53
 
54
  // Do nothing if the termmeta table does not exists
@@ -70,7 +84,6 @@ class PLL_Uninstall {
70
  // Delete users options
71
  foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
72
  delete_user_meta( $user_id, 'pll_filter_content' );
73
- delete_user_meta( $user_id, 'pll_duplicate_content' );
74
  foreach ( $languages as $lang ) {
75
  delete_user_meta( $user_id, 'description_' . $lang->slug );
76
  }
@@ -138,7 +151,6 @@ class PLL_Uninstall {
138
  // Delete transients
139
  delete_transient( 'pll_languages_list' );
140
  delete_transient( 'pll_upgrade_1_4' );
141
- delete_transient( 'pll_translated_slugs' );
142
  }
143
  }
144
 
46
  return;
47
  }
48
 
49
+ global $wpdb;
50
+
51
+ // Executes each module's uninstall script, if it exists
52
+ $pll_modules_dir = dirname( __FILE__ ) . '/modules';
53
+ opendir( $pll_modules_dir );
54
+ while ( ( $module = readdir() ) != false ) {
55
+ if ( substr( $module, 0, 1 ) !== '.' ) {
56
+ $uninstall_script = $pll_modules_dir . '/' . $module . '/uninstall.php';
57
+ if ( file_exists( $uninstall_script ) ) {
58
+ require $uninstall_script;
59
+ }
60
+ }
61
+ }
62
+ closedir();
63
+
64
  // Suppress data of the old model < 1.2
65
  // FIXME: to remove when support for v1.1.6 will be dropped
 
66
  $wpdb->termmeta = $wpdb->prefix . 'termmeta'; // registers the termmeta table in wpdb
67
 
68
  // Do nothing if the termmeta table does not exists
84
  // Delete users options
85
  foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
86
  delete_user_meta( $user_id, 'pll_filter_content' );
 
87
  foreach ( $languages as $lang ) {
88
  delete_user_meta( $user_id, 'description_' . $lang->slug );
89
  }
151
  // Delete transients
152
  delete_transient( 'pll_languages_list' );
153
  delete_transient( 'pll_upgrade_1_4' );
 
154
  }
155
  }
156