Polylang - Version 3.3

Version Description

(2022-11-28) =

  • Requires WP 5.7 as minimum version
  • Pro: Allow to export and import XLIFF files for posts
  • Pro: Honor the provided context for the navigation language switcher block.
  • Pro: Remove the parent hyperlink in the navigation language switcher block.
  • Pro: Add spacing between flag and name in the navigation language switcher block.
  • Pro: Disallow some special characters in translated slugs to avoid 404 errors.
  • Pro: Fix string translation not imported when the original is registered but has never been saved in database.
  • Pro: Fix string translation not imported when it includes an html entity.
  • Pro: Fix navigation language switcher block rendering in block editor.
  • Pro: Fix navigation language switcher may be displayed wrong color.
  • Translate the post pages in get_post_type_archive_link() on admin side too. #1000
  • Enable the block editor in page for posts translations to match the WordPress behavior since version 5.8 #1002
  • Improve the site health report #1062 #1076
  • Set the current language when saving a post #1065
  • The search block is now filtered by language #1081
  • Display slug of CPT and taxonomies in Custom post types and Taxonomies settings. Props @nicomollet #1112
  • Add support for wpml-config.xml to MU plugins #1140 Props Jeremy Simkins
  • Fix some deprecated notices fired by PHP 8.1 #975
  • Fix some missing canonical redirect taxonomies #1074
  • Fix redirect when permalink structure has no trailing slash #1080
  • Fix language switcher in legacy navigation menu widget not rendered in widgets block editor #1083
  • Fix language in tax query when an OR relation is used #1098
  • Fix parent of translated category removed when assigning an untranslated parent #1105
  • Fix is_front_page() when a static front page is not translated #1123
  • Yoast SEO: Fix posts without language displayed in the sitemap #1103
  • Yoast SEO: Avoid syncing robots meta. #1118
Download this release

Release Info

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

Code changes from version 3.2.8 to 3.3

Files changed (92) hide show
  1. admin/admin-base.php +48 -20
  2. admin/admin-block-editor.php +1 -1
  3. admin/admin-classic-editor.php +3 -3
  4. admin/admin-default-term.php +1 -1
  5. admin/admin-filters-columns.php +2 -2
  6. admin/admin-filters-media.php +1 -1
  7. admin/admin-filters-post-base.php +9 -2
  8. admin/admin-filters-post.php +2 -2
  9. admin/admin-filters-term.php +92 -72
  10. admin/admin-model.php +8 -8
  11. admin/admin-static-pages.php +1 -47
  12. admin/admin-strings.php +1 -1
  13. admin/admin.php +10 -10
  14. frontend/canonical.php +266 -0
  15. frontend/choose-lang.php +23 -14
  16. frontend/frontend-auto-translate.php +6 -3
  17. frontend/frontend-filters-links.php +30 -261
  18. frontend/frontend-filters-search.php +23 -11
  19. frontend/frontend-filters-widgets.php +1 -1
  20. frontend/frontend-filters.php +9 -1
  21. frontend/frontend-links.php +5 -9
  22. frontend/frontend-nav-menu.php +5 -1
  23. frontend/frontend-static-pages.php +7 -28
  24. frontend/frontend.php +18 -10
  25. include/base.php +3 -2
  26. include/cache.php +1 -1
  27. include/class-polylang.php +4 -4
  28. include/crud-posts.php +10 -8
  29. include/crud-terms.php +89 -7
  30. include/db-tools.php +6 -5
  31. include/filters-links.php +7 -7
  32. include/filters.php +5 -5
  33. include/language.php +23 -14
  34. include/license.php +1 -1
  35. include/links-abstract-domain.php +11 -12
  36. include/links-default.php +20 -25
  37. include/links-directory.php +40 -44
  38. include/links-domain.php +14 -18
  39. include/links-model.php +19 -22
  40. include/links-permalinks.php +22 -22
  41. include/links-subdomain.php +11 -13
  42. include/links.php +7 -0
  43. include/model.php +39 -10
  44. include/olt-manager.php +5 -5
  45. include/query.php +49 -7
  46. include/rest-request.php +32 -15
  47. include/static-pages.php +40 -10
  48. include/switcher.php +1 -1
  49. include/translate-option.php +1 -1
  50. include/translated-object.php +33 -23
  51. include/translated-term.php +0 -20
  52. include/walker-dropdown.php +1 -1
  53. include/walker-list.php +1 -1
  54. include/widget-languages.php +4 -1
  55. install/t15s.php +2 -2
  56. install/upgrade.php +4 -1
  57. integrations/integrations.php +2 -1
  58. integrations/wp-importer/wp-import.php +1 -1
  59. integrations/wp-offload-media/as3cf.php +1 -1
  60. integrations/wpseo/wpseo.php +58 -29
  61. js/build/block-editor.js +58 -63
  62. js/build/block-editor.min.js +1 -1
  63. js/build/classic-editor.js +47 -40
  64. js/build/classic-editor.min.js +1 -1
  65. js/build/post.js +67 -64
  66. js/build/post.min.js +1 -1
  67. js/build/term.js +22 -16
  68. js/build/term.min.js +1 -1
  69. modules/site-health/admin-site-health.php +121 -45
  70. modules/sitemaps/sitemaps.php +0 -13
  71. modules/sync/sync-metas.php +8 -7
  72. modules/sync/sync-tax.php +2 -2
  73. modules/sync/sync.php +1 -1
  74. modules/wizard/wizard.php +1 -1
  75. modules/wpml/wpml-api.php +1 -1
  76. modules/wpml/wpml-compat.php +1 -1
  77. modules/wpml/wpml-config.php +529 -113
  78. modules/wpml/wpml-legacy-api.php +7 -4
  79. polylang.php +4 -4
  80. readme.txt +32 -3
  81. settings/settings-browser.php +2 -0
  82. settings/settings-cpt.php +18 -4
  83. settings/settings-module.php +1 -1
  84. settings/settings-url.php +2 -2
  85. settings/settings.php +2 -2
  86. settings/table-string.php +7 -3
  87. uninstall.php +2 -2
  88. vendor/composer/InstalledVersions.php +352 -0
  89. vendor/composer/autoload_classmap.php +1 -0
  90. vendor/composer/autoload_static.php +1 -0
  91. vendor/composer/installed.php +23 -0
  92. vendor/composer/platform_check.php +26 -0
admin/admin-base.php CHANGED
@@ -12,46 +12,46 @@ abstract class PLL_Admin_Base extends PLL_Base {
12
  /**
13
  * Current language (used to filter the content).
14
  *
15
- * @var PLL_Language
16
  */
17
  public $curlang;
18
 
19
  /**
20
  * Language selected in the admin language filter.
21
  *
22
- * @var PLL_Language
23
  */
24
  public $filter_lang;
25
 
26
  /**
27
  * Preferred language to assign to new contents.
28
  *
29
- * @var PLL_Language
30
  */
31
  public $pref_lang;
32
 
33
  /**
34
- * @var PLL_Filters_Links
35
  */
36
  public $filters_links;
37
 
38
  /**
39
- * @var PLL_Admin_Links
40
  */
41
  public $links;
42
 
43
  /**
44
- * @var PLL_Admin_Notices
45
  */
46
  public $notices;
47
 
48
  /**
49
- * @var PLL_Admin_Static_Pages
50
  */
51
  public $static_pages;
52
 
53
  /**
54
- * @var PLL_Admin_Default_Term
55
  */
56
  public $default_term;
57
 
@@ -123,7 +123,7 @@ abstract class PLL_Admin_Base extends PLL_Base {
123
 
124
  // Only if at least one language has been created
125
  if ( $this->model->get_languages_list() ) {
126
- $tabs['strings'] = __( 'Strings translations', 'polylang' );
127
  }
128
 
129
  $tabs['settings'] = __( 'Settings', 'polylang' );
@@ -178,6 +178,8 @@ abstract class PLL_Admin_Base extends PLL_Base {
178
  'widgets' => array( array( 'widgets' ), array( 'jquery' ), 0, 0 ),
179
  );
180
 
 
 
181
  if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
182
  $scripts['post'] = array( array( 'edit', 'upload' ), array( 'jquery', 'wp-ajax-response' ), 0, 1 );
183
 
@@ -187,9 +189,11 @@ abstract class PLL_Admin_Base extends PLL_Base {
187
  }
188
 
189
  // Block editor with legacy metabox in WP 5.0+.
190
- if ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin() ) {
191
- $scripts['block-editor'] = array( array( 'post' ), array( 'jquery', 'wp-ajax-response', 'wp-api-fetch', 'jquery-ui-dialog', 'wp-i18n' ), 0, 1 );
192
- }
 
 
193
  }
194
 
195
  if ( ! empty( $screen->taxonomy ) && $this->model->is_translated_taxonomy( $screen->taxonomy ) ) {
@@ -208,7 +212,20 @@ abstract class PLL_Admin_Base extends PLL_Base {
208
  wp_register_style( 'polylang_admin', plugins_url( '/css/build/admin' . $suffix . '.css', POLYLANG_ROOT_FILE ), array( 'wp-jquery-ui-dialog' ), POLYLANG_VERSION );
209
  wp_enqueue_style( 'polylang_dialog', plugins_url( '/css/build/dialog' . $suffix . '.css', POLYLANG_ROOT_FILE ), array( 'polylang_admin' ), POLYLANG_VERSION );
210
 
211
- $this->localize_scripts();
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  }
213
 
214
  /**
@@ -222,18 +239,27 @@ abstract class PLL_Admin_Base extends PLL_Base {
222
  if ( $this->model->get_languages_list() ) {
223
  $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
224
  wp_enqueue_script( 'pll_widgets', plugins_url( '/js/build/widgets' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true );
225
- $this->localize_scripts();
226
  }
227
  }
228
 
229
  /**
230
- * Localize scripts.
 
231
  *
232
- * @since 2.4.0
233
  *
234
  * @return void
235
  */
236
- public function localize_scripts() {
 
 
 
 
 
 
 
 
237
  if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
238
  wp_localize_script(
239
  'pll_widgets',
@@ -345,7 +371,9 @@ abstract class PLL_Admin_Base extends PLL_Base {
345
  // Edit Post
346
  if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
347
  $this->curlang = $lang;
348
- } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && $this->model->is_translated_post_type( get_post_type( (int) $_GET['post'] ) ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
 
 
349
  $this->curlang = $lang;
350
  } 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
351
  $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
@@ -354,8 +382,8 @@ abstract class PLL_Admin_Base extends PLL_Base {
354
  // Edit Term
355
  elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
356
  $this->curlang = $lang;
357
- } elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
358
- if ( isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
359
  $this->curlang = $lang;
360
  } elseif ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
361
  $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
12
  /**
13
  * Current language (used to filter the content).
14
  *
15
+ * @var PLL_Language|null
16
  */
17
  public $curlang;
18
 
19
  /**
20
  * Language selected in the admin language filter.
21
  *
22
+ * @var PLL_Language|null
23
  */
24
  public $filter_lang;
25
 
26
  /**
27
  * Preferred language to assign to new contents.
28
  *
29
+ * @var PLL_Language|null
30
  */
31
  public $pref_lang;
32
 
33
  /**
34
+ * @var PLL_Filters_Links|null
35
  */
36
  public $filters_links;
37
 
38
  /**
39
+ * @var PLL_Admin_Links|null
40
  */
41
  public $links;
42
 
43
  /**
44
+ * @var PLL_Admin_Notices|null
45
  */
46
  public $notices;
47
 
48
  /**
49
+ * @var PLL_Admin_Static_Pages|null
50
  */
51
  public $static_pages;
52
 
53
  /**
54
+ * @var PLL_Admin_Default_Term|null
55
  */
56
  public $default_term;
57
 
123
 
124
  // Only if at least one language has been created
125
  if ( $this->model->get_languages_list() ) {
126
+ $tabs['strings'] = __( 'Translations', 'polylang' );
127
  }
128
 
129
  $tabs['settings'] = __( 'Settings', 'polylang' );
178
  'widgets' => array( array( 'widgets' ), array( 'jquery' ), 0, 0 ),
179
  );
180
 
181
+ $block_screens = array( 'widgets', 'site-editor' );
182
+
183
  if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) {
184
  $scripts['post'] = array( array( 'edit', 'upload' ), array( 'jquery', 'wp-ajax-response' ), 0, 1 );
185
 
189
  }
190
 
191
  // Block editor with legacy metabox in WP 5.0+.
192
+ $block_screens[] = 'post';
193
+ }
194
+
195
+ if ( $this->is_block_editor( $screen ) ) {
196
+ $scripts['block-editor'] = array( $block_screens, array( 'jquery', 'wp-ajax-response', 'wp-api-fetch', 'jquery-ui-dialog', 'wp-i18n' ), 0, 1 );
197
  }
198
 
199
  if ( ! empty( $screen->taxonomy ) && $this->model->is_translated_taxonomy( $screen->taxonomy ) ) {
212
  wp_register_style( 'polylang_admin', plugins_url( '/css/build/admin' . $suffix . '.css', POLYLANG_ROOT_FILE ), array( 'wp-jquery-ui-dialog' ), POLYLANG_VERSION );
213
  wp_enqueue_style( 'polylang_dialog', plugins_url( '/css/build/dialog' . $suffix . '.css', POLYLANG_ROOT_FILE ), array( 'polylang_admin' ), POLYLANG_VERSION );
214
 
215
+ $this->add_inline_scripts();
216
+ }
217
+
218
+ /**
219
+ * Tells whether or not the given screen is block editor kind.
220
+ * e.g. widget, site or post editor.
221
+ *
222
+ * @since 3.3
223
+ *
224
+ * @param WP_Screen $screen Screen object.
225
+ * @return bool True if the screen is a block editor, false otherwise.
226
+ */
227
+ protected function is_block_editor( $screen ) {
228
+ return method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin();
229
  }
230
 
231
  /**
239
  if ( $this->model->get_languages_list() ) {
240
  $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
241
  wp_enqueue_script( 'pll_widgets', plugins_url( '/js/build/widgets' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true );
242
+ $this->add_inline_scripts();
243
  }
244
  }
245
 
246
  /**
247
+ * Adds inline scripts to set the default language in JS
248
+ * and localizes scripts.
249
  *
250
+ * @since 3.3
251
  *
252
  * @return void
253
  */
254
+ private function add_inline_scripts() {
255
+ if ( wp_script_is( 'pll_block-editor', 'enqueued' ) ) {
256
+ $default_lang_script = 'const pllDefaultLanguage = "' . $this->options['default_lang'] . '";';
257
+ wp_add_inline_script(
258
+ 'pll_block-editor',
259
+ $default_lang_script,
260
+ 'before'
261
+ );
262
+ }
263
  if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) {
264
  wp_localize_script(
265
  'pll_widgets',
371
  // Edit Post
372
  if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
373
  $this->curlang = $lang;
374
+ } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && false !== get_post_type( (int) $_GET['post'] ) && $this->model->is_translated_post_type( get_post_type( (int) $_GET['post'] ) ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
375
+ $this->curlang = $lang;
376
+ } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_POST['post_ID'] ) && false !== get_post_type( (int) $_POST['post_ID'] ) && $this->model->is_translated_post_type( get_post_type( (int) $_POST['post_ID'] ) ) && $lang = $this->model->post->get_language( (int) $_POST['post_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
377
  $this->curlang = $lang;
378
  } 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
379
  $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
382
  // Edit Term
383
  elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
384
  $this->curlang = $lang;
385
+ } elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_REQUEST['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_REQUEST['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
386
+ if ( isset( $_REQUEST['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
387
  $this->curlang = $lang;
388
  } elseif ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
389
  $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
admin/admin-block-editor.php CHANGED
@@ -17,7 +17,7 @@ class PLL_Admin_Block_Editor {
17
  /**
18
  * Preferred language to assign to a new post.
19
  *
20
- * @var PLL_Language
21
  */
22
  protected $pref_lang;
23
 
17
  /**
18
  * Preferred language to assign to a new post.
19
  *
20
+ * @var PLL_Language|null
21
  */
22
  protected $pref_lang;
23
 
admin/admin-classic-editor.php CHANGED
@@ -15,21 +15,21 @@ class PLL_Admin_Classic_Editor {
15
  public $model;
16
 
17
  /**
18
- * @var PLL_Admin_Links
19
  */
20
  public $links;
21
 
22
  /**
23
  * Current language (used to filter the content).
24
  *
25
- * @var PLL_Language
26
  */
27
  public $curlang;
28
 
29
  /**
30
  * Preferred language to assign to new contents.
31
  *
32
- * @var PLL_Language
33
  */
34
  public $pref_lang;
35
 
15
  public $model;
16
 
17
  /**
18
+ * @var PLL_Admin_Links|null
19
  */
20
  public $links;
21
 
22
  /**
23
  * Current language (used to filter the content).
24
  *
25
+ * @var PLL_Language|null
26
  */
27
  public $curlang;
28
 
29
  /**
30
  * Preferred language to assign to new contents.
31
  *
32
+ * @var PLL_Language|null
33
  */
34
  public $pref_lang;
35
 
admin/admin-default-term.php CHANGED
@@ -20,7 +20,7 @@ class PLL_Admin_Default_Term {
20
  /**
21
  * Preferred language to assign to new contents.
22
  *
23
- * @var PLL_Language
24
  */
25
  protected $pref_lang;
26
 
20
  /**
21
  * Preferred language to assign to new contents.
22
  *
23
+ * @var PLL_Language|null
24
  */
25
  protected $pref_lang;
26
 
admin/admin-filters-columns.php CHANGED
@@ -16,14 +16,14 @@ class PLL_Admin_Filters_Columns {
16
  public $model;
17
 
18
  /**
19
- * @var PLL_Admin_Links
20
  */
21
  public $links;
22
 
23
  /**
24
  * Language selected in the admin language filter.
25
  *
26
- * @var PLL_Language
27
  */
28
  public $filter_lang;
29
 
16
  public $model;
17
 
18
  /**
19
+ * @var PLL_Admin_Links|null
20
  */
21
  public $links;
22
 
23
  /**
24
  * Language selected in the admin language filter.
25
  *
26
+ * @var PLL_Language|null
27
  */
28
  public $filter_lang;
29
 
admin/admin-filters-media.php CHANGED
@@ -11,7 +11,7 @@
11
  */
12
  class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
13
  /**
14
- * @var PLL_CRUD_Posts
15
  */
16
  public $posts;
17
 
11
  */
12
  class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
13
  /**
14
+ * @var PLL_CRUD_Posts|null
15
  */
16
  public $posts;
17
 
admin/admin-filters-post-base.php CHANGED
@@ -15,17 +15,24 @@ abstract class PLL_Admin_Filters_Post_Base {
15
  public $model;
16
 
17
  /**
18
- * @var PLL_Links
19
  */
20
  public $links;
21
 
22
  /**
23
  * Language selected in the admin language filter.
24
  *
25
- * @var PLL_Language
26
  */
27
  public $filter_lang;
28
 
 
 
 
 
 
 
 
29
  /**
30
  * Constructor: setups filters and actions
31
  *
15
  public $model;
16
 
17
  /**
18
+ * @var PLL_Links|null
19
  */
20
  public $links;
21
 
22
  /**
23
  * Language selected in the admin language filter.
24
  *
25
+ * @var PLL_Language|null
26
  */
27
  public $filter_lang;
28
 
29
+ /**
30
+ * Preferred language to assign to new contents.
31
+ *
32
+ * @var PLL_Language|null
33
+ */
34
+ public $pref_lang;
35
+
36
  /**
37
  * Constructor: setups filters and actions
38
  *
admin/admin-filters-post.php CHANGED
@@ -12,7 +12,7 @@ class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
12
  /**
13
  * Current language (used to filter the content).
14
  *
15
- * @var PLL_Language
16
  */
17
  public $curlang;
18
 
@@ -69,7 +69,7 @@ class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
69
  }
70
 
71
  if ( ! empty( $hierarchical_taxonomies ) ) {
72
- $terms = get_terms( $hierarchical_taxonomies, array( 'get' => 'all' ) );
73
  $term_languages = array();
74
 
75
  if ( is_array( $terms ) ) {
12
  /**
13
  * Current language (used to filter the content).
14
  *
15
+ * @var PLL_Language|null
16
  */
17
  public $curlang;
18
 
69
  }
70
 
71
  if ( ! empty( $hierarchical_taxonomies ) ) {
72
+ $terms = get_terms( array( 'taxonomy' => $hierarchical_taxonomies, 'get' => 'all' ) );
73
  $term_languages = array();
74
 
75
  if ( is_array( $terms ) ) {
admin/admin-filters-term.php CHANGED
@@ -22,35 +22,28 @@ class PLL_Admin_Filters_Term {
22
  public $model;
23
 
24
  /**
25
- * @var PLL_Admin_Links
26
  */
27
  public $links;
28
 
29
  /**
30
  * Language selected in the admin language filter.
31
  *
32
- * @var PLL_Language
33
  */
34
  public $filter_lang;
35
 
36
  /**
37
  * Preferred language to assign to the new terms.
38
  *
39
- * @var PLL_Language
40
  */
41
  public $pref_lang;
42
 
43
- /**
44
- * Stores the term name before creating a slug if needed.
45
- *
46
- * @var string
47
- */
48
- protected $pre_term_name;
49
-
50
  /**
51
  * Stores the current post_id when bulk editing posts.
52
  *
53
- * @var int
54
  */
55
  protected $post_id;
56
 
@@ -59,7 +52,7 @@ class PLL_Admin_Filters_Term {
59
  *
60
  * @since 2.8
61
  *
62
- * @var PLL_Admin_Default_Term
63
  */
64
  protected $default_term;
65
 
@@ -69,10 +62,10 @@ class PLL_Admin_Filters_Term {
69
  * @param object $polylang
70
  */
71
  public function __construct( &$polylang ) {
72
- $this->links = &$polylang->links;
73
- $this->model = &$polylang->model;
74
- $this->options = &$polylang->options;
75
- $this->pref_lang = &$polylang->pref_lang;
76
  $this->default_term = &$polylang->default_term;
77
 
78
  foreach ( $this->model->get_translated_taxonomies() as $tax ) {
@@ -88,8 +81,8 @@ class PLL_Admin_Filters_Term {
88
  add_action( 'create_term', array( $this, 'save_term' ), 900, 3 );
89
  add_action( 'edit_term', array( $this, 'save_term' ), 900, 3 ); // Late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast
90
  add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
91
- add_filter( 'pre_term_name', array( $this, 'pre_term_name' ) );
92
- add_filter( 'pre_term_slug', array( $this, 'pre_term_slug' ), 10, 2 );
93
 
94
  // Ajax response for edit term form
95
  add_action( 'wp_ajax_term_lang_choice', array( $this, 'term_lang_choice' ) );
@@ -258,7 +251,7 @@ class PLL_Admin_Filters_Term {
258
  }
259
 
260
  /**
261
- * Stores the current post_id when bulk editing posts for use in save_language and pre_term_slug
262
  *
263
  * @since 1.7
264
  *
@@ -421,58 +414,6 @@ class PLL_Admin_Filters_Term {
421
  }
422
  }
423
 
424
- /**
425
- * Stores the term name for use in pre_term_slug
426
- *
427
- * @since 0.9.5
428
- *
429
- * @param string $name term name
430
- * @return string unmodified term name
431
- */
432
- public function pre_term_name( $name ) {
433
- return $this->pre_term_name = $name;
434
- }
435
-
436
- /**
437
- * Creates the term slug in case the term already exists in another language
438
- *
439
- * @since 0.9.5
440
- *
441
- * @param string $slug
442
- * @param string $taxonomy
443
- * @return string
444
- */
445
- public function pre_term_slug( $slug, $taxonomy ) {
446
- $name = sanitize_title( $this->pre_term_name );
447
-
448
- // If the term already exists in another language
449
- if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
450
- if ( isset( $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
451
- $lang = $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
452
- }
453
-
454
- elseif ( isset( $_POST['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
455
- $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
456
- }
457
-
458
- // *Post* bulk edit, in case a new term is created
459
- elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
460
- // Bulk edit does not modify the language
461
- if ( -1 == $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification
462
- $lang = $this->model->post->get_language( $this->post_id );
463
- } else {
464
- $lang = $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
465
- }
466
- }
467
-
468
- if ( ! empty( $lang ) && ! $this->model->term_exists_by_slug( $name, $lang, $taxonomy ) ) {
469
- $slug = $name . '-' . $lang->slug;
470
- }
471
- }
472
-
473
- return $slug;
474
- }
475
-
476
  /**
477
  * Ajax response for edit term form
478
  *
@@ -576,7 +517,7 @@ class PLL_Admin_Filters_Term {
576
  }
577
 
578
  // It is more efficient to use one common query for all languages as soon as there are more than 2.
579
- $all_terms = get_terms( $taxonomy, 'hide_empty=0&lang=0&name__like=' . $s );
580
  if ( is_array( $all_terms ) ) {
581
  foreach ( $all_terms as $term ) {
582
  $lang = $this->model->term->get_language( $term->term_id );
@@ -661,4 +602,83 @@ class PLL_Admin_Filters_Term {
661
  $this->model->term->save_translations( $new_term_id, $translations );
662
  $avoid_recursion = false;
663
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  }
22
  public $model;
23
 
24
  /**
25
+ * @var PLL_Admin_Links|null
26
  */
27
  public $links;
28
 
29
  /**
30
  * Language selected in the admin language filter.
31
  *
32
+ * @var PLL_Language|null
33
  */
34
  public $filter_lang;
35
 
36
  /**
37
  * Preferred language to assign to the new terms.
38
  *
39
+ * @var PLL_Language|null
40
  */
41
  public $pref_lang;
42
 
 
 
 
 
 
 
 
43
  /**
44
  * Stores the current post_id when bulk editing posts.
45
  *
46
+ * @var int|null
47
  */
48
  protected $post_id;
49
 
52
  *
53
  * @since 2.8
54
  *
55
+ * @var PLL_Admin_Default_Term|null
56
  */
57
  protected $default_term;
58
 
62
  * @param object $polylang
63
  */
64
  public function __construct( &$polylang ) {
65
+ $this->links = &$polylang->links;
66
+ $this->model = &$polylang->model;
67
+ $this->options = &$polylang->options;
68
+ $this->pref_lang = &$polylang->pref_lang;
69
  $this->default_term = &$polylang->default_term;
70
 
71
  foreach ( $this->model->get_translated_taxonomies() as $tax ) {
81
  add_action( 'create_term', array( $this, 'save_term' ), 900, 3 );
82
  add_action( 'edit_term', array( $this, 'save_term' ), 900, 3 ); // Late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast
83
  add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
84
+ add_filter( 'pll_inserted_term_language', array( $this, 'get_inserted_term_language' ) );
85
+ add_filter( 'pll_inserted_term_parent', array( $this, 'get_inserted_term_parent' ), 10, 2 );
86
 
87
  // Ajax response for edit term form
88
  add_action( 'wp_ajax_term_lang_choice', array( $this, 'term_lang_choice' ) );
251
  }
252
 
253
  /**
254
+ * Stores the current post_id when bulk editing posts for use in save_language and get_inserted_term_language.
255
  *
256
  * @since 1.7
257
  *
414
  }
415
  }
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  /**
418
  * Ajax response for edit term form
419
  *
517
  }
518
 
519
  // It is more efficient to use one common query for all languages as soon as there are more than 2.
520
+ $all_terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'lang' => '', 'name__like' => $s ) );
521
  if ( is_array( $all_terms ) ) {
522
  foreach ( $all_terms as $term ) {
523
  $lang = $this->model->term->get_language( $term->term_id );
602
  $this->model->term->save_translations( $new_term_id, $translations );
603
  $avoid_recursion = false;
604
  }
605
+
606
+ /**
607
+ * Returns the language for subsequently inserted term in admin.
608
+ *
609
+ * @since 3.3
610
+ *
611
+ * @param PLL_Language|null $lang Term language object if found, null otherwise.
612
+ * @return PLL_Language|null Language object, null if none found.
613
+ */
614
+ public function get_inserted_term_language( $lang ) {
615
+ if ( $lang instanceof PLL_Language ) {
616
+ return $lang;
617
+ }
618
+
619
+ if ( ! empty( $_POST['term_lang_choice'] ) && is_string( $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
620
+ $lang_slug = sanitize_key( $_POST['term_lang_choice'] ); // phpcs:ignore WordPress.Security.NonceVerification
621
+ if ( ! empty( $lang_slug ) ) {
622
+ $lang = $this->model->get_language( $lang_slug );
623
+ }
624
+ }
625
+
626
+ elseif ( ! empty( $_POST['inline_lang_choice'] ) && is_string( $_POST['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
627
+ $lang_slug = sanitize_key( $_POST['inline_lang_choice'] ); // phpcs:ignore WordPress.Security.NonceVerification
628
+ if ( ! empty( $lang_slug ) ) {
629
+ $lang = $this->model->get_language( $lang_slug );
630
+ }
631
+ }
632
+
633
+ // *Post* bulk edit, in case a new term is created
634
+ elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
635
+ // Bulk edit does not modify the language
636
+ if ( -1 === (int) $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification
637
+ $lang = $this->model->post->get_language( $this->post_id );
638
+ } elseif ( is_string( $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
639
+ $lang_slug = sanitize_key( $_GET['inline_lang_choice'] ); // phpcs:ignore WordPress.Security.NonceVerification
640
+ if ( ! empty( $lang_slug ) ) {
641
+ $lang = $this->model->get_language( $lang_slug );
642
+ }
643
+ }
644
+ }
645
+
646
+ // Special cases for default categories as the select is disabled.
647
+ elseif ( ! empty( $_POST['tag_ID'] ) && in_array( intval( get_option( 'default_category' ) ), $this->model->term->get_translations( (int) $_POST['tag_ID'] ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification
648
+ $lang = $this->model->term->get_language( (int) $_POST['tag_ID'] ); // phpcs:ignore WordPress.Security.NonceVerification
649
+ }
650
+
651
+ elseif ( ! empty( $_POST['tax_ID'] ) && in_array( intval( get_option( 'default_category' ) ), $this->model->term->get_translations( (int) $_POST['tax_ID'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
652
+ $lang = $this->model->term->get_language( (int) $_POST['tax_ID'] ); // phpcs:ignore WordPress.Security.NonceVerification
653
+ }
654
+
655
+ if ( $lang instanceof PLL_Language ) {
656
+ return $lang;
657
+ }
658
+
659
+ return null;
660
+ }
661
+
662
+ /**
663
+ * Filters the subsequently inserted term parent in admin.
664
+ *
665
+ * @since 3.3
666
+ *
667
+ * @param int $parent Parent term ID, 0 if none found.
668
+ * @param string $taxonomy Term taxonomy.
669
+ * @return int Parent term ID if found, 0 otherwise.
670
+ */
671
+ public function get_inserted_term_parent( $parent, $taxonomy ) {
672
+ if ( $parent ) {
673
+ return $parent;
674
+ }
675
+
676
+ if ( isset( $_POST['parent'], $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
677
+ $parent = intval( $_POST['parent'] ); // phpcs:ignore WordPress.Security.NonceVerification
678
+ } elseif ( isset( $_POST[ "new{$taxonomy}_parent" ], $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
679
+ $parent = intval( $_POST[ "new{$taxonomy}_parent" ] ); // phpcs:ignore WordPress.Security.NonceVerification
680
+ }
681
+
682
+ return $parent;
683
+ }
684
  }
admin/admin-model.php CHANGED
@@ -243,7 +243,7 @@ class PLL_Admin_Model extends PLL_Model {
243
  * @since 1.9
244
  * @since 3.2 Added $lang parameter.
245
  *
246
- * @param array<mixed> $args {
247
  * Arguments used to modify the language. @see PLL_Admin_Model::update_language().
248
  *
249
  * @type string $name Language name (used only for display).
@@ -416,7 +416,7 @@ class PLL_Admin_Model extends PLL_Model {
416
  }
417
 
418
  // Get all terms with term_taxonomy_id
419
- $terms = get_terms( $taxonomy, array( 'hide_empty' => false ) );
420
  $trs = array();
421
 
422
  // Prepare objects relationships.
@@ -456,7 +456,7 @@ class PLL_Admin_Model extends PLL_Model {
456
  public function update_translations( $old_slug, $new_slug = '' ) {
457
  global $wpdb;
458
 
459
- $terms = get_terms( array( 'post_translations', 'term_translations' ) );
460
  $term_ids = array();
461
  $dr = array();
462
  $dt = array();
@@ -473,15 +473,15 @@ class PLL_Admin_Model extends PLL_Model {
473
  *
474
  * @since 3.2
475
  *
476
- * @param array<int|array<string>> $tr {
477
  * List of translations with lang codes as array keys and IDs as array values.
478
  * Also in this array:
479
  *
480
- * @type array<string> $sync List of synchronized translations with lang codes as array keys and array values.
481
  * }
482
- * @param string $old_slug The old language slug.
483
- * @param string $new_slug The new language slug.
484
- * @param WP_Term $term The term containing the post or term translation group.
485
  */
486
  $tr = apply_filters( 'update_translation_group', $tr, $old_slug, $new_slug, $term );
487
 
243
  * @since 1.9
244
  * @since 3.2 Added $lang parameter.
245
  *
246
+ * @param array $args {
247
  * Arguments used to modify the language. @see PLL_Admin_Model::update_language().
248
  *
249
  * @type string $name Language name (used only for display).
416
  }
417
 
418
  // Get all terms with term_taxonomy_id
419
+ $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false ) );
420
  $trs = array();
421
 
422
  // Prepare objects relationships.
456
  public function update_translations( $old_slug, $new_slug = '' ) {
457
  global $wpdb;
458
 
459
+ $terms = get_terms( array( 'taxonomy' => array( 'post_translations', 'term_translations' ) ) );
460
  $term_ids = array();
461
  $dr = array();
462
  $dt = array();
473
  *
474
  * @since 3.2
475
  *
476
+ * @param (int|string[])[] $tr {
477
  * List of translations with lang codes as array keys and IDs as array values.
478
  * Also in this array:
479
  *
480
+ * @type string[] $sync List of synchronized translations with lang codes as array keys and array values.
481
  * }
482
+ * @param string $old_slug The old language slug.
483
+ * @param string $new_slug The new language slug.
484
+ * @param WP_Term $term The term containing the post or term translation group.
485
  */
486
  $tr = apply_filters( 'update_translation_group', $tr, $old_slug, $new_slug, $term );
487
 
admin/admin-static-pages.php CHANGED
@@ -10,7 +10,7 @@
10
  */
11
  class PLL_Admin_Static_Pages extends PLL_Static_Pages {
12
  /**
13
- * @var PLL_Admin_Links
14
  */
15
  protected $links;
16
 
@@ -26,10 +26,6 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
26
 
27
  $this->links = &$polylang->links;
28
 
29
- // Removes the editor and the template select dropdown for pages for posts
30
- add_filter( 'use_block_editor_for_post', array( $this, 'use_block_editor_for_post' ), 10, 2 ); // Since WP 5.0
31
- add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
32
-
33
  // Add post state for translations of the front page and posts page
34
  add_filter( 'display_post_states', array( $this, 'display_post_states' ), 10, 2 );
35
 
@@ -42,48 +38,6 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
42
  add_action( 'admin_notices', array( $this, 'notice_must_translate' ) );
43
  }
44
 
45
- /**
46
- * Don't use the block editor for the translations of the pages for posts
47
- *
48
- * @since 2.5
49
- *
50
- * @param bool $use_block_editor Whether the post can be edited or not.
51
- * @param WP_Post $post The post being checked.
52
- * @return bool
53
- */
54
- public function use_block_editor_for_post( $use_block_editor, $post ) {
55
- if ( 'page' === $post->post_type ) {
56
- add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
57
-
58
- if ( ( get_option( 'page_for_posts' ) == $post->ID ) && empty( $post->post_content ) ) {
59
- return false;
60
- }
61
- }
62
-
63
- return $use_block_editor;
64
- }
65
-
66
- /**
67
- * Removes the editor for the translations of the pages for posts.
68
- * Removes the page template select dropdown in page attributes metabox too.
69
- *
70
- * @since 2.2.2
71
- *
72
- * @param string $post_type Current post type.
73
- * @param WP_Post $post Current post.
74
- * @return void
75
- */
76
- public function add_meta_boxes( $post_type, $post ) {
77
- if ( 'page' === $post_type ) {
78
- add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
79
-
80
- if ( ( get_option( 'page_for_posts' ) == $post->ID ) && empty( $post->post_content ) ) {
81
- add_action( 'edit_form_after_title', '_wp_posts_page_notice' );
82
- remove_post_type_support( $post_type, 'editor' );
83
- }
84
- }
85
- }
86
-
87
  /**
88
  * Adds post state for translations of the front page and posts page.
89
  *
10
  */
11
  class PLL_Admin_Static_Pages extends PLL_Static_Pages {
12
  /**
13
+ * @var PLL_Admin_Links|null
14
  */
15
  protected $links;
16
 
26
 
27
  $this->links = &$polylang->links;
28
 
 
 
 
 
29
  // Add post state for translations of the front page and posts page
30
  add_filter( 'display_post_states', array( $this, 'display_post_states' ), 10, 2 );
31
 
38
  add_action( 'admin_notices', array( $this, 'notice_must_translate' ) );
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  /**
42
  * Adds post state for translations of the front page and posts page.
43
  *
admin/admin-strings.php CHANGED
@@ -24,7 +24,7 @@ class PLL_Admin_Strings {
24
  /**
25
  * The strings to register by default.
26
  *
27
- * @var string[]
28
  */
29
  protected static $default_strings;
30
 
24
  /**
25
  * The strings to register by default.
26
  *
27
+ * @var string[]|null
28
  */
29
  protected static $default_strings;
30
 
admin/admin.php CHANGED
@@ -10,54 +10,54 @@
10
  */
11
  class PLL_Admin extends PLL_Admin_Base {
12
  /**
13
- * @var PLL_Admin_Filters
14
  */
15
  public $filters;
16
 
17
  /**
18
- * @var PLL_Admin_Filters_Columns
19
  */
20
  public $filters_columns;
21
 
22
  /**
23
- * @var PLL_Admin_Filters_Post
24
  */
25
  public $filters_post;
26
 
27
  /**
28
- * @var PLL_Admin_Filters_Term
29
  */
30
  public $filters_term;
31
 
32
  /**
33
- * @var PLL_Admin_Filters_Media
34
  */
35
  public $filters_media;
36
 
37
  /**
38
  * @since 2.9
39
  *
40
- * @var PLL_Filters_Sanitization
41
  */
42
  public $filters_sanitization;
43
 
44
  /**
45
- * @var PLL_Admin_Block_Editor
46
  */
47
  public $block_editor;
48
 
49
  /**
50
- * @var PLL_Admin_Classic_Editor
51
  */
52
  public $classic_editor;
53
 
54
  /**
55
- * @var PLL_Admin_Nav_Menu
56
  */
57
  public $nav_menu;
58
 
59
  /**
60
- * @var PLL_Admin_Filters_Widgets_Options
61
  */
62
  public $filters_widgets_options;
63
 
10
  */
11
  class PLL_Admin extends PLL_Admin_Base {
12
  /**
13
+ * @var PLL_Admin_Filters|null
14
  */
15
  public $filters;
16
 
17
  /**
18
+ * @var PLL_Admin_Filters_Columns|null
19
  */
20
  public $filters_columns;
21
 
22
  /**
23
+ * @var PLL_Admin_Filters_Post|null
24
  */
25
  public $filters_post;
26
 
27
  /**
28
+ * @var PLL_Admin_Filters_Term|null
29
  */
30
  public $filters_term;
31
 
32
  /**
33
+ * @var PLL_Admin_Filters_Media|null
34
  */
35
  public $filters_media;
36
 
37
  /**
38
  * @since 2.9
39
  *
40
+ * @var PLL_Filters_Sanitization|null
41
  */
42
  public $filters_sanitization;
43
 
44
  /**
45
+ * @var PLL_Admin_Block_Editor|null
46
  */
47
  public $block_editor;
48
 
49
  /**
50
+ * @var PLL_Admin_Classic_Editor|null
51
  */
52
  public $classic_editor;
53
 
54
  /**
55
+ * @var PLL_Admin_Nav_Menu|null
56
  */
57
  public $nav_menu;
58
 
59
  /**
60
+ * @var PLL_Admin_Filters_Widgets_Options|null
61
  */
62
  public $filters_widgets_options;
63
 
frontend/canonical.php ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package Polylang
4
+ */
5
+
6
+ /**
7
+ * Manages canonical redirect on frontend.
8
+ *
9
+ * @since 3.3
10
+ */
11
+ class PLL_Canonical {
12
+ /**
13
+ * Stores the plugin options.
14
+ *
15
+ * @var array
16
+ */
17
+ protected $options;
18
+
19
+ /**
20
+ * @var PLL_Model
21
+ */
22
+ protected $model;
23
+
24
+ /**
25
+ * Instance of a child class of PLL_Links_Model.
26
+ *
27
+ * @var PLL_Links_Model
28
+ */
29
+ protected $links_model;
30
+
31
+ /**
32
+ * Current language.
33
+ *
34
+ * @var PLL_Language
35
+ */
36
+ protected $curlang;
37
+
38
+ /**
39
+ * Constructor.
40
+ *
41
+ * @since 3.3
42
+ *
43
+ * @param object $polylang Main Polylang object.
44
+ */
45
+ public function __construct( &$polylang ) {
46
+ $this->links_model = &$polylang->links_model;
47
+ $this->model = &$polylang->model;
48
+ $this->options = &$polylang->options;
49
+ $this->curlang = &$polylang->curlang;
50
+ }
51
+
52
+ /**
53
+ * If the language code is not in agreement with the language of the content,
54
+ * redirects incoming links to the proper URL to avoid duplicate content.
55
+ *
56
+ * @since 0.9.6
57
+ *
58
+ * @global WP_Query $wp_query WordPress Query object.
59
+ * @global bool $is_IIS
60
+ *
61
+ * @param string $requested_url Optional, defaults to requested url.
62
+ * @param bool $do_redirect Optional, whether to perform the redirect or not.
63
+ * @return string|void Returns if redirect is not performed.
64
+ */
65
+ public function check_canonical_url( $requested_url = '', $do_redirect = true ) {
66
+ global $wp_query;
67
+
68
+ // Don't redirect in same cases as WP.
69
+ if ( is_trackback() || is_search() || is_admin() || is_preview() || is_robots() || ( $GLOBALS['is_IIS'] && ! iis7_supports_permalinks() ) ) {
70
+ return;
71
+ }
72
+
73
+ // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id=.
74
+ if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
75
+ return;
76
+ }
77
+
78
+ /*
79
+ * If the default language code is not hidden and the static front page url contains the page name,
80
+ * the customizer lands here and the code below would redirect to the list of posts.
81
+ */
82
+ if ( is_customize_preview() ) {
83
+ return;
84
+ }
85
+
86
+ if ( empty( $requested_url ) ) {
87
+ $requested_url = pll_get_requested_url();
88
+ }
89
+
90
+ if ( ( is_single() || is_page() ) && ! is_front_page() ) {
91
+ $post = get_post();
92
+ if ( $post instanceof WP_Post && $this->model->is_translated_post_type( $post->post_type ) ) {
93
+ $language = $this->model->post->get_language( (int) $post->ID );
94
+ }
95
+ }
96
+
97
+ if ( ! empty( $wp_query->tax_query ) ) {
98
+ if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $wp_query->tax_query ) ) ) {
99
+ $term_id = $this->get_queried_term_id( $wp_query->tax_query );
100
+ if ( $term_id ) {
101
+ $language = $this->model->term->get_language( $term_id );
102
+ }
103
+ }
104
+ }
105
+
106
+ if ( $wp_query->is_posts_page ) {
107
+ $page_id = get_query_var( 'page_id' );
108
+ if ( ! $page_id ) {
109
+ $page_id = get_queried_object_id();
110
+ }
111
+ if ( $page_id && is_numeric( $page_id ) ) {
112
+ $language = $this->model->post->get_language( (int) $page_id );
113
+ }
114
+ }
115
+
116
+ if ( 3 === $this->options['force_lang'] ) {
117
+ $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST );
118
+ foreach ( $this->options['domains'] as $lang => $domain ) {
119
+ $host = wp_parse_url( $domain, PHP_URL_HOST );
120
+ if ( $requested_host && $host && ltrim( $requested_host, 'w.' ) === ltrim( $host, 'w.' ) ) {
121
+ $language = $this->model->get_language( $lang );
122
+ }
123
+ }
124
+ }
125
+
126
+ if ( empty( $language ) ) {
127
+ $language = $this->curlang;
128
+ $redirect_url = $requested_url;
129
+ } else {
130
+ $redirect_url = $this->redirect_canonical( $requested_url, $language );
131
+ $redirect_url = $this->options['force_lang'] ?
132
+ $this->links_model->switch_language_in_link( $redirect_url, $language ) :
133
+ $this->links_model->remove_language_from_link( $redirect_url ); // Works only for default permalinks.
134
+ }
135
+
136
+
137
+ /**
138
+ * Filters the canonical url detected by Polylang.
139
+ *
140
+ * @since 1.6
141
+ *
142
+ * @param string|false $redirect_url False or the url to redirect to.
143
+ * @param PLL_Language $language The language detected.
144
+ */
145
+ $redirect_url = apply_filters( 'pll_check_canonical_url', $redirect_url, $language );
146
+
147
+ if ( ! $redirect_url || $requested_url === $redirect_url ) {
148
+ return $requested_url;
149
+ }
150
+
151
+ if ( ! $do_redirect ) {
152
+ return $redirect_url;
153
+ }
154
+
155
+ // Protect against chained redirects.
156
+ if ( $redirect_url === $this->check_canonical_url( $redirect_url, false ) && wp_validate_redirect( $redirect_url ) ) {
157
+ wp_safe_redirect( $redirect_url, 301, POLYLANG );
158
+ exit;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Returns the term_id of the requested term.
164
+ *
165
+ * @since 2.9
166
+ *
167
+ * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query.
168
+ * @return int
169
+ */
170
+ protected function get_queried_term_id( $tax_query ) {
171
+ $queried_terms = $tax_query->queried_terms;
172
+ $taxonomy = $this->get_queried_taxonomy( $tax_query );
173
+
174
+ if ( ! is_array( $queried_terms[ $taxonomy ]['terms'] ) ) {
175
+ return 0;
176
+ }
177
+ $field = $queried_terms[ $taxonomy ]['field'];
178
+ $term = reset( $queried_terms[ $taxonomy ]['terms'] );
179
+ $lang = isset( $queried_terms['language']['terms'] ) ? reset( $queried_terms['language']['terms'] ) : '';
180
+
181
+ // We can get a term_id when requesting a plain permalink, eg /?cat=1.
182
+ if ( 'term_id' === $field ) {
183
+ return $term;
184
+ }
185
+
186
+ // We get a slug when requesting a pretty permalink. Let's query all corresponding terms.
187
+ $args = array(
188
+ 'lang' => '',
189
+ 'taxonomy' => $taxonomy,
190
+ $field => $term,
191
+ 'hide_empty' => false,
192
+ 'fields' => 'ids',
193
+ );
194
+ $term_ids = get_terms( $args );
195
+
196
+ if ( ! is_array( $term_ids ) || empty( $term_ids ) ) {
197
+ return 0;
198
+ }
199
+
200
+ $term_ids = array_filter( $term_ids, 'is_numeric' );
201
+
202
+ $filtered_terms_by_lang = array_filter(
203
+ $term_ids,
204
+ function ( $term_id ) use ( $lang ) {
205
+ $term_lang = $this->model->term->get_language( (int) $term_id );
206
+
207
+ return ! empty( $term_lang ) && $term_lang->slug === $lang;
208
+ }
209
+ );
210
+
211
+ $tr_term = (int) reset( $filtered_terms_by_lang );
212
+
213
+ if ( ! empty( $tr_term ) ) {
214
+ // The queried term exists in the desired language.
215
+ return $tr_term;
216
+ }
217
+
218
+ // The queried term doesn't exist in the desired language, let's return the first one retrieved.
219
+ return (int) reset( $term_ids );
220
+ }
221
+
222
+ /**
223
+ * Find the taxonomy being queried.
224
+ *
225
+ * @since 2.9
226
+ *
227
+ * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query.
228
+ * @return string A taxonomy slug
229
+ */
230
+ protected function get_queried_taxonomy( $tax_query ) {
231
+ $queried_terms = $tax_query->queried_terms;
232
+ unset( $queried_terms['language'] );
233
+
234
+ return (string) key( $queried_terms );
235
+ }
236
+
237
+ /**
238
+ * Evaluates the canonical redirect url through the deidcated WP function.
239
+ *
240
+ * @since 3.3
241
+ *
242
+ * @global WP_Query $wp_query WordPress Query object.
243
+ *
244
+ * @param string $url Requested url.
245
+ * @param PLL_Language $language Language of the queried object.
246
+ * @return string
247
+ */
248
+ protected function redirect_canonical( $url, $language ) {
249
+ global $wp_query;
250
+
251
+ $this->curlang = $language; // Hack to filter the `page_for_posts` option in the correct language.
252
+
253
+ $backup_wp_query = $wp_query;
254
+
255
+ if ( isset( $wp_query->tax_query ) ) {
256
+ unset( $wp_query->tax_query->queried_terms['language'] );
257
+ unset( $wp_query->query['lang'] );
258
+ }
259
+
260
+ $redirect_url = redirect_canonical( $url, false );
261
+
262
+ $wp_query = $backup_wp_query;
263
+
264
+ return $redirect_url ? $redirect_url : $url;
265
+ }
266
+ }
frontend/choose-lang.php CHANGED
@@ -31,17 +31,9 @@ abstract class PLL_Choose_Lang {
31
  /**
32
  * Current language.
33
  *
34
- * @var PLL_Language
35
  */
36
  public $curlang;
37
- /**
38
- * @var PLL_Accept_Language
39
- */
40
- private $lang_parse;
41
- /**
42
- * @var PLL_Accept_Languages_Collection
43
- */
44
- private $accept_langs;
45
 
46
  /**
47
  * Constructor
@@ -306,15 +298,30 @@ abstract class PLL_Choose_Lang {
306
  $this->set_language( $lang );
307
  $this->set_curlang_in_query( $query );
308
  } elseif ( ( count( $query->query ) == 1 || ( is_paged() && count( $query->query ) == 2 ) ) && $lang = get_query_var( 'lang' ) ) {
309
- // Set is_home on translated home page when it displays posts. It must be true on page 2, 3... too.
310
  $lang = $this->model->get_language( $lang );
311
  $this->set_language( $lang ); // Set the language now otherwise it will be too late to filter sticky posts!
312
- $query->is_home = true;
313
- $query->is_tax = false;
 
 
314
  $query->is_archive = false;
 
 
 
315
  }
316
  }
317
 
 
 
 
 
 
 
 
 
 
 
 
318
  /**
319
  * Sets the current language in the query.
320
  *
@@ -324,7 +331,9 @@ abstract class PLL_Choose_Lang {
324
  * @return void
325
  */
326
  protected function set_curlang_in_query( &$query ) {
327
- $pll_query = new PLL_Query( $query, $this->model );
328
- $pll_query->set_language( $this->curlang );
 
 
329
  }
330
  }
31
  /**
32
  * Current language.
33
  *
34
+ * @var PLL_Language|null
35
  */
36
  public $curlang;
 
 
 
 
 
 
 
 
37
 
38
  /**
39
  * Constructor
298
  $this->set_language( $lang );
299
  $this->set_curlang_in_query( $query );
300
  } elseif ( ( count( $query->query ) == 1 || ( is_paged() && count( $query->query ) == 2 ) ) && $lang = get_query_var( 'lang' ) ) {
 
301
  $lang = $this->model->get_language( $lang );
302
  $this->set_language( $lang ); // Set the language now otherwise it will be too late to filter sticky posts!
303
+
304
+ // Set is_home on translated home page when it displays posts. It must be true on page 2, 3... too.
305
+ $query->is_home = true;
306
+ $query->is_tax = false;
307
  $query->is_archive = false;
308
+
309
+ // Filters is_front_page() in case a static front page is not translated in this language.
310
+ add_filter( 'option_show_on_front', array( $this, 'filter_option_show_on_front' ) );
311
  }
312
  }
313
 
314
+ /**
315
+ * Filters the option show_on_front when the current front page displays posts.
316
+ *
317
+ * This is useful when a static front page is not translated in all languages.
318
+ *
319
+ * @return string
320
+ */
321
+ public function filter_option_show_on_front() {
322
+ return 'posts';
323
+ }
324
+
325
  /**
326
  * Sets the current language in the query.
327
  *
331
  * @return void
332
  */
333
  protected function set_curlang_in_query( &$query ) {
334
+ if ( ! empty( $this->curlang ) ) {
335
+ $pll_query = new PLL_Query( $query, $this->model );
336
+ $pll_query->set_language( $this->curlang );
337
+ }
338
  }
339
  }
frontend/frontend-auto-translate.php CHANGED
@@ -18,7 +18,7 @@ class PLL_Frontend_Auto_Translate {
18
  /**
19
  * Current language.
20
  *
21
- * @var PLL_Language
22
  */
23
  public $curlang;
24
 
@@ -275,10 +275,13 @@ class PLL_Frontend_Auto_Translate {
275
  return $tr_id;
276
  }
277
  } else {
278
- $terms = get_terms( $taxonomy, array( $field => $term, 'lang' => '' ) );
279
 
280
- if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
281
  $t = reset( $terms );
 
 
 
282
  $tr_id = $this->get_term( $t->term_id );
283
 
284
  if ( ! is_wp_error( $tr = get_term( $tr_id, $taxonomy ) ) ) {
18
  /**
19
  * Current language.
20
  *
21
+ * @var PLL_Language|null
22
  */
23
  public $curlang;
24
 
275
  return $tr_id;
276
  }
277
  } else {
278
+ $terms = get_terms( array( 'taxonomy' => $taxonomy, $field => $term, 'lang' => '' ) );
279
 
280
+ if ( ! empty( $terms ) && is_array( $terms ) ) {
281
  $t = reset( $terms );
282
+ if ( ! $t instanceof WP_Term ) {
283
+ return $term;
284
+ }
285
  $tr_id = $this->get_term( $t->term_id );
286
 
287
  if ( ! is_wp_error( $tr = get_term( $tr_id, $taxonomy ) ) ) {
frontend/frontend-filters-links.php CHANGED
@@ -11,7 +11,7 @@
11
  class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
12
 
13
  /**
14
- * @var PLL_Frontend_Links
15
  */
16
  public $links;
17
 
@@ -73,10 +73,6 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
73
  // Rewrites ajax url
74
  add_filter( 'admin_url', array( $this, 'admin_url' ), 10, 2 );
75
  }
76
-
77
- // Redirects to canonical url before WordPress redirect_canonical
78
- // but after Nextgen Gallery which hacks $_SERVER['REQUEST_URI'] !!! and restores it in 'template_redirect' with priority 1
79
- add_action( 'template_redirect', array( $this, 'check_canonical_url' ), 4 );
80
  }
81
 
82
  /**
@@ -274,48 +270,47 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
274
  $theme_root = get_theme_root();
275
  $theme_root = ( false === strpos( $theme_root, '\\' ) ) ? $theme_root : str_replace( '/', '\\', $theme_root );
276
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  /**
278
- * Filter the white list of the Polylang 'home_url' filter
279
- * The $args contains an array of arrays each of them having
280
- * a 'file' key and/or a 'function' key to decide which functions in
281
- * which files using home_url() calls must be filtered
282
  *
283
  * @since 1.1.2
284
  *
285
- * @param array $args
 
 
286
  */
287
- $this->white_list = apply_filters(
288
- 'pll_home_url_white_list',
289
- array(
290
- array( 'file' => $theme_root ),
291
- array( 'function' => 'wp_nav_menu' ),
292
- array( 'function' => 'login_footer' ),
293
- array( 'function' => 'get_custom_logo' ),
294
- array( 'function' => 'render_block_core_site_title' ),
295
- )
296
- );
297
  }
298
 
299
- // We don't want to filter the home url in these cases
300
  if ( empty( $this->black_list ) ) {
 
 
 
 
301
 
302
  /**
303
- * Filter the black list of the Polylang 'home_url' filter
304
- * The $args contains an array of arrays each of them having
305
- * a 'file' key and/or a 'function' key to decide which functions in
306
- * which files using home_url() calls must be filtered
307
  *
308
  * @since 1.1.2
309
  *
310
- * @param array $args
 
 
311
  */
312
- $this->black_list = apply_filters(
313
- 'pll_home_url_black_list',
314
- array(
315
- array( 'file' => 'searchform.php' ), // Since WP 3.6 searchform.php is passed through get_search_form
316
- array( 'function' => 'get_search_form' ),
317
- )
318
- );
319
  }
320
 
321
  $traces = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
@@ -324,13 +319,13 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
324
  foreach ( $traces as $trace ) {
325
  // Black list first
326
  foreach ( $this->black_list as $v ) {
327
- if ( ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) ) || ( isset( $trace['function'], $v['function'] ) && $trace['function'] == $v['function'] ) ) {
328
  return $url;
329
  }
330
  }
331
 
332
  foreach ( $this->white_list as $v ) {
333
- if ( ( isset( $trace['function'], $v['function'] ) && $trace['function'] == $v['function'] ) ||
334
  ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) && in_array( $trace['function'], array( 'home_url', 'get_home_url', 'bloginfo', 'get_bloginfo' ) ) ) ) {
335
  $ok = true;
336
  }
@@ -352,230 +347,4 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
352
  public function admin_url( $url, $path ) {
353
  return 'admin-ajax.php' === $path ? $this->links_model->switch_language_in_link( $url, $this->curlang ) : $url;
354
  }
355
-
356
- /**
357
- * If the language code is not in agreement with the language of the content,
358
- * redirects incoming links to the proper URL to avoid duplicate content.
359
- *
360
- * @since 0.9.6
361
- *
362
- * @param string $requested_url Optional, defaults to requested url.
363
- * @param bool $do_redirect Optional, whether to perform the redirect or not.
364
- * @return string|void Returns if redirect is not performed.
365
- */
366
- public function check_canonical_url( $requested_url = '', $do_redirect = true ) {
367
- // Don't redirect in same cases as WP.
368
- if ( is_trackback() || is_search() || is_admin() || is_preview() || is_robots() || ( $GLOBALS['is_IIS'] && ! iis7_supports_permalinks() ) ) {
369
- return;
370
- }
371
-
372
- // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id=.
373
- if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
374
- return;
375
- }
376
-
377
- /*
378
- * If the default language code is not hidden and the static front page url contains the page name,
379
- * the customizer lands here and the code below would redirect to the list of posts.
380
- */
381
- if ( is_customize_preview() ) {
382
- return;
383
- }
384
-
385
- if ( empty( $requested_url ) ) {
386
- $requested_url = pll_get_requested_url();
387
- }
388
-
389
- if ( is_single() || is_page() ) {
390
- $post = get_post();
391
- if ( $post instanceof WP_Post && $this->model->is_translated_post_type( $post->post_type ) ) {
392
- $language = $this->model->post->get_language( (int) $post->ID );
393
- }
394
- }
395
-
396
- elseif ( $this->links_model->using_permalinks && is_category() && ! empty( $this->wp_query()->query['cat'] ) ) {
397
- // When we receive a plain permaling with a cat query var, we need to redirect to the pretty permalink.
398
- if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $this->wp_query()->tax_query ) ) ) {
399
- $term_id = $this->get_queried_term_id( $this->wp_query()->tax_query );
400
- if ( $term_id ) {
401
- $language = $this->model->term->get_language( $term_id );
402
- $redirect_url = $this->maybe_add_page_to_redirect_url( get_term_link( $term_id ) );
403
- }
404
- }
405
- }
406
-
407
- elseif ( is_category() || is_tag() || is_tax() ) {
408
- // We need to switch the language when there is no language provided in a pretty permalink.
409
- $obj = get_queried_object();
410
- if ( ! empty( $obj ) && $this->model->is_translated_taxonomy( $obj->taxonomy ) ) {
411
- $language = $this->model->term->get_language( (int) $obj->term_id );
412
- }
413
- }
414
-
415
- elseif ( is_404() && ! empty( $this->wp_query()->tax_query ) ) {
416
- // When a wrong language is passed through a pretty permalink, we just need to switch the language.
417
- if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $this->wp_query()->tax_query ) ) ) {
418
- $term_id = $this->get_queried_term_id( $this->wp_query()->tax_query );
419
- if ( $term_id ) {
420
- $language = $this->model->term->get_language( $term_id );
421
- }
422
- }
423
- }
424
-
425
- elseif ( $this->links_model->using_permalinks && $this->wp_query()->is_posts_page && ! empty( $this->wp_query()->query['page_id'] ) && $id = get_query_var( 'page_id' ) ) {
426
- $language = $this->model->post->get_language( (int) $id );
427
- $redirect_url = $this->maybe_add_page_to_redirect_url( get_permalink( $id ) );
428
- }
429
-
430
- elseif ( $this->wp_query()->is_posts_page ) {
431
- $obj = get_queried_object();
432
- if ( $obj instanceof WP_Post ) {
433
- $language = $this->model->post->get_language( (int) $obj->ID );
434
- }
435
- }
436
-
437
- if ( 3 === $this->options['force_lang'] ) {
438
- $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST );
439
- foreach ( $this->options['domains'] as $lang => $domain ) {
440
- $host = wp_parse_url( $domain, PHP_URL_HOST );
441
- if ( 'www.' . $requested_host === $host || 'www.' . $host === $requested_host ) {
442
- $language = $this->model->get_language( $lang );
443
- $redirect_url = str_replace( '://' . $requested_host, '://' . $host, $requested_url );
444
- }
445
- }
446
- }
447
-
448
- if ( empty( $language ) ) {
449
- $language = $this->curlang;
450
- $redirect_url = $requested_url;
451
- } elseif ( empty( $redirect_url ) ) {
452
- // First get the canonical url evaluated by WP
453
- // Workaround a WP bug which removes the port for some urls and get it back at second call to redirect_canonical
454
- $_redirect_url = ( ! $_redirect_url = redirect_canonical( $requested_url, false ) ) ? $requested_url : $_redirect_url;
455
- $redirect_url = ( ! $redirect_url = redirect_canonical( $_redirect_url, false ) ) ? $_redirect_url : $redirect_url;
456
-
457
- // Then get the right language code in url
458
- $redirect_url = $this->options['force_lang'] ?
459
- $this->links_model->switch_language_in_link( $redirect_url, $language ) :
460
- $this->links_model->remove_language_from_link( $redirect_url ); // Works only for default permalinks
461
- }
462
-
463
- /**
464
- * Filters the canonical url detected by Polylang.
465
- *
466
- * @since 1.6
467
- *
468
- * @param string|false $redirect_url False or the url to redirect to.
469
- * @param PLL_Language $language The language detected.
470
- */
471
- $redirect_url = apply_filters( 'pll_check_canonical_url', $redirect_url, $language );
472
-
473
- // The language is not correctly set so let's redirect to the correct url for this object
474
- if ( $do_redirect ) {
475
- // Protect against chained redirects.
476
- if ( $redirect_url && $requested_url != $redirect_url && $redirect_url === $this->check_canonical_url( $redirect_url, false ) && wp_validate_redirect( $redirect_url ) ) {
477
- wp_safe_redirect( $redirect_url, 301, POLYLANG );
478
- exit;
479
- } else {
480
- return;
481
- }
482
- }
483
-
484
- return $redirect_url;
485
- }
486
-
487
- /**
488
- * Returns the link to the paged page if requested.
489
- *
490
- * @since 2.9
491
- *
492
- * @param string $redirect_url The url to redirect to.
493
- * @return string
494
- */
495
- protected function maybe_add_page_to_redirect_url( $redirect_url ) {
496
- if ( ! empty( $this->wp_query()->query['paged'] ) && $page = get_query_var( 'paged' ) ) {
497
- $redirect_url = $this->links_model->add_paged_to_link( $redirect_url, $page );
498
- }
499
- return $redirect_url;
500
- }
501
-
502
- /**
503
- * Returns the term_id of the requested term.
504
- *
505
- * @since 2.9
506
- *
507
- * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query.
508
- * @return int|false
509
- */
510
- protected function get_queried_term_id( $tax_query ) {
511
- $queried_terms = $tax_query->queried_terms;
512
- $taxonomy = $this->get_queried_taxonomy( $tax_query );
513
-
514
- if ( ! is_array( $queried_terms[ $taxonomy ]['terms'] ) ) {
515
- return false;
516
- }
517
- $field = $queried_terms[ $taxonomy ]['field'];
518
- $term = reset( $queried_terms[ $taxonomy ]['terms'] );
519
- $lang = isset( $queried_terms['language']['terms'] ) ? reset( $queried_terms['language']['terms'] ) : '';
520
-
521
- // We can get a term_id when requesting a plain permalink, eg /?cat=1.
522
- if ( 'term_id' === $field ) {
523
- return $term;
524
- }
525
-
526
- // We get a slug when requesting a pretty permalink. Let's query all corresponding terms.
527
- $args = array(
528
- 'lang' => '',
529
- 'taxonomy' => $taxonomy,
530
- $field => $term,
531
- 'hide_empty' => false,
532
- 'fields' => 'ids',
533
- );
534
- $terms = get_terms( $args );
535
-
536
- $filtered_terms_by_lang = array_filter(
537
- $terms,
538
- function ( $term ) use ( $lang ) {
539
- $term_lang = $this->model->term->get_language( $term );
540
-
541
- return ! empty( $term_lang ) && $term_lang->slug === $lang;
542
- }
543
- );
544
-
545
- $tr_term = reset( $filtered_terms_by_lang );
546
-
547
- if ( ! empty( $tr_term ) ) {
548
- // The queried term exists in the desired language.
549
- return $tr_term;
550
- }
551
-
552
- // The queried term doesn't exist in the desired language, let's return the first one retrieved.
553
- return reset( $terms );
554
- }
555
-
556
- /**
557
- * Find the taxonomy being queried.
558
- *
559
- * @since 2.9
560
- *
561
- * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query.
562
- * @return string A taxonomy slug
563
- */
564
- protected function get_queried_taxonomy( $tax_query ) {
565
- $queried_terms = $tax_query->queried_terms;
566
- unset( $queried_terms['language'] );
567
-
568
- return key( $queried_terms );
569
- }
570
-
571
- /**
572
- * Returns the Global WordPress WP_Query object.
573
- *
574
- * @since 3.0
575
- *
576
- * @return WP_Query
577
- */
578
- protected function wp_query() {
579
- return $GLOBALS['wp_query'];
580
- }
581
  }
11
  class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
12
 
13
  /**
14
+ * @var PLL_Frontend_Links|null
15
  */
16
  public $links;
17
 
73
  // Rewrites ajax url
74
  add_filter( 'admin_url', array( $this, 'admin_url' ), 10, 2 );
75
  }
 
 
 
 
76
  }
77
 
78
  /**
270
  $theme_root = get_theme_root();
271
  $theme_root = ( false === strpos( $theme_root, '\\' ) ) ? $theme_root : str_replace( '/', '\\', $theme_root );
272
 
273
+ $white_list = array(
274
+ array( 'file' => $theme_root ),
275
+ array( 'function' => 'wp_nav_menu' ),
276
+ array( 'function' => 'login_footer' ),
277
+ array( 'function' => 'get_custom_logo' ),
278
+ array( 'function' => 'render_block_core_site_title' ),
279
+ );
280
+
281
+ if ( 3 === $this->options['force_lang'] ) {
282
+ $white_list[] = array( 'function' => 'redirect_canonical' );
283
+ }
284
+
285
  /**
286
+ * Filters the white list of the Polylang 'home_url' filter.
 
 
 
287
  *
288
  * @since 1.1.2
289
  *
290
+ * @param string[][] $white_list An array of arrays each of them having a 'file' key
291
+ * and/or a 'function' key to decide which functions in
292
+ * which files using home_url() calls must be filtered.
293
  */
294
+ $this->white_list = apply_filters( 'pll_home_url_white_list', $white_list );
 
 
 
 
 
 
 
 
 
295
  }
296
 
297
+ // We don't want to filter the home url in these cases.
298
  if ( empty( $this->black_list ) ) {
299
+ $black_list = array(
300
+ array( 'file' => 'searchform.php' ), // Since WP 3.6 searchform.php is passed through get_search_form.
301
+ array( 'function' => 'get_search_form' ),
302
+ );
303
 
304
  /**
305
+ * Filters the black list of the Polylang 'home_url' filter.
 
 
 
306
  *
307
  * @since 1.1.2
308
  *
309
+ * @param string[][] $black_list An array of arrays each of them having a 'file' key
310
+ * and/or a 'function' key to decide which functions in
311
+ * which files using home_url() calls must be filtered.
312
  */
313
+ $this->black_list = apply_filters( 'pll_home_url_black_list', $black_list );
 
 
 
 
 
 
314
  }
315
 
316
  $traces = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
319
  foreach ( $traces as $trace ) {
320
  // Black list first
321
  foreach ( $this->black_list as $v ) {
322
+ if ( ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) ) || ( ! empty( $v['function'] ) && $trace['function'] === $v['function'] ) ) {
323
  return $url;
324
  }
325
  }
326
 
327
  foreach ( $this->white_list as $v ) {
328
+ if ( ( ! empty( $v['function'] ) && $trace['function'] === $v['function'] ) ||
329
  ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) && in_array( $trace['function'], array( 'home_url', 'get_home_url', 'bloginfo', 'get_bloginfo' ) ) ) ) {
330
  $ok = true;
331
  }
347
  public function admin_url( $url, $path ) {
348
  return 'admin-ajax.php' === $path ? $this->links_model->switch_language_in_link( $url, $this->curlang ) : $url;
349
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  }
frontend/frontend-filters-search.php CHANGED
@@ -19,7 +19,7 @@ class PLL_Frontend_Filters_Search {
19
  /**
20
  * Current language.
21
  *
22
- * @var PLL_Language
23
  */
24
  public $curlang;
25
 
@@ -38,9 +38,13 @@ class PLL_Frontend_Filters_Search {
38
  // Low priority in case the search form is created using the same filter as described in http://codex.wordpress.org/Function_Reference/get_search_form
39
  add_filter( 'get_search_form', array( $this, 'get_search_form' ), 99 );
40
 
 
 
 
41
  // Adds the language information in admin bar search form
42
  add_action( 'add_admin_bar_menus', array( $this, 'add_admin_bar_menus' ) );
43
 
 
44
  // Adds javascript at the end of the document
45
  // Was used for WP < 3.6. kept just in case
46
  if ( defined( 'PLL_SEARCH_FORM_JS' ) && PLL_SEARCH_FORM_JS ) {
@@ -59,17 +63,25 @@ class PLL_Frontend_Filters_Search {
59
  * @return string Modified search form.
60
  */
61
  public function get_search_form( $form ) {
62
- if ( $form ) {
63
- if ( $this->links_model->using_permalinks ) {
64
- // Take care to modify only the url in the <form> tag.
65
- preg_match( '#<form.+?>#', $form, $matches );
66
- $old = reset( $matches );
67
- // Replace action attribute (a text with no space and no closing tag within double quotes or simple quotes or without quotes).
68
- $new = preg_replace( '#\saction=("[^"\r\n]+"|\'[^\'\r\n]+\'|[^\'"][^>\s]+)#', ' action="' . esc_url( $this->curlang->search_url ) . '"', $old );
69
- $form = str_replace( $old, $new, $form );
70
- } else {
71
- $form = str_replace( '</form>', '<input type="hidden" name="lang" value="' . esc_attr( $this->curlang->slug ) . '" /></form>', $form );
 
 
 
 
 
72
  }
 
 
 
73
  }
74
 
75
  return $form;
19
  /**
20
  * Current language.
21
  *
22
+ * @var PLL_Language|null
23
  */
24
  public $curlang;
25
 
38
  // Low priority in case the search form is created using the same filter as described in http://codex.wordpress.org/Function_Reference/get_search_form
39
  add_filter( 'get_search_form', array( $this, 'get_search_form' ), 99 );
40
 
41
+ // Adds the language information in the search block.
42
+ add_filter( 'render_block_core/search', array( $this, 'get_search_form' ) );
43
+
44
  // Adds the language information in admin bar search form
45
  add_action( 'add_admin_bar_menus', array( $this, 'add_admin_bar_menus' ) );
46
 
47
+
48
  // Adds javascript at the end of the document
49
  // Was used for WP < 3.6. kept just in case
50
  if ( defined( 'PLL_SEARCH_FORM_JS' ) && PLL_SEARCH_FORM_JS ) {
63
  * @return string Modified search form.
64
  */
65
  public function get_search_form( $form ) {
66
+ if ( empty( $form ) ) {
67
+ return $form;
68
+ }
69
+
70
+ if ( $this->links_model->using_permalinks ) {
71
+ // Take care to modify only the url in the <form> tag.
72
+ preg_match( '#<form.+?>#', $form, $matches );
73
+ $old = reset( $matches );
74
+ if ( empty( $old ) ) {
75
+ return $form;
76
+ }
77
+ // Replace action attribute (a text with no space and no closing tag within double quotes or simple quotes or without quotes).
78
+ $new = preg_replace( '#\saction=("[^"\r\n]+"|\'[^\'\r\n]+\'|[^\'"][^>\s]+)#', ' action="' . esc_url( $this->curlang->search_url ) . '"', $old );
79
+ if ( empty( $new ) ) {
80
+ return $form;
81
  }
82
+ $form = str_replace( $old, $new, $form );
83
+ } else {
84
+ $form = str_replace( '</form>', '<input type="hidden" name="lang" value="' . esc_attr( $this->curlang->slug ) . '" /></form>', $form );
85
  }
86
 
87
  return $form;
frontend/frontend-filters-widgets.php CHANGED
@@ -19,7 +19,7 @@ class PLL_Frontend_Filters_Widgets {
19
  /**
20
  * Current language.
21
  *
22
- * @var PLL_Language
23
  */
24
  public $curlang;
25
 
19
  /**
20
  * Current language.
21
  *
22
+ * @var PLL_Language|null
23
  */
24
  public $curlang;
25
 
frontend/frontend-filters.php CHANGED
@@ -121,7 +121,15 @@ class PLL_Frontend_Filters extends PLL_Filters {
121
  * @return string modified WHERE clause
122
  */
123
  public function getarchives_where( $sql, $r ) {
124
- return ! empty( $r['post_type'] ) && $this->model->is_translated_post_type( $r['post_type'] ) ? $sql . $this->model->post->where_clause( $this->curlang ) : $sql;
 
 
 
 
 
 
 
 
125
  }
126
 
127
  /**
121
  * @return string modified WHERE clause
122
  */
123
  public function getarchives_where( $sql, $r ) {
124
+ if ( ! $this->curlang instanceof PLL_Language ) {
125
+ return $sql;
126
+ }
127
+
128
+ if ( empty( $r['post_type'] ) || ! $this->model->is_translated_post_type( $r['post_type'] ) ) {
129
+ return $sql;
130
+ }
131
+
132
+ return $sql . $this->model->post->where_clause( $this->curlang );
133
  }
134
 
135
  /**
frontend/frontend-links.php CHANGED
@@ -9,12 +9,6 @@
9
  * @since 1.2
10
  */
11
  class PLL_Frontend_Links extends PLL_Links {
12
- /**
13
- * Current language.
14
- *
15
- * @var PLL_Language
16
- */
17
- public $curlang;
18
 
19
  /**
20
  * Internal non persistent cache object.
@@ -89,7 +83,7 @@ class PLL_Frontend_Links extends PLL_Links {
89
  if ( ! empty( $tax_query['taxonomy'] ) && $this->model->is_translated_taxonomy( $tax_query['taxonomy'] ) ) {
90
 
91
  $tax = get_taxonomy( $tax_query['taxonomy'] );
92
- $terms = get_terms( $tax->name, array( 'fields' => 'id=>slug' ) ); // Filtered by current language
93
 
94
  foreach ( $tax_query['terms'] as $slug ) {
95
  $term_id = array_search( $slug, $terms ); // What is the term_id corresponding to taxonomy term?
@@ -115,7 +109,7 @@ class PLL_Frontend_Links extends PLL_Links {
115
  elseif ( $tr_id = $this->model->term->get_translation( $term->term_id, $language ) ) {
116
  if ( $tr_term = get_term( $tr_id, $term->taxonomy ) ) {
117
  // Check if translated term ( or children ) have posts
118
- $count = $tr_term->count || ( is_taxonomy_hierarchical( $term->taxonomy ) && array_sum( wp_list_pluck( get_terms( $term->taxonomy, array( 'child_of' => $tr_term->term_id, 'lang' => $language->slug ) ), 'count' ) ) );
119
 
120
  /**
121
  * Filter whether to hide an archive translation url
@@ -166,6 +160,8 @@ class PLL_Frontend_Links extends PLL_Links {
166
  }
167
  }
168
 
 
 
169
  /**
170
  * Filter the translation url of the current page before Polylang caches it
171
  *
@@ -174,7 +170,7 @@ class PLL_Frontend_Links extends PLL_Links {
174
  * @param null|string $url The translation url, null if none was found
175
  * @param string $language The language code of the translation
176
  */
177
- $translation_url = apply_filters( 'pll_translation_url', ( isset( $url ) && ! is_wp_error( $url ) ? $url : null ), $language->slug );
178
 
179
  // Don't cache before template_redirect to avoid a conflict with Barrel + WP Bakery Page Builder
180
  if ( did_action( 'template_redirect' ) ) {
9
  * @since 1.2
10
  */
11
  class PLL_Frontend_Links extends PLL_Links {
 
 
 
 
 
 
12
 
13
  /**
14
  * Internal non persistent cache object.
83
  if ( ! empty( $tax_query['taxonomy'] ) && $this->model->is_translated_taxonomy( $tax_query['taxonomy'] ) ) {
84
 
85
  $tax = get_taxonomy( $tax_query['taxonomy'] );
86
+ $terms = get_terms( array( 'taxonomy' => $tax->name, 'fields' => 'id=>slug' ) ); // Filtered by current language
87
 
88
  foreach ( $tax_query['terms'] as $slug ) {
89
  $term_id = array_search( $slug, $terms ); // What is the term_id corresponding to taxonomy term?
109
  elseif ( $tr_id = $this->model->term->get_translation( $term->term_id, $language ) ) {
110
  if ( $tr_term = get_term( $tr_id, $term->taxonomy ) ) {
111
  // Check if translated term ( or children ) have posts
112
+ $count = $tr_term->count || ( is_taxonomy_hierarchical( $term->taxonomy ) && array_sum( wp_list_pluck( get_terms( array( 'taxonomy' => $term->taxonomy, 'child_of' => $tr_term->term_id, 'lang' => $language->slug ) ), 'count' ) ) );
113
 
114
  /**
115
  * Filter whether to hide an archive translation url
160
  }
161
  }
162
 
163
+ $url = ! empty( $url ) && ! is_wp_error( $url ) ? $url : null;
164
+
165
  /**
166
  * Filter the translation url of the current page before Polylang caches it
167
  *
170
  * @param null|string $url The translation url, null if none was found
171
  * @param string $language The language code of the translation
172
  */
173
+ $translation_url = apply_filters( 'pll_translation_url', $url, $language->slug );
174
 
175
  // Don't cache before template_redirect to avoid a conflict with Barrel + WP Bakery Page Builder
176
  if ( did_action( 'template_redirect' ) ) {
frontend/frontend-nav-menu.php CHANGED
@@ -12,7 +12,7 @@ class PLL_Frontend_Nav_Menu extends PLL_Nav_Menu {
12
  /**
13
  * Current language.
14
  *
15
- * @var PLL_Language
16
  */
17
  public $curlang;
18
 
@@ -90,6 +90,10 @@ class PLL_Frontend_Nav_Menu extends PLL_Nav_Menu {
90
  * @return stdClass[] Modified menu items.
91
  */
92
  public function wp_get_nav_menu_items( $items ) {
 
 
 
 
93
  if ( doing_action( 'customize_register' ) ) { // needed since WP 4.3, doing_action available since WP 3.9
94
  return $items;
95
  }
12
  /**
13
  * Current language.
14
  *
15
+ * @var PLL_Language|null|false
16
  */
17
  public $curlang;
18
 
90
  * @return stdClass[] Modified menu items.
91
  */
92
  public function wp_get_nav_menu_items( $items ) {
93
+ if ( empty( $this->curlang ) ) {
94
+ return $items;
95
+ }
96
+
97
  if ( doing_action( 'customize_register' ) ) { // needed since WP 4.3, doing_action available since WP 3.9
98
  return $items;
99
  }
frontend/frontend-static-pages.php CHANGED
@@ -17,7 +17,7 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
17
  protected $links_model;
18
 
19
  /**
20
- * @var PLL_Frontend_Links
21
  */
22
  protected $links;
23
 
@@ -34,7 +34,6 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
34
  $this->links_model = &$polylang->links_model;
35
  $this->links = &$polylang->links;
36
 
37
- add_action( 'pll_language_defined', array( $this, 'pll_language_defined' ) );
38
  add_action( 'pll_home_requested', array( $this, 'pll_home_requested' ) );
39
 
40
  // Manages the redirection of the homepage
@@ -55,12 +54,7 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
55
  * @return void
56
  */
57
  public function pll_language_defined() {
58
- // Translates our page on front and page for posts properties
59
- $this->init();
60
-
61
- // Translates page for posts and page on front
62
- add_filter( 'option_page_on_front', array( $this, 'translate_page_on_front' ) );
63
- add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
64
 
65
  // Support theme customizer
66
  if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
@@ -80,19 +74,6 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
80
  set_query_var( 'page_id', $this->curlang->page_on_front );
81
  }
82
 
83
- /**
84
- * Translates page on front
85
- *
86
- * @since 1.8
87
- *
88
- * @param int $v page on front page id
89
- * @return int
90
- */
91
- public function translate_page_on_front( $v ) {
92
- // Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache
93
- return isset( $this->curlang->page_on_front ) && ( $this->curlang->page_on_front ) && ! doing_action( 'switch_blog' ) ? $this->curlang->page_on_front : $v;
94
- }
95
-
96
  /**
97
  * Manages canonical redirection of the homepage when using page on front
98
  *
@@ -184,13 +165,8 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
184
  return $lang;
185
  }
186
 
187
- // The home page is requested
188
- if ( did_action( 'home_requested' ) ) {
189
- $query->set( 'page_id', $lang->page_on_front );
190
- }
191
-
192
  // Redirect the language page to the homepage when using a static front page
193
- elseif ( ( $this->options['redirect_lang'] || $this->options['hide_default'] ) && $this->is_front_page( $query ) && $lang = $this->model->get_language( get_query_var( 'lang' ) ) ) {
194
  $query->is_archive = $query->is_tax = false;
195
  if ( ! empty( $lang->page_on_front ) ) {
196
  $query->set( 'page_id', $lang->page_on_front );
@@ -205,6 +181,9 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
205
  // Fix paged static front page in plain permalinks when Settings > Reading doesn't match the default language
206
  elseif ( ! $this->links_model->using_permalinks && count( $query->query ) === 1 && ! empty( $query->query['page'] ) ) {
207
  $lang = $this->model->get_language( $this->options['default_lang'] );
 
 
 
208
  $query->set( 'page_id', $lang->page_on_front );
209
  $query->is_singular = $query->is_page = true;
210
  $query->is_archive = $query->is_tax = false;
@@ -249,7 +228,7 @@ class PLL_Frontend_Static_Pages extends PLL_Static_Pages {
249
  if ( ! empty( $page_id ) && in_array( $page_id, $pages = $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ) ) ) {
250
  // Fill the cache with all pages for posts to avoid one query per page later
251
  // The posts_per_page limit is a trick to avoid splitting the query
252
- get_posts( array( 'posts_per_page' => 999, 'post_type' => 'page', 'post__in' => $pages, 'lang' => '' ) );
253
 
254
  $lang = $this->model->post->get_language( $page_id );
255
  $query->is_singular = $query->is_page = false;
17
  protected $links_model;
18
 
19
  /**
20
+ * @var PLL_Frontend_Links|null
21
  */
22
  protected $links;
23
 
34
  $this->links_model = &$polylang->links_model;
35
  $this->links = &$polylang->links;
36
 
 
37
  add_action( 'pll_home_requested', array( $this, 'pll_home_requested' ) );
38
 
39
  // Manages the redirection of the homepage
54
  * @return void
55
  */
56
  public function pll_language_defined() {
57
+ parent::pll_language_defined();
 
 
 
 
 
58
 
59
  // Support theme customizer
60
  if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
74
  set_query_var( 'page_id', $this->curlang->page_on_front );
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  /**
78
  * Manages canonical redirection of the homepage when using page on front
79
  *
165
  return $lang;
166
  }
167
 
 
 
 
 
 
168
  // Redirect the language page to the homepage when using a static front page
169
+ if ( ( $this->options['redirect_lang'] || $this->options['hide_default'] ) && $this->is_front_page( $query ) && $lang = $this->model->get_language( get_query_var( 'lang' ) ) ) {
170
  $query->is_archive = $query->is_tax = false;
171
  if ( ! empty( $lang->page_on_front ) ) {
172
  $query->set( 'page_id', $lang->page_on_front );
181
  // Fix paged static front page in plain permalinks when Settings > Reading doesn't match the default language
182
  elseif ( ! $this->links_model->using_permalinks && count( $query->query ) === 1 && ! empty( $query->query['page'] ) ) {
183
  $lang = $this->model->get_language( $this->options['default_lang'] );
184
+ if ( empty( $lang ) ) {
185
+ return $lang;
186
+ }
187
  $query->set( 'page_id', $lang->page_on_front );
188
  $query->is_singular = $query->is_page = true;
189
  $query->is_archive = $query->is_tax = false;
228
  if ( ! empty( $page_id ) && in_array( $page_id, $pages = $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ) ) ) {
229
  // Fill the cache with all pages for posts to avoid one query per page later
230
  // The posts_per_page limit is a trick to avoid splitting the query
231
+ get_posts( array( 'posts_per_page' => 99, 'post_type' => 'page', 'post__in' => $pages, 'lang' => '' ) );
232
 
233
  $lang = $this->model->post->get_language( $page_id );
234
  $query->is_singular = $query->is_page = false;
frontend/frontend.php CHANGED
@@ -12,54 +12,54 @@ class PLL_Frontend extends PLL_Base {
12
  /**
13
  * Current language.
14
  *
15
- * @var PLL_Language
16
  */
17
  public $curlang;
18
 
19
  /**
20
- * @var PLL_Frontend_Auto_Translate
21
  */
22
  public $auto_translate;
23
 
24
  /**
25
  * The class selecting the current language.
26
  *
27
- * @var PLL_Choose_Lang
28
  */
29
  public $choose_lang;
30
 
31
  /**
32
- * @var PLL_Frontend_Filters
33
  */
34
  public $filters;
35
 
36
  /**
37
- * @var PLL_Frontend_Filters_Links
38
  */
39
  public $filters_links;
40
 
41
  /**
42
- * @var PLL_Frontend_Filters_Search
43
  */
44
  public $filters_search;
45
 
46
  /**
47
- * @var PLL_Frontend_Links
48
  */
49
  public $links;
50
 
51
  /**
52
- * @var PLL_Frontend_Nav_Menu
53
  */
54
  public $nav_menu;
55
 
56
  /**
57
- * @var PLL_Frontend_Static_Pages
58
  */
59
  public $static_pages;
60
 
61
  /**
62
- * @var PLL_Frontend_Filters_Widgets
63
  */
64
  public $filters_widgets;
65
 
@@ -128,6 +128,14 @@ class PLL_Frontend extends PLL_Base {
128
  $this->filters_search = new PLL_Frontend_Filters_Search( $this );
129
  $this->filters_widgets = new PLL_Frontend_Filters_Widgets( $this );
130
 
 
 
 
 
 
 
 
 
131
  // Auto translate for Ajax
132
  if ( ( ! defined( 'PLL_AUTO_TRANSLATE' ) || PLL_AUTO_TRANSLATE ) && wp_doing_ajax() ) {
133
  $this->auto_translate();
12
  /**
13
  * Current language.
14
  *
15
+ * @var PLL_Language|null
16
  */
17
  public $curlang;
18
 
19
  /**
20
+ * @var PLL_Frontend_Auto_Translate|null
21
  */
22
  public $auto_translate;
23
 
24
  /**
25
  * The class selecting the current language.
26
  *
27
+ * @var PLL_Choose_Lang|null
28
  */
29
  public $choose_lang;
30
 
31
  /**
32
+ * @var PLL_Frontend_Filters|null
33
  */
34
  public $filters;
35
 
36
  /**
37
+ * @var PLL_Frontend_Filters_Links|null
38
  */
39
  public $filters_links;
40
 
41
  /**
42
+ * @var PLL_Frontend_Filters_Search|null
43
  */
44
  public $filters_search;
45
 
46
  /**
47
+ * @var PLL_Frontend_Links|null
48
  */
49
  public $links;
50
 
51
  /**
52
+ * @var PLL_Frontend_Nav_Menu|null
53
  */
54
  public $nav_menu;
55
 
56
  /**
57
+ * @var PLL_Frontend_Static_Pages|null
58
  */
59
  public $static_pages;
60
 
61
  /**
62
+ * @var PLL_Frontend_Filters_Widgets|null
63
  */
64
  public $filters_widgets;
65
 
128
  $this->filters_search = new PLL_Frontend_Filters_Search( $this );
129
  $this->filters_widgets = new PLL_Frontend_Filters_Widgets( $this );
130
 
131
+ /*
132
+ * Redirects to canonical url before WordPress redirect_canonical
133
+ * but after Nextgen Gallery which hacks $_SERVER['REQUEST_URI'] !!!
134
+ * and restores it in 'template_redirect' with priority 1.
135
+ */
136
+ $this->canonical = new PLL_Canonical( $this );
137
+ add_action( 'template_redirect', array( $this->canonical, 'check_canonical_url' ), 4 );
138
+
139
  // Auto translate for Ajax
140
  if ( ( ! defined( 'PLL_AUTO_TRANSLATE' ) || PLL_AUTO_TRANSLATE ) && wp_doing_ajax() ) {
141
  $this->auto_translate();
include/base.php CHANGED
@@ -8,6 +8,7 @@
8
  *
9
  * @since 1.2
10
  */
 
11
  abstract class PLL_Base {
12
  /**
13
  * Stores the plugin options.
@@ -31,14 +32,14 @@ abstract class PLL_Base {
31
  /**
32
  * Registers hooks on insert / update post related actions and filters.
33
  *
34
- * @var PLL_CRUD_Posts
35
  */
36
  public $posts;
37
 
38
  /**
39
  * Registers hooks on insert / update term related action and filters.
40
  *
41
- * @var PLL_CRUD_Terms
42
  */
43
  public $terms;
44
 
8
  *
9
  * @since 1.2
10
  */
11
+ #[AllowDynamicProperties]
12
  abstract class PLL_Base {
13
  /**
14
  * Stores the plugin options.
32
  /**
33
  * Registers hooks on insert / update post related actions and filters.
34
  *
35
+ * @var PLL_CRUD_Posts|null
36
  */
37
  public $posts;
38
 
39
  /**
40
  * Registers hooks on insert / update term related action and filters.
41
  *
42
+ * @var PLL_CRUD_Terms|null
43
  */
44
  public $terms;
45
 
include/cache.php CHANGED
@@ -22,7 +22,7 @@ class PLL_Cache {
22
  *
23
  * @var array
24
  */
25
- protected $cache;
26
 
27
  /**
28
  * Constructor
22
  *
23
  * @var array
24
  */
25
+ protected $cache = array();
26
 
27
  /**
28
  * Constructor
include/class-polylang.php CHANGED
@@ -193,9 +193,8 @@ class Polylang {
193
  */
194
  $class = apply_filters( 'pll_model', PLL_SETTINGS || self::is_wizard() ? 'PLL_Admin_Model' : 'PLL_Model' );
195
  $model = new $class( $options );
196
- $links_model = $model->get_links_model();
197
 
198
- if ( ! $model->get_languages_list() ) {
199
  /**
200
  * Fires when no language has been defined yet
201
  * Used to load overridden textdomains
@@ -213,7 +212,7 @@ class Polylang {
213
  $class = 'PLL_Admin';
214
  } elseif ( self::is_rest_request() ) {
215
  $class = 'PLL_REST_Request';
216
- } elseif ( $model->get_languages_list() ) {
217
  $class = 'PLL_Frontend';
218
  }
219
 
@@ -227,7 +226,8 @@ class Polylang {
227
  $class = apply_filters( 'pll_context', $class );
228
 
229
  if ( ! empty( $class ) ) {
230
- $polylang = new $class( $links_model );
 
231
 
232
  /**
233
  * Fires after the $polylang object is created and before the API is loaded
193
  */
194
  $class = apply_filters( 'pll_model', PLL_SETTINGS || self::is_wizard() ? 'PLL_Admin_Model' : 'PLL_Model' );
195
  $model = new $class( $options );
 
196
 
197
+ if ( ! $model->has_languages() ) {
198
  /**
199
  * Fires when no language has been defined yet
200
  * Used to load overridden textdomains
212
  $class = 'PLL_Admin';
213
  } elseif ( self::is_rest_request() ) {
214
  $class = 'PLL_REST_Request';
215
+ } elseif ( $model->has_languages() ) {
216
  $class = 'PLL_Frontend';
217
  }
218
 
226
  $class = apply_filters( 'pll_context', $class );
227
 
228
  if ( ! empty( $class ) ) {
229
+ $links_model = $model->get_links_model();
230
+ $polylang = new $class( $links_model );
231
 
232
  /**
233
  * Fires after the $polylang object is created and before the API is loaded
include/crud-posts.php CHANGED
@@ -18,14 +18,14 @@ class PLL_CRUD_Posts {
18
  /**
19
  * Preferred language to assign to a new post.
20
  *
21
- * @var PLL_Language
22
  */
23
  protected $pref_lang;
24
 
25
  /**
26
  * Current language.
27
  *
28
- * @var PLL_Language
29
  */
30
  protected $curlang;
31
 
@@ -123,10 +123,10 @@ class PLL_CRUD_Posts {
123
  *
124
  * @since 2.3
125
  *
126
- * @param int $object_id Object ID.
127
- * @param WP_Term[] $terms An array of object terms.
128
- * @param int[] $tt_ids An array of term taxonomy IDs.
129
- * @param string $taxonomy Taxonomy slug.
130
  * @return void
131
  */
132
  public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy ) {
@@ -139,7 +139,7 @@ class PLL_CRUD_Posts {
139
  // Convert to term ids if we got tag names
140
  $strings = array_filter( $terms, 'is_string' );
141
  if ( ! empty( $strings ) ) {
142
- $_terms = get_terms( $taxonomy, array( 'name' => $strings, 'object_ids' => $object_id, 'fields' => 'ids' ) );
143
  $terms = array_merge( array_diff( $terms, $strings ), $_terms );
144
  }
145
 
@@ -283,7 +283,9 @@ class PLL_CRUD_Posts {
283
  // Create a new attachment ( translate attachment parent if exists ).
284
  add_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Avoid a conflict with automatic duplicate at upload.
285
  unset( $post['ID'] ); // Will force the creation.
286
- $post['post_parent'] = ( $post['post_parent'] && $tr_parent = $this->model->post->get_translation( $post['post_parent'], $lang->slug ) ) ? $tr_parent : 0;
 
 
287
  $post['tax_input'] = array( 'language' => array( $lang->slug ) ); // Assigns the language.
288
  $tr_id = wp_insert_attachment( wp_slash( $post ) );
289
  remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload.
18
  /**
19
  * Preferred language to assign to a new post.
20
  *
21
+ * @var PLL_Language|null
22
  */
23
  protected $pref_lang;
24
 
25
  /**
26
  * Current language.
27
  *
28
+ * @var PLL_Language|null
29
  */
30
  protected $curlang;
31
 
123
  *
124
  * @since 2.3
125
  *
126
+ * @param int $object_id Object ID.
127
+ * @param int[]|string[] $terms An array of object term IDs or slugs.
128
+ * @param int[] $tt_ids An array of term taxonomy IDs.
129
+ * @param string $taxonomy Taxonomy slug.
130
  * @return void
131
  */
132
  public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy ) {
139
  // Convert to term ids if we got tag names
140
  $strings = array_filter( $terms, 'is_string' );
141
  if ( ! empty( $strings ) ) {
142
+ $_terms = get_terms( array( 'taxonomy' => $taxonomy, 'name' => $strings, 'object_ids' => $object_id, 'fields' => 'ids' ) );
143
  $terms = array_merge( array_diff( $terms, $strings ), $_terms );
144
  }
145
 
283
  // Create a new attachment ( translate attachment parent if exists ).
284
  add_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Avoid a conflict with automatic duplicate at upload.
285
  unset( $post['ID'] ); // Will force the creation.
286
+ if ( ! empty( $post['post_parent'] ) ) {
287
+ $post['post_parent'] = (int) $this->model->post->get_translation( $post['post_parent'], $lang->slug );
288
+ }
289
  $post['tax_input'] = array( 'language' => array( $lang->slug ) ); // Assigns the language.
290
  $tr_id = wp_insert_attachment( wp_slash( $post ) );
291
  remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload.
include/crud-terms.php CHANGED
@@ -18,31 +18,38 @@ class PLL_CRUD_Terms {
18
  /**
19
  * Current language (used to filter the content).
20
  *
21
- * @var PLL_Language
22
  */
23
  public $curlang;
24
 
25
  /**
26
  * Language selected in the admin language filter.
27
  *
28
- * @var PLL_Language
29
  */
30
  public $filter_lang;
31
 
32
  /**
33
  * Preferred language to assign to new contents.
34
  *
35
- * @var PLL_Language
36
  */
37
  public $pref_lang;
38
 
39
  /**
40
  * Stores the 'lang' query var from WP_Query.
41
  *
42
- * @var string
43
  */
44
  private $tax_query_lang;
45
 
 
 
 
 
 
 
 
46
  /**
47
  * Constructor
48
  *
@@ -51,14 +58,16 @@ class PLL_CRUD_Terms {
51
  * @param object $polylang
52
  */
53
  public function __construct( &$polylang ) {
54
- $this->model = &$polylang->model;
55
- $this->curlang = &$polylang->curlang;
56
  $this->filter_lang = &$polylang->filter_lang;
57
- $this->pref_lang = &$polylang->pref_lang;
58
 
59
  // Saving terms
60
  add_action( 'create_term', array( $this, 'save_term' ), 999, 3 );
61
  add_action( 'edit_term', array( $this, 'save_term' ), 999, 3 ); // After PLL_Admin_Filters_Term
 
 
62
 
63
  // Adds cache domain when querying terms
64
  add_filter( 'get_terms_args', array( $this, 'get_terms_args' ), 10, 2 );
@@ -244,4 +253,77 @@ class PLL_CRUD_Terms {
244
  $this->model->term->delete_translation( $term_id );
245
  $this->model->term->delete_language( $term_id );
246
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  }
18
  /**
19
  * Current language (used to filter the content).
20
  *
21
+ * @var PLL_Language|null
22
  */
23
  public $curlang;
24
 
25
  /**
26
  * Language selected in the admin language filter.
27
  *
28
+ * @var PLL_Language|null
29
  */
30
  public $filter_lang;
31
 
32
  /**
33
  * Preferred language to assign to new contents.
34
  *
35
+ * @var PLL_Language|null
36
  */
37
  public $pref_lang;
38
 
39
  /**
40
  * Stores the 'lang' query var from WP_Query.
41
  *
42
+ * @var string|null
43
  */
44
  private $tax_query_lang;
45
 
46
+ /**
47
+ * Stores the term name before creating a slug if needed.
48
+ *
49
+ * @var string
50
+ */
51
+ private $pre_term_name = '';
52
+
53
  /**
54
  * Constructor
55
  *
58
  * @param object $polylang
59
  */
60
  public function __construct( &$polylang ) {
61
+ $this->model = &$polylang->model;
62
+ $this->curlang = &$polylang->curlang;
63
  $this->filter_lang = &$polylang->filter_lang;
64
+ $this->pref_lang = &$polylang->pref_lang;
65
 
66
  // Saving terms
67
  add_action( 'create_term', array( $this, 'save_term' ), 999, 3 );
68
  add_action( 'edit_term', array( $this, 'save_term' ), 999, 3 ); // After PLL_Admin_Filters_Term
69
+ add_filter( 'pre_term_name', array( $this, 'set_pre_term_name' ) );
70
+ add_filter( 'pre_term_slug', array( $this, 'set_pre_term_slug' ), 10, 2 );
71
 
72
  // Adds cache domain when querying terms
73
  add_filter( 'get_terms_args', array( $this, 'get_terms_args' ), 10, 2 );
253
  $this->model->term->delete_translation( $term_id );
254
  $this->model->term->delete_language( $term_id );
255
  }
256
+
257
+ /**
258
+ * Stores the term name for use in pre_term_slug
259
+ *
260
+ * @since 0.9.5
261
+ *
262
+ * @param string $name term name
263
+ * @return string unmodified term name
264
+ */
265
+ public function set_pre_term_name( $name ) {
266
+ return $this->pre_term_name = $name;
267
+ }
268
+
269
+ /**
270
+ * Appends language slug to the term slug if needed.
271
+ *
272
+ * @since 3.3
273
+ *
274
+ * @param string $slug Term slug.
275
+ * @param string $taxonomy Term taxonomy.
276
+ * @return string Slug with a language suffix if found.
277
+ */
278
+ public function set_pre_term_slug( $slug, $taxonomy ) {
279
+ if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
280
+ return $slug;
281
+ }
282
+
283
+ if ( ! $slug ) {
284
+ $slug = sanitize_title( $this->pre_term_name );
285
+ }
286
+
287
+ if ( ! term_exists( $slug, $taxonomy ) ) {
288
+ return $slug;
289
+ }
290
+
291
+ /**
292
+ * Filters the subsequently inserted term language.
293
+ *
294
+ * @since 3.3
295
+ *
296
+ * @param PLL_Language|null $lang Found language object, null otherwise.
297
+ * @param string $taxonomy Term taonomy.
298
+ * @param string $slug Term slug
299
+ */
300
+ $lang = apply_filters( 'pll_inserted_term_language', null, $taxonomy, $slug );
301
+
302
+ if ( ! $lang instanceof PLL_Language ) {
303
+ return $slug;
304
+ }
305
+
306
+ $parent = 0;
307
+ if ( is_taxonomy_hierarchical( $taxonomy ) ) {
308
+ /**
309
+ * Filters the subsequently inserted term parent.
310
+ *
311
+ * @since 3.3
312
+ *
313
+ * @param int $parent Parent term ID, 0 if none.
314
+ * @param string $taxonomy Term taxonomy.
315
+ * @param string $slug Term slug
316
+ */
317
+ $parent = apply_filters( 'pll_inserted_term_parent', 0, $taxonomy, $slug );
318
+ }
319
+
320
+ $term_id = (int) $this->model->term_exists_by_slug( $slug, $lang, $taxonomy, $parent );
321
+
322
+ // If no term exist in the given language with that slug, it can be created.
323
+ if ( ! $term_id ) {
324
+ $slug .= '-' . $lang->slug;
325
+ }
326
+
327
+ return $slug;
328
+ }
329
  }
include/db-tools.php CHANGED
@@ -3,7 +3,7 @@
3
  * @package Polylang
4
  */
5
 
6
- defined( 'ABSPATH' ) || exit; // @phpstan-ignore-line
7
 
8
  /**
9
  * Small set of tools to work with the database.
@@ -18,8 +18,8 @@ class PLL_Db_Tools {
18
  *
19
  * @since 3.2
20
  *
21
- * @param array<int|string> $values An array of values.
22
- * @return string A comma separated list of values.
23
  */
24
  public static function prepare_values_list( $values ) {
25
  $values = array_map( array( __CLASS__, 'prepare_value' ), (array) $values );
@@ -31,10 +31,11 @@ class PLL_Db_Tools {
31
  * Wraps a value in escaped double quotes or casts as an integer.
32
  * Only string and integers and supported for now.
33
  *
34
- * @since 3.2
 
35
  * @global wpdb $wpdb
36
  *
37
- * @param int|string $value A value.
38
  * @return int|string
39
  */
40
  public static function prepare_value( $value ) {
3
  * @package Polylang
4
  */
5
 
6
+ defined( 'ABSPATH' ) || exit;
7
 
8
  /**
9
  * Small set of tools to work with the database.
18
  *
19
  * @since 3.2
20
  *
21
+ * @param (int|string)[] $values An array of values.
22
+ * @return string A comma separated list of values.
23
  */
24
  public static function prepare_values_list( $values ) {
25
  $values = array_map( array( __CLASS__, 'prepare_value' ), (array) $values );
31
  * Wraps a value in escaped double quotes or casts as an integer.
32
  * Only string and integers and supported for now.
33
  *
34
+ * @since 3.2
35
+ *
36
  * @global wpdb $wpdb
37
  *
38
+ * @param int|string $value A value.
39
  * @return int|string
40
  */
41
  public static function prepare_value( $value ) {
include/filters-links.php CHANGED
@@ -29,14 +29,14 @@ class PLL_Filters_Links {
29
  public $links_model;
30
 
31
  /**
32
- * @var PLL_Links
33
  */
34
  public $links;
35
 
36
  /**
37
  * Current language.
38
  *
39
- * @var PLL_Language
40
  */
41
  public $curlang;
42
 
@@ -185,16 +185,16 @@ class PLL_Filters_Links {
185
 
186
  /**
187
  * Modifies the post type archive links to add the language parameter
188
- * only if the post type is translated
189
  *
190
  * The filter was originally only on frontend but is needed on admin too for
191
- * compatibility with the archive link of the ACF link field since ACF 5.4.0
192
  *
193
  * @since 1.7.6
194
  *
195
- * @param string $link
196
- * @param string $post_type
197
- * @return string modified link
198
  */
199
  public function post_type_archive_link( $link, $post_type ) {
200
  return $this->model->is_translated_post_type( $post_type ) && 'post' !== $post_type ? $this->links_model->switch_language_in_link( $link, $this->curlang ) : $link;
29
  public $links_model;
30
 
31
  /**
32
+ * @var PLL_Links|null
33
  */
34
  public $links;
35
 
36
  /**
37
  * Current language.
38
  *
39
+ * @var PLL_Language|null
40
  */
41
  public $curlang;
42
 
185
 
186
  /**
187
  * Modifies the post type archive links to add the language parameter
188
+ * only if the post type is translated.
189
  *
190
  * The filter was originally only on frontend but is needed on admin too for
191
+ * compatibility with the archive link of the ACF link field since ACF 5.4.0.
192
  *
193
  * @since 1.7.6
194
  *
195
+ * @param string $link The post type archive permalink.
196
+ * @param string $post_type Post type name.
197
+ * @return string
198
  */
199
  public function post_type_archive_link( $link, $post_type ) {
200
  return $this->model->is_translated_post_type( $post_type ) && 'post' !== $post_type ? $this->links_model->switch_language_in_link( $link, $this->curlang ) : $link;
include/filters.php CHANGED
@@ -31,7 +31,7 @@ class PLL_Filters {
31
  /**
32
  * Current language.
33
  *
34
- * @var PLL_Language
35
  */
36
  public $curlang;
37
 
@@ -425,10 +425,10 @@ class PLL_Filters {
425
  *
426
  * @since 3.2
427
  *
428
- * @param array<mixed> $defaults An array of arguments passed to get_terms().
429
- * @param int|string $term The term to check. Accepts term ID, slug, or name.
430
- * @param string $taxonomy The taxonomy name to use. An empty string indicates the search is against all taxonomies.
431
- * @return array<mixed>
432
  */
433
  public function term_exists_default_query_args( $defaults, $term, $taxonomy ) {
434
  if ( ! empty( $taxonomy ) && ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
31
  /**
32
  * Current language.
33
  *
34
+ * @var PLL_Language|null
35
  */
36
  public $curlang;
37
 
425
  *
426
  * @since 3.2
427
  *
428
+ * @param array $defaults An array of arguments passed to get_terms().
429
+ * @param int|string $term The term to check. Accepts term ID, slug, or name.
430
+ * @param string $taxonomy The taxonomy name to use. An empty string indicates the search is against all taxonomies.
431
+ * @return array
432
  */
433
  public function term_exists_default_query_args( $defaults, $term, $taxonomy ) {
434
  if ( ! empty( $taxonomy ) && ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
include/language.php CHANGED
@@ -9,6 +9,7 @@
9
  *
10
  * @since 1.2
11
  */
 
12
  class PLL_Language {
13
  /**
14
  * Id of the term in 'language' taxonomy.
@@ -90,35 +91,35 @@ class PLL_Language {
90
  /**
91
  * W3C locale.
92
  *
93
- * @var string.
94
  */
95
  public $w3c;
96
 
97
  /**
98
  * Facebook locale.
99
  *
100
- * @var string.
101
  */
102
  public $facebook;
103
 
104
  /**
105
  * Home url in this language.
106
  *
107
- * @var string
108
  */
109
  public $home_url;
110
 
111
  /**
112
  * Home url to use in search forms.
113
  *
114
- * @var string
115
  */
116
  public $search_url;
117
 
118
  /**
119
  * Host corresponding to this language.
120
  *
121
- * @var string
122
  */
123
  public $host;
124
 
@@ -132,14 +133,14 @@ class PLL_Language {
132
  /**
133
  * Id of the page on front in this language ( set from pll_languages_list filter ).
134
  *
135
- * @var int
136
  */
137
  public $page_on_front;
138
 
139
  /**
140
  * Id of the page for posts in this language ( set from pll_languages_list filter ).
141
  *
142
- * @var int
143
  */
144
  public $page_for_posts;
145
 
@@ -153,28 +154,28 @@ class PLL_Language {
153
  /**
154
  * Url of the flag.
155
  *
156
- * @var string
157
  */
158
  public $flag_url;
159
 
160
  /**
161
  * Html markup of the flag.
162
  *
163
- * @var string
164
  */
165
  public $flag;
166
 
167
  /**
168
  * Url of the custom flag if it exists.
169
  *
170
- * @var string
171
  */
172
  public $custom_flag_url;
173
 
174
  /**
175
  * Html markup of the custom flag if it exists.
176
  *
177
- * @var string
178
  */
179
  public $custom_flag;
180
 
@@ -459,11 +460,19 @@ class PLL_Language {
459
  * @return void
460
  */
461
  public function set_url_scheme() {
462
- $this->home_url = set_url_scheme( $this->home_url );
463
- $this->search_url = set_url_scheme( $this->search_url );
 
 
 
 
 
464
 
465
  // Set url scheme, also for the flags.
466
- $this->flag_url = set_url_scheme( $this->flag_url );
 
 
 
467
  if ( ! empty( $this->custom_flag_url ) ) {
468
  $this->custom_flag_url = set_url_scheme( $this->custom_flag_url );
469
  }
9
  *
10
  * @since 1.2
11
  */
12
+ #[AllowDynamicProperties]
13
  class PLL_Language {
14
  /**
15
  * Id of the term in 'language' taxonomy.
91
  /**
92
  * W3C locale.
93
  *
94
+ * @var string
95
  */
96
  public $w3c;
97
 
98
  /**
99
  * Facebook locale.
100
  *
101
+ * @var string|null
102
  */
103
  public $facebook;
104
 
105
  /**
106
  * Home url in this language.
107
  *
108
+ * @var string|null
109
  */
110
  public $home_url;
111
 
112
  /**
113
  * Home url to use in search forms.
114
  *
115
+ * @var string|null
116
  */
117
  public $search_url;
118
 
119
  /**
120
  * Host corresponding to this language.
121
  *
122
+ * @var string|null
123
  */
124
  public $host;
125
 
133
  /**
134
  * Id of the page on front in this language ( set from pll_languages_list filter ).
135
  *
136
+ * @var int|null
137
  */
138
  public $page_on_front;
139
 
140
  /**
141
  * Id of the page for posts in this language ( set from pll_languages_list filter ).
142
  *
143
+ * @var int|null
144
  */
145
  public $page_for_posts;
146
 
154
  /**
155
  * Url of the flag.
156
  *
157
+ * @var string|null
158
  */
159
  public $flag_url;
160
 
161
  /**
162
  * Html markup of the flag.
163
  *
164
+ * @var string|null
165
  */
166
  public $flag;
167
 
168
  /**
169
  * Url of the custom flag if it exists.
170
  *
171
+ * @var string|null
172
  */
173
  public $custom_flag_url;
174
 
175
  /**
176
  * Html markup of the custom flag if it exists.
177
  *
178
+ * @var string|null
179
  */
180
  public $custom_flag;
181
 
460
  * @return void
461
  */
462
  public function set_url_scheme() {
463
+ if ( ! empty( $this->home_url ) ) {
464
+ $this->home_url = set_url_scheme( $this->home_url );
465
+ }
466
+
467
+ if ( ! empty( $this->search_url ) ) {
468
+ $this->search_url = set_url_scheme( $this->search_url );
469
+ }
470
 
471
  // Set url scheme, also for the flags.
472
+ if ( ! empty( $this->flag_url ) ) {
473
+ $this->flag_url = set_url_scheme( $this->flag_url );
474
+ }
475
+
476
  if ( ! empty( $this->custom_flag_url ) ) {
477
  $this->custom_flag_url = set_url_scheme( $this->custom_flag_url );
478
  }
include/license.php CHANGED
@@ -33,7 +33,7 @@ class PLL_License {
33
  /**
34
  * License data, obtained from the API request.
35
  *
36
- * @var stdClass
37
  */
38
  public $license_data;
39
 
33
  /**
34
  * License data, obtained from the API request.
35
  *
36
+ * @var stdClass|null
37
  */
38
  public $license_data;
39
 
include/links-abstract-domain.php CHANGED
@@ -4,7 +4,7 @@
4
  */
5
 
6
  /**
7
- * Links model for use when using one domain or subdomain per language
8
  *
9
  * @since 2.0
10
  */
@@ -20,7 +20,7 @@ abstract class PLL_Links_Abstract_Domain extends PLL_Links_Permalinks {
20
  public function __construct( &$model ) {
21
  parent::__construct( $model );
22
 
23
- // Avoid cross domain requests ( mainly for custom fonts ).
24
  add_filter( 'content_url', array( $this, 'site_url' ) );
25
  add_filter( 'theme_root_uri', array( $this, 'site_url' ) ); // The above filter is not sufficient with WPMU Domain Mapping.
26
  add_filter( 'plugins_url', array( $this, 'site_url' ) );
@@ -29,14 +29,13 @@ abstract class PLL_Links_Abstract_Domain extends PLL_Links_Permalinks {
29
  }
30
 
31
  /**
32
- * Returns the language based on language code in url
33
- * links_model interface
34
  *
35
  * @since 1.2
36
- * @since 2.0 add $url argument
37
  *
38
- * @param string $url optional, defaults to current url
39
- * @return string language slug
40
  */
41
  public function get_language_from_url( $url = '' ) {
42
  if ( empty( $url ) ) {
@@ -60,12 +59,12 @@ abstract class PLL_Links_Abstract_Domain extends PLL_Links_Permalinks {
60
  }
61
 
62
  /**
63
- * Returns the current site url
64
  *
65
  * @since 1.8
66
  *
67
- * @param string $url
68
- * @return string
69
  */
70
  public function site_url( $url ) {
71
  $lang = $this->get_language_from_url();
@@ -74,11 +73,11 @@ abstract class PLL_Links_Abstract_Domain extends PLL_Links_Permalinks {
74
  }
75
 
76
  /**
77
- * Fix the domain for upload directory
78
  *
79
  * @since 2.0.6
80
  *
81
- * @param array $uploads
82
  * @return array
83
  */
84
  public function upload_dir( $uploads ) {
4
  */
5
 
6
  /**
7
+ * Links model for use when using one domain or subdomain per language.
8
  *
9
  * @since 2.0
10
  */
20
  public function __construct( &$model ) {
21
  parent::__construct( $model );
22
 
23
+ // Avoid cross domain requests (mainly for custom fonts).
24
  add_filter( 'content_url', array( $this, 'site_url' ) );
25
  add_filter( 'theme_root_uri', array( $this, 'site_url' ) ); // The above filter is not sufficient with WPMU Domain Mapping.
26
  add_filter( 'plugins_url', array( $this, 'site_url' ) );
29
  }
30
 
31
  /**
32
+ * Returns the language based on the language code in url.
 
33
  *
34
  * @since 1.2
35
+ * @since 2.0 Add the $url argument.
36
  *
37
+ * @param string $url Optional, defaults to the current url.
38
+ * @return string Language slug.
39
  */
40
  public function get_language_from_url( $url = '' ) {
41
  if ( empty( $url ) ) {
59
  }
60
 
61
  /**
62
+ * Modifies an url to use the domain associated to the current language.
63
  *
64
  * @since 1.8
65
  *
66
+ * @param string $url The url to modify.
67
+ * @return string The modified url.
68
  */
69
  public function site_url( $url ) {
70
  $lang = $this->get_language_from_url();
73
  }
74
 
75
  /**
76
+ * Fixes the domain for the upload directory.
77
  *
78
  * @since 2.0.6
79
  *
80
+ * @param array $uploads Array of information about the upload directory. @see wp_upload_dir().
81
  * @return array
82
  */
83
  public function upload_dir( $uploads ) {
include/links-default.php CHANGED
@@ -4,9 +4,8 @@
4
  */
5
 
6
  /**
7
- * Links model for default permalinks
8
- * for example mysite.com/?somevar=something&lang=en
9
- * implements the "links_model interface"
10
  *
11
  * @since 1.2
12
  */
@@ -20,66 +19,62 @@ class PLL_Links_Default extends PLL_Links_Model {
20
 
21
  /**
22
  * Adds the language code in a url.
23
- * links_model interface.
24
  *
25
  * @since 1.2
26
  *
27
- * @param string $url The url to modify.
28
- * @param PLL_Language $lang The language object.
29
- * @return string Modified url.
30
  */
31
  public function add_language_to_link( $url, $lang ) {
32
  return empty( $lang ) || ( $this->options['hide_default'] && $this->options['default_lang'] == $lang->slug ) ? $url : add_query_arg( 'lang', $lang->slug, $url );
33
  }
34
 
35
  /**
36
- * Removes the language information from an url
37
- * links_model interface
38
  *
39
  * @since 1.2
40
  *
41
- * @param string $url url to modify
42
- * @return string modified url
43
  */
44
  public function remove_language_from_link( $url ) {
45
  return remove_query_arg( 'lang', $url );
46
  }
47
 
48
  /**
49
- * Returns the link to the first page
50
- * links_model interface
51
  *
52
  * @since 1.2
53
  *
54
- * @param string $url url to modify
55
- * @return string modified url
56
  */
57
  public function remove_paged_from_link( $url ) {
58
  return remove_query_arg( 'paged', $url );
59
  }
60
 
61
  /**
62
- * Returns the link to the paged page when using pretty permalinks
63
  *
64
  * @since 1.5
65
  *
66
- * @param string $url url to modify
67
- * @param int $page
68
- * @return string modified url
69
  */
70
  public function add_paged_to_link( $url, $page ) {
71
  return add_query_arg( array( 'paged' => $page ), $url );
72
  }
73
 
74
  /**
75
- * Gets the language slug from the url if present
76
- * links_model interface
77
  *
78
  * @since 1.2
79
- * @since 2.0 add $url argument
80
  *
81
- * @param string $url optional, defaults to current url
82
- * @return string language slug
83
  */
84
  public function get_language_from_url( $url = '' ) {
85
  if ( empty( $url ) ) {
@@ -91,7 +86,7 @@ class PLL_Links_Default extends PLL_Links_Model {
91
  }
92
 
93
  /**
94
- * Returns the static front page url.
95
  *
96
  * @since 1.8
97
  *
4
  */
5
 
6
  /**
7
+ * Links model for the default permalinks
8
+ * for example mysite.com/?somevar=something&lang=en.
 
9
  *
10
  * @since 1.2
11
  */
19
 
20
  /**
21
  * Adds the language code in a url.
 
22
  *
23
  * @since 1.2
24
  *
25
+ * @param string $url The url to modify.
26
+ * @param PLL_Language|false $lang The language object.
27
+ * @return string The modified url.
28
  */
29
  public function add_language_to_link( $url, $lang ) {
30
  return empty( $lang ) || ( $this->options['hide_default'] && $this->options['default_lang'] == $lang->slug ) ? $url : add_query_arg( 'lang', $lang->slug, $url );
31
  }
32
 
33
  /**
34
+ * Removes the language information from an url.
 
35
  *
36
  * @since 1.2
37
  *
38
+ * @param string $url The url to modify.
39
+ * @return string The modified url.
40
  */
41
  public function remove_language_from_link( $url ) {
42
  return remove_query_arg( 'lang', $url );
43
  }
44
 
45
  /**
46
+ * Returns the link to the first page.
 
47
  *
48
  * @since 1.2
49
  *
50
+ * @param string $url The url to modify.
51
+ * @return string The modified url.
52
  */
53
  public function remove_paged_from_link( $url ) {
54
  return remove_query_arg( 'paged', $url );
55
  }
56
 
57
  /**
58
+ * Returns the link to the paged page.
59
  *
60
  * @since 1.5
61
  *
62
+ * @param string $url The url to modify.
63
+ * @param int $page The page number.
64
+ * @return string The modified url.
65
  */
66
  public function add_paged_to_link( $url, $page ) {
67
  return add_query_arg( array( 'paged' => $page ), $url );
68
  }
69
 
70
  /**
71
+ * Gets the language slug from the url if present.
 
72
  *
73
  * @since 1.2
74
+ * @since 2.0 Add the $url argument.
75
  *
76
+ * @param string $url Optional, defaults to the current url.
77
+ * @return string Language slug.
78
  */
79
  public function get_language_from_url( $url = '' ) {
80
  if ( empty( $url ) ) {
86
  }
87
 
88
  /**
89
+ * Returns the static front page url in the given language.
90
  *
91
  * @since 1.8
92
  *
include/links-directory.php CHANGED
@@ -4,9 +4,8 @@
4
  */
5
 
6
  /**
7
- * Links model for use when the language code is added in url as a directory
8
- * for example mysite.com/en/something
9
- * implements the "links_model interface"
10
  *
11
  * @since 1.2
12
  */
@@ -38,7 +37,7 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
38
  }
39
 
40
  /**
41
- * Called only at first object creation to avoid duplicating filters when switching blog
42
  *
43
  * @since 1.6
44
  *
@@ -51,19 +50,18 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
51
  add_action( 'setup_theme', array( $this, 'add_permastruct' ), 2 );
52
  }
53
 
54
- // Make sure to prepare rewrite rules when flushing
55
  add_action( 'pre_option_rewrite_rules', array( $this, 'prepare_rewrite_rules' ) );
56
  }
57
 
58
  /**
59
  * Adds the language code in a url.
60
- * links_model interface.
61
  *
62
  * @since 1.2
63
  *
64
- * @param string $url The url to modify.
65
- * @param PLL_Language $lang The language object.
66
- * @return string Modified url.
67
  */
68
  public function add_language_to_link( $url, $lang ) {
69
  if ( ! empty( $lang ) ) {
@@ -74,20 +72,19 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
74
  if ( false === strpos( $url, $new = $root . $slug ) ) {
75
  $pattern = preg_quote( $root, '#' );
76
  $pattern = '#' . $pattern . '#';
77
- return preg_replace( $pattern, $new, $url, 1 ); // Only once
78
  }
79
  }
80
  return $url;
81
  }
82
 
83
  /**
84
- * Returns the url without language code
85
- * links_model interface
86
  *
87
  * @since 1.2
88
  *
89
- * @param string $url url to modify
90
- * @return string modified url
91
  */
92
  public function remove_language_from_link( $url ) {
93
  $languages = array();
@@ -101,22 +98,21 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
101
  if ( ! empty( $languages ) ) {
102
  $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root );
103
 
104
- $pattern = preg_quote( $root, '#' );
105
- $pattern = '#' . $pattern . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')(/|$)#';
106
- $url = preg_replace( $pattern, $root, $url );
107
  }
108
  return $url;
109
  }
110
 
111
  /**
112
- * Returns the language based on language code in url
113
- * links_model interface
114
  *
115
  * @since 1.2
116
- * @since 2.0 add $url argument
117
  *
118
- * @param string $url optional, defaults to current url
119
- * @return string language slug
120
  */
121
  public function get_language_from_url( $url = '' ) {
122
  if ( empty( $url ) ) {
@@ -129,16 +125,15 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
129
  $pattern = (string) wp_parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH );
130
  $pattern = preg_quote( $pattern, '#' );
131
  $pattern = '#^' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(/|$)#';
132
- return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language
133
  }
134
 
135
  /**
136
  * Returns the home url in a given language.
137
- * links_model interface.
138
  *
139
  * @since 1.3.1
140
  *
141
- * @param PLL_Language $lang PLL_Language object.
142
  * @return string
143
  */
144
  public function home_url( $lang ) {
@@ -148,16 +143,14 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
148
  }
149
 
150
  /**
151
- * Optionally removes 'language' in permalinks so that we get http://www.myblog/en/ instead of http://www.myblog/language/en/
152
  *
153
  * @since 1.2
154
  *
155
  * @return void
156
  */
157
  public function add_permastruct() {
158
- // Language information always in front of the uri ( 'with_front' => false )
159
- // The 3rd parameter structure has been modified in WP 3.4
160
- // Leads to error 404 for pages when there is no language created yet
161
  if ( $this->model->get_languages_list() ) {
162
  add_permastruct( 'language', $this->options['rewrite'] ? '%language%' : 'language/%language%', array( 'with_front' => false ) );
163
  }
@@ -172,23 +165,26 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
172
  * @return mixed
173
  */
174
  public function prepare_rewrite_rules( $pre ) {
175
- // Don't modify the rules if there is no languages created yet
176
- // Make sure to add filter only once and if all custom post types and taxonomies have been registered
 
 
 
177
  if ( $this->model->get_languages_list() && did_action( 'wp_loaded' ) && ! has_filter( 'language_rewrite_rules', '__return_empty_array' ) ) {
178
- // Suppress the rules created by WordPress for our taxonomy
179
- add_filter( 'language_rewrite_rules', '__return_empty_array' );
180
 
181
  foreach ( $this->get_rewrite_rules_filters() as $type ) {
182
  add_filter( $type . '_rewrite_rules', array( $this, 'rewrite_rules' ) );
183
  }
184
 
185
- add_filter( 'rewrite_rules_array', array( $this, 'rewrite_rules' ) ); // needed for post type archives
186
  }
187
  return $pre;
188
  }
189
 
190
  /**
191
  * The rewrite rules !
 
192
  * Always make sure that the default language is at the end in case the language information is hidden for default language.
193
  * Thanks to brbrbr http://wordpress.org/support/topic/plugin-polylang-rewrite-rules-not-correct.
194
  *
@@ -212,23 +208,23 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
212
  $slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')/';
213
  }
214
 
215
- // For custom post type archives
216
  $cpts = array_intersect( $this->model->get_translated_post_types(), get_post_types( array( '_builtin' => false ) ) );
217
  $cpts = $cpts ? '#post_type=(' . implode( '|', $cpts ) . ')#' : '';
218
 
219
  foreach ( $rules as $key => $rule ) {
220
- // Special case for translated post types and taxonomies to allow canonical redirection
221
  if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) {
222
 
223
  /**
224
- * Filters the rewrite rules to modify
225
  *
226
  * @since 1.9.1
227
  *
228
- * @param bool $modify whether to modify or not the rule, defaults to true
229
- * @param array $rule original rewrite rule
230
- * @param string $filter current set of rules being modified
231
- * @param string|bool $archive custom post post type archive name or false if it is not a cpt archive
232
  */
233
  if ( isset( $slug ) && apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, false ) ) {
234
  $newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace(
@@ -241,7 +237,7 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
241
  $newrules[ $key ] = $rule;
242
  }
243
 
244
- // Rewrite rules filtered by language
245
  elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule, $matches ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) {
246
 
247
  /** This filter is documented in include/links-directory.php */
@@ -262,13 +258,13 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
262
  }
263
  }
264
 
265
- // Unmodified rules
266
  else {
267
  $newrules[ $key ] = $rule;
268
  }
269
  }
270
 
271
- // The home rewrite rule
272
  if ( 'root' == $filter && isset( $slug ) ) {
273
  $newrules[ $slug . '?$' ] = $wp_rewrite->index . '?lang=$matches[1]';
274
  }
4
  */
5
 
6
  /**
7
+ * Links model for use when the language code is added in the url as a directory
8
+ * for example mysite.com/en/something.
 
9
  *
10
  * @since 1.2
11
  */
37
  }
38
 
39
  /**
40
+ * Called only at first object creation to avoid duplicating filters when switching blog.
41
  *
42
  * @since 1.6
43
  *
50
  add_action( 'setup_theme', array( $this, 'add_permastruct' ), 2 );
51
  }
52
 
53
+ // Make sure to prepare rewrite rules when flushing.
54
  add_action( 'pre_option_rewrite_rules', array( $this, 'prepare_rewrite_rules' ) );
55
  }
56
 
57
  /**
58
  * Adds the language code in a url.
 
59
  *
60
  * @since 1.2
61
  *
62
+ * @param string $url The url to modify.
63
+ * @param PLL_Language|false $lang The language object.
64
+ * @return string The modified url.
65
  */
66
  public function add_language_to_link( $url, $lang ) {
67
  if ( ! empty( $lang ) ) {
72
  if ( false === strpos( $url, $new = $root . $slug ) ) {
73
  $pattern = preg_quote( $root, '#' );
74
  $pattern = '#' . $pattern . '#';
75
+ return preg_replace( $pattern, $new, $url, 1 ); // Only once.
76
  }
77
  }
78
  return $url;
79
  }
80
 
81
  /**
82
+ * Returns the url without the language code.
 
83
  *
84
  * @since 1.2
85
  *
86
+ * @param string $url The url to modify.
87
+ * @return string The modified url.
88
  */
89
  public function remove_language_from_link( $url ) {
90
  $languages = array();
98
  if ( ! empty( $languages ) ) {
99
  $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root );
100
 
101
+ $pattern = preg_quote( $root, '@' );
102
+ $pattern = '@' . $pattern . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')(([?#])|(/|$))@';
103
+ $url = preg_replace( $pattern, $root . '$3', $url );
104
  }
105
  return $url;
106
  }
107
 
108
  /**
109
+ * Returns the language based on the language code in the url.
 
110
  *
111
  * @since 1.2
112
+ * @since 2.0 Add the $url argument.
113
  *
114
+ * @param string $url Optional, defaults to the current url.
115
+ * @return string The language slug.
116
  */
117
  public function get_language_from_url( $url = '' ) {
118
  if ( empty( $url ) ) {
125
  $pattern = (string) wp_parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH );
126
  $pattern = preg_quote( $pattern, '#' );
127
  $pattern = '#^' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(/|$)#';
128
+ return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language.
129
  }
130
 
131
  /**
132
  * Returns the home url in a given language.
 
133
  *
134
  * @since 1.3.1
135
  *
136
+ * @param PLL_Language $lang The language object.
137
  * @return string
138
  */
139
  public function home_url( $lang ) {
143
  }
144
 
145
  /**
146
+ * Optionally removes 'language' in permalinks so that we get http://www.myblog/en/ instead of http://www.myblog/language/en/.
147
  *
148
  * @since 1.2
149
  *
150
  * @return void
151
  */
152
  public function add_permastruct() {
153
+ // Language information always in front of the uri ( 'with_front' => false ).
 
 
154
  if ( $this->model->get_languages_list() ) {
155
  add_permastruct( 'language', $this->options['rewrite'] ? '%language%' : 'language/%language%', array( 'with_front' => false ) );
156
  }
165
  * @return mixed
166
  */
167
  public function prepare_rewrite_rules( $pre ) {
168
+ /*
169
+ * Don't modify the rules if there is no languages created yet and make sure
170
+ * to add the filters only once and if all custom post types and taxonomies
171
+ * have been registered.
172
+ */
173
  if ( $this->model->get_languages_list() && did_action( 'wp_loaded' ) && ! has_filter( 'language_rewrite_rules', '__return_empty_array' ) ) {
174
+ add_filter( 'language_rewrite_rules', '__return_empty_array' ); // Suppress the rules created by WordPress for our taxonomy.
 
175
 
176
  foreach ( $this->get_rewrite_rules_filters() as $type ) {
177
  add_filter( $type . '_rewrite_rules', array( $this, 'rewrite_rules' ) );
178
  }
179
 
180
+ add_filter( 'rewrite_rules_array', array( $this, 'rewrite_rules' ) ); // Needed for post type archives.
181
  }
182
  return $pre;
183
  }
184
 
185
  /**
186
  * The rewrite rules !
187
+ *
188
  * Always make sure that the default language is at the end in case the language information is hidden for default language.
189
  * Thanks to brbrbr http://wordpress.org/support/topic/plugin-polylang-rewrite-rules-not-correct.
190
  *
208
  $slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')/';
209
  }
210
 
211
+ // For custom post type archives.
212
  $cpts = array_intersect( $this->model->get_translated_post_types(), get_post_types( array( '_builtin' => false ) ) );
213
  $cpts = $cpts ? '#post_type=(' . implode( '|', $cpts ) . ')#' : '';
214
 
215
  foreach ( $rules as $key => $rule ) {
216
+ // Special case for translated post types and taxonomies to allow canonical redirection.
217
  if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) {
218
 
219
  /**
220
+ * Filters the rewrite rules to modify.
221
  *
222
  * @since 1.9.1
223
  *
224
+ * @param bool $modify Whether to modify or not the rule, defaults to true.
225
+ * @param array $rule Original rewrite rule.
226
+ * @param string $filter Current set of rules being modified.
227
+ * @param string|bool $archive Custom post post type archive name or false if it is not a cpt archive.
228
  */
229
  if ( isset( $slug ) && apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, false ) ) {
230
  $newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace(
237
  $newrules[ $key ] = $rule;
238
  }
239
 
240
+ // Rewrite rules filtered by language.
241
  elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule, $matches ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) {
242
 
243
  /** This filter is documented in include/links-directory.php */
258
  }
259
  }
260
 
261
+ // Unmodified rules.
262
  else {
263
  $newrules[ $key ] = $rule;
264
  }
265
  }
266
 
267
+ // The home rewrite rule.
268
  if ( 'root' == $filter && isset( $slug ) ) {
269
  $newrules[ $slug . '?$' ] = $wp_rewrite->index . '?lang=$matches[1]';
270
  }
include/links-domain.php CHANGED
@@ -5,8 +5,7 @@
5
 
6
  /**
7
  * Links model for use when using one domain per language
8
- * for example mysite.com/sth and mysite.fr/qqch
9
- * implements the "links_model interface"
10
  *
11
  * @since 1.2
12
  */
@@ -20,31 +19,30 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
20
  protected $hosts;
21
 
22
  /**
23
- * Constructor
24
  *
25
  * @since 1.8
26
  *
27
- * @param object $model PLL_Model instance
28
  */
29
  public function __construct( &$model ) {
30
  parent::__construct( $model );
31
 
32
  $this->hosts = $this->get_hosts();
33
 
34
- // Filter the site url ( mainly to get the correct login form )
35
  add_filter( 'site_url', array( $this, 'site_url' ) );
36
  }
37
 
38
 
39
  /**
40
- * Adds the language code in url
41
- * links_model interface
42
  *
43
  * @since 1.2
44
  *
45
- * @param string $url The url to modify.
46
- * @param PLL_Language $lang The language object.
47
- * @return string Modified url.
48
  */
49
  public function add_language_to_link( $url, $lang ) {
50
  if ( ! empty( $lang ) && ! empty( $this->hosts[ $lang->slug ] ) ) {
@@ -54,13 +52,12 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
54
  }
55
 
56
  /**
57
- * Returns the url without language code
58
- * links_model interface
59
  *
60
  * @since 1.2
61
  *
62
- * @param string $url url to modify
63
- * @return string modified url
64
  */
65
  public function remove_language_from_link( $url ) {
66
  if ( ! empty( $this->hosts ) ) {
@@ -71,11 +68,10 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
71
 
72
  /**
73
  * Returns the home url in a given language.
74
- * links_model interface.
75
  *
76
  * @since 1.3.1
77
  *
78
- * @param PLL_Language $lang PLL_Language object.
79
  * @return string
80
  */
81
  public function home_url( $lang ) {
@@ -83,7 +79,7 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
83
  }
84
 
85
  /**
86
- * Get hosts managed on the website.
87
  *
88
  * @since 1.5
89
  *
@@ -93,7 +89,7 @@ class PLL_Links_Domain extends PLL_Links_Abstract_Domain {
93
  $hosts = array();
94
  foreach ( $this->options['domains'] as $lang => $domain ) {
95
  $host = wp_parse_url( $domain, PHP_URL_HOST );
96
- // idn_to_ascii is much faster than the WordPress method.
97
  if ( function_exists( 'idn_to_ascii' ) ) {
98
  // The use of the constant is mandatory in PHP 7.2 and PHP 7.3 to avoid a deprecated notice.
99
  $hosts[ $lang ] = defined( 'INTL_IDNA_VARIANT_UTS46' ) ? idn_to_ascii( $host, 0, INTL_IDNA_VARIANT_UTS46 ) : idn_to_ascii( $host );
5
 
6
  /**
7
  * Links model for use when using one domain per language
8
+ * for example mysite.com/something and mysite.fr/quelquechose.
 
9
  *
10
  * @since 1.2
11
  */
19
  protected $hosts;
20
 
21
  /**
22
+ * Constructor.
23
  *
24
  * @since 1.8
25
  *
26
+ * @param object $model PLL_Model instance.
27
  */
28
  public function __construct( &$model ) {
29
  parent::__construct( $model );
30
 
31
  $this->hosts = $this->get_hosts();
32
 
33
+ // Filters the site url (mainly to get the correct login form).
34
  add_filter( 'site_url', array( $this, 'site_url' ) );
35
  }
36
 
37
 
38
  /**
39
+ * Switches the primary domain to a secondary domain in the url.
 
40
  *
41
  * @since 1.2
42
  *
43
+ * @param string $url The url to modify.
44
+ * @param PLL_Language|false $lang The language object.
45
+ * @return string The modified url.
46
  */
47
  public function add_language_to_link( $url, $lang ) {
48
  if ( ! empty( $lang ) && ! empty( $this->hosts[ $lang->slug ] ) ) {
52
  }
53
 
54
  /**
55
+ * Returns the url with the primary domain.
 
56
  *
57
  * @since 1.2
58
  *
59
+ * @param string $url The url to modify.
60
+ * @return string The modified url.
61
  */
62
  public function remove_language_from_link( $url ) {
63
  if ( ! empty( $this->hosts ) ) {
68
 
69
  /**
70
  * Returns the home url in a given language.
 
71
  *
72
  * @since 1.3.1
73
  *
74
+ * @param PLL_Language $lang The language object.
75
  * @return string
76
  */
77
  public function home_url( $lang ) {
79
  }
80
 
81
  /**
82
+ * Get the hosts managed on the website.
83
  *
84
  * @since 1.5
85
  *
89
  $hosts = array();
90
  foreach ( $this->options['domains'] as $lang => $domain ) {
91
  $host = wp_parse_url( $domain, PHP_URL_HOST );
92
+ // The function idn_to_ascii() is much faster than the WordPress method.
93
  if ( function_exists( 'idn_to_ascii' ) ) {
94
  // The use of the constant is mandatory in PHP 7.2 and PHP 7.3 to avoid a deprecated notice.
95
  $hosts[ $lang ] = defined( 'INTL_IDNA_VARIANT_UTS46' ) ? idn_to_ascii( $host, 0, INTL_IDNA_VARIANT_UTS46 ) : idn_to_ascii( $host );
include/links-model.php CHANGED
@@ -60,9 +60,9 @@ abstract class PLL_Links_Model {
60
  *
61
  * @since 1.2
62
  *
63
- * @param string $url The url to modify.
64
- * @param PLL_Language $lang The language object.
65
- * @return string Modified url.
66
  */
67
  abstract public function add_language_to_link( $url, $lang );
68
 
@@ -72,7 +72,7 @@ abstract class PLL_Links_Model {
72
  * @since 1.2
73
  *
74
  * @param string $url The url to modify.
75
- * @return string Modified url.
76
  */
77
  abstract public function remove_language_from_link( $url );
78
 
@@ -82,7 +82,7 @@ abstract class PLL_Links_Model {
82
  * @since 1.2
83
  *
84
  * @param string $url The url to modify.
85
- * @return string Modified url.
86
  */
87
  abstract public function remove_paged_from_link( $url );
88
 
@@ -93,23 +93,23 @@ abstract class PLL_Links_Model {
93
  *
94
  * @param string $url The url to modify.
95
  * @param int $page The page number.
96
- * @return string Modified url.
97
  */
98
  abstract public function add_paged_to_link( $url, $page );
99
 
100
  /**
101
- * Returns the language based on language code in url.
102
  *
103
  * @since 1.2
104
- * @since 2.0 add $url argument.
105
  *
106
- * @param string $url Optional, defaults to thej current url.
107
- * @return string Language slug.
108
  */
109
  abstract public function get_language_from_url( $url = '' );
110
 
111
  /**
112
- * Returns the static front page url.
113
  *
114
  * @since 1.8
115
  *
@@ -125,7 +125,7 @@ abstract class PLL_Links_Model {
125
  *
126
  * @param string $url The url to modify.
127
  * @param PLL_Language $lang The language object.
128
- * @return string Modified url.
129
  */
130
  public function switch_language_in_link( $url, $lang ) {
131
  $url = $this->remove_language_from_link( $url );
@@ -133,7 +133,7 @@ abstract class PLL_Links_Model {
133
  }
134
 
135
  /**
136
- * Get hosts managed on the website.
137
  *
138
  * @since 1.5
139
  *
@@ -148,7 +148,7 @@ abstract class PLL_Links_Model {
148
  *
149
  * @since 1.3.1
150
  *
151
- * @param PLL_Language $lang PLL_Language object.
152
  * @return string
153
  */
154
  public function home_url( $lang ) {
@@ -161,16 +161,13 @@ abstract class PLL_Links_Model {
161
  *
162
  * @since 1.8
163
  *
164
- * @param PLL_Language $language PLL_Language object.
165
  * @return void
166
  */
167
  protected function set_home_url( $language ) {
168
- // We should always have a default language here, except, temporarily, in PHPUnit tests. The test here protects against PHP notices.
169
- if ( isset( $this->options['default_lang'] ) ) {
170
- $search_url = $this->home_url( $language );
171
- $home_url = empty( $language->page_on_front ) || $this->options['redirect_lang'] ? $search_url : $this->front_page_url( $language );
172
- $language->set_home_url( $search_url, $home_url );
173
- }
174
  }
175
 
176
  /**
@@ -205,7 +202,7 @@ abstract class PLL_Links_Model {
205
  $this->set_home_url( $language );
206
  }
207
 
208
- // Ensures that the ( possibly cached ) home and flag urls use the right scheme http or https.
209
  $language->set_url_scheme();
210
  }
211
  return $languages;
60
  *
61
  * @since 1.2
62
  *
63
+ * @param string $url The url to modify.
64
+ * @param PLL_Language|false $lang The language object.
65
+ * @return string The modified url.
66
  */
67
  abstract public function add_language_to_link( $url, $lang );
68
 
72
  * @since 1.2
73
  *
74
  * @param string $url The url to modify.
75
+ * @return string The modified url.
76
  */
77
  abstract public function remove_language_from_link( $url );
78
 
82
  * @since 1.2
83
  *
84
  * @param string $url The url to modify.
85
+ * @return string The modified url.
86
  */
87
  abstract public function remove_paged_from_link( $url );
88
 
93
  *
94
  * @param string $url The url to modify.
95
  * @param int $page The page number.
96
+ * @return string The modified url.
97
  */
98
  abstract public function add_paged_to_link( $url, $page );
99
 
100
  /**
101
+ * Returns the language based on the language code in the url.
102
  *
103
  * @since 1.2
104
+ * @since 2.0 Add the $url argument.
105
  *
106
+ * @param string $url Optional, defaults to the current url.
107
+ * @return string The language slug.
108
  */
109
  abstract public function get_language_from_url( $url = '' );
110
 
111
  /**
112
+ * Returns the static front page url in a given language.
113
  *
114
  * @since 1.8
115
  *
125
  *
126
  * @param string $url The url to modify.
127
  * @param PLL_Language $lang The language object.
128
+ * @return string The modified url.
129
  */
130
  public function switch_language_in_link( $url, $lang ) {
131
  $url = $this->remove_language_from_link( $url );
133
  }
134
 
135
  /**
136
+ * Get the hosts managed on the website.
137
  *
138
  * @since 1.5
139
  *
148
  *
149
  * @since 1.3.1
150
  *
151
+ * @param PLL_Language $lang The language object.
152
  * @return string
153
  */
154
  public function home_url( $lang ) {
161
  *
162
  * @since 1.8
163
  *
164
+ * @param PLL_Language $language The language object.
165
  * @return void
166
  */
167
  protected function set_home_url( $language ) {
168
+ $search_url = $this->home_url( $language );
169
+ $home_url = empty( $language->page_on_front ) || $this->options['redirect_lang'] ? $search_url : $this->front_page_url( $language );
170
+ $language->set_home_url( $search_url, $home_url );
 
 
 
171
  }
172
 
173
  /**
202
  $this->set_home_url( $language );
203
  }
204
 
205
+ // Ensures that the (possibly cached) home and flag urls use the right scheme http or https.
206
  $language->set_url_scheme();
207
  }
208
  return $languages;
include/links-permalinks.php CHANGED
@@ -4,7 +4,7 @@
4
  */
5
 
6
  /**
7
- * Links model base class when using pretty permalinks
8
  *
9
  * @since 1.6
10
  */
@@ -56,60 +56,60 @@ abstract class PLL_Links_Permalinks extends PLL_Links_Model {
56
  public function __construct( &$model ) {
57
  parent::__construct( $model );
58
 
59
- // Inspired by wp-includes/rewrite.php
60
  $permalink_structure = get_option( 'permalink_structure' );
61
  $this->root = preg_match( '#^/*' . $this->index . '#', $permalink_structure ) ? $this->index . '/' : '';
62
  $this->use_trailing_slashes = ( '/' == substr( $permalink_structure, -1, 1 ) );
63
  }
64
 
65
  /**
66
- * Returns the link to the first page when using pretty permalinks
67
  *
68
  * @since 1.2
69
  *
70
- * @param string $url url to modify
71
- * @return string modified url
72
  */
73
  public function remove_paged_from_link( $url ) {
74
  /**
75
- * Filter an url after the paged part has been removed
76
  *
77
  * @since 2.0.6
78
  *
79
- * @param string $modified_url The link to the first page
80
- * @param string $original_url The link to the original paged page
81
  */
82
  return apply_filters( 'pll_remove_paged_from_link', preg_replace( '#/page/[0-9]+/?#', $this->use_trailing_slashes ? '/' : '', $url ), $url );
83
  }
84
 
85
  /**
86
- * Returns the link to the paged page when using pretty permalinks
87
  *
88
  * @since 1.5
89
  *
90
- * @param string $url url to modify
91
- * @param int $page
92
- * @return string modified url
93
  */
94
  public function add_paged_to_link( $url, $page ) {
95
  /**
96
- * Filter an url after the paged part has been added
97
  *
98
  * @since 2.0.6
99
  *
100
- * @param string $modified_url The link to the paged page
101
- * @param string $original_url The link to the original first page
102
- * @param int $page The page number
103
  */
104
  return apply_filters( 'pll_add_paged_to_link', user_trailingslashit( trailingslashit( $url ) . 'page/' . $page, 'paged' ), $url, $page );
105
  }
106
 
107
  /**
108
- * Returns the home url.
109
  *
110
  * @since 1.3.1
111
  *
112
- * @param PLL_Language $lang PLL_Language object.
113
  * @return string
114
  */
115
  public function home_url( $lang ) {
@@ -134,23 +134,23 @@ abstract class PLL_Links_Permalinks extends PLL_Links_Model {
134
  }
135
 
136
  /**
137
- * Prepares rewrite rules filters
138
  *
139
  * @since 1.6
140
  *
141
  * @return string[]
142
  */
143
  public function get_rewrite_rules_filters() {
144
- // Make sure we have the right post types and taxonomies
145
  $types = array_values( array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies(), $this->model->get_filtered_taxonomies() ) );
146
  $types = array_merge( $this->always_rewrite, $types );
147
 
148
  /**
149
- * Filter the list of rewrite rules filters to be used by Polylang
150
  *
151
  * @since 0.8.1
152
  *
153
- * @param array $types the list of filters (without '_rewrite_rules' at the end)
154
  */
155
  return apply_filters( 'pll_rewrite_rules', $types );
156
  }
4
  */
5
 
6
  /**
7
+ * Links model base class when using pretty permalinks.
8
  *
9
  * @since 1.6
10
  */
56
  public function __construct( &$model ) {
57
  parent::__construct( $model );
58
 
59
+ // Inspired by WP_Rewrite.
60
  $permalink_structure = get_option( 'permalink_structure' );
61
  $this->root = preg_match( '#^/*' . $this->index . '#', $permalink_structure ) ? $this->index . '/' : '';
62
  $this->use_trailing_slashes = ( '/' == substr( $permalink_structure, -1, 1 ) );
63
  }
64
 
65
  /**
66
+ * Returns the link to the first page when using pretty permalinks.
67
  *
68
  * @since 1.2
69
  *
70
+ * @param string $url The url to modify.
71
+ * @return string The modified url.
72
  */
73
  public function remove_paged_from_link( $url ) {
74
  /**
75
+ * Filters an url after the paged part has been removed.
76
  *
77
  * @since 2.0.6
78
  *
79
+ * @param string $modified_url The link to the first page.
80
+ * @param string $original_url The link to the original paged page.
81
  */
82
  return apply_filters( 'pll_remove_paged_from_link', preg_replace( '#/page/[0-9]+/?#', $this->use_trailing_slashes ? '/' : '', $url ), $url );
83
  }
84
 
85
  /**
86
+ * Returns the link to the paged page when using pretty permalinks.
87
  *
88
  * @since 1.5
89
  *
90
+ * @param string $url The url to modify.
91
+ * @param int $page The page number.
92
+ * @return string The modified url.
93
  */
94
  public function add_paged_to_link( $url, $page ) {
95
  /**
96
+ * Filters an url after the paged part has been added.
97
  *
98
  * @since 2.0.6
99
  *
100
+ * @param string $modified_url The link to the paged page.
101
+ * @param string $original_url The link to the original first page.
102
+ * @param int $page The page number.
103
  */
104
  return apply_filters( 'pll_add_paged_to_link', user_trailingslashit( trailingslashit( $url ) . 'page/' . $page, 'paged' ), $url, $page );
105
  }
106
 
107
  /**
108
+ * Returns the home url in a given language.
109
  *
110
  * @since 1.3.1
111
  *
112
+ * @param PLL_Language $lang A language object.
113
  * @return string
114
  */
115
  public function home_url( $lang ) {
134
  }
135
 
136
  /**
137
+ * Prepares rewrite rules filters.
138
  *
139
  * @since 1.6
140
  *
141
  * @return string[]
142
  */
143
  public function get_rewrite_rules_filters() {
144
+ // Make sure that we have the right post types and taxonomies.
145
  $types = array_values( array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies(), $this->model->get_filtered_taxonomies() ) );
146
  $types = array_merge( $this->always_rewrite, $types );
147
 
148
  /**
149
+ * Filters the list of rewrite rules filters to be used by Polylang.
150
  *
151
  * @since 0.8.1
152
  *
153
+ * @param array $types The list of filters (without '_rewrite_rules' at the end).
154
  */
155
  return apply_filters( 'pll_rewrite_rules', $types );
156
  }
include/links-subdomain.php CHANGED
@@ -4,9 +4,8 @@
4
  */
5
 
6
  /**
7
- * Links model for use when the language code is added in url as a subdomain
8
- * for example en.mysite.com/something
9
- * implements the "links_model interface"
10
  *
11
  * @since 1.2
12
  */
@@ -33,13 +32,12 @@ class PLL_Links_Subdomain extends PLL_Links_Abstract_Domain {
33
 
34
  /**
35
  * Adds the language code in a url.
36
- * links_model interface.
37
  *
38
  * @since 1.2
39
  *
40
- * @param string $url The url to modify.
41
- * @param PLL_Language $lang The language object.
42
- * @return string Modified url.
43
  */
44
  public function add_language_to_link( $url, $lang ) {
45
  if ( ! empty( $lang ) && false === strpos( $url, '://' . $lang->slug . '.' ) ) {
@@ -49,13 +47,12 @@ class PLL_Links_Subdomain extends PLL_Links_Abstract_Domain {
49
  }
50
 
51
  /**
52
- * Returns the url without language code
53
- * links_model interface
54
  *
55
  * @since 1.2
56
  *
57
- * @param string $url url to modify
58
- * @return string modified url
59
  */
60
  public function remove_language_from_link( $url ) {
61
  $languages = array();
@@ -74,7 +71,7 @@ class PLL_Links_Subdomain extends PLL_Links_Abstract_Domain {
74
  }
75
 
76
  /**
77
- * Get hosts managed on the website.
78
  *
79
  * @since 1.5
80
  *
@@ -83,7 +80,8 @@ class PLL_Links_Subdomain extends PLL_Links_Abstract_Domain {
83
  public function get_hosts() {
84
  $hosts = array();
85
  foreach ( $this->model->get_languages_list() as $lang ) {
86
- $hosts[ $lang->slug ] = wp_parse_url( $this->home_url( $lang ), PHP_URL_HOST );
 
87
  }
88
  return $hosts;
89
  }
4
  */
5
 
6
  /**
7
+ * Links model for use when the language code is added in the url as a subdomain
8
+ * for example en.mysite.com/something.
 
9
  *
10
  * @since 1.2
11
  */
32
 
33
  /**
34
  * Adds the language code in a url.
 
35
  *
36
  * @since 1.2
37
  *
38
+ * @param string $url The url to modify.
39
+ * @param PLL_Language|false $lang The language object.
40
+ * @return string The modified url.
41
  */
42
  public function add_language_to_link( $url, $lang ) {
43
  if ( ! empty( $lang ) && false === strpos( $url, '://' . $lang->slug . '.' ) ) {
47
  }
48
 
49
  /**
50
+ * Returns the url without the language code.
 
51
  *
52
  * @since 1.2
53
  *
54
+ * @param string $url The url to modify.
55
+ * @return string The modified url.
56
  */
57
  public function remove_language_from_link( $url ) {
58
  $languages = array();
71
  }
72
 
73
  /**
74
+ * Get the hosts managed on the website.
75
  *
76
  * @since 1.5
77
  *
80
  public function get_hosts() {
81
  $hosts = array();
82
  foreach ( $this->model->get_languages_list() as $lang ) {
83
+ $host = wp_parse_url( $this->home_url( $lang ), PHP_URL_HOST );
84
+ $hosts[ $lang->slug ] = $host ? $host : '';
85
  }
86
  return $hosts;
87
  }
include/links.php CHANGED
@@ -28,6 +28,13 @@ class PLL_Links {
28
  */
29
  public $links_model;
30
 
 
 
 
 
 
 
 
31
  /**
32
  * Constructor
33
  *
28
  */
29
  public $links_model;
30
 
31
+ /**
32
+ * Current language (used to filter the content).
33
+ *
34
+ * @var PLL_Language|null
35
+ */
36
+ public $curlang;
37
+
38
  /**
39
  * Constructor
40
  *
include/model.php CHANGED
@@ -65,6 +65,25 @@ class PLL_Model {
65
  add_filter( 'language_description', '__return_empty_string' );
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /**
69
  * Returns the list of available languages.
70
  * - Stores the list in a db transient ( except flags ), unless PLL_CACHE_LANGUAGES is set to false.
@@ -88,7 +107,7 @@ class PLL_Model {
88
 
89
  $post_languages = $this->get_language_terms();
90
 
91
- $term_languages = get_terms( 'term_language', array( 'hide_empty' => false ) );
92
  $term_languages = empty( $term_languages ) || is_wp_error( $term_languages ) ?
93
  array() : array_combine( wp_list_pluck( $term_languages, 'slug' ), $term_languages );
94
 
@@ -216,9 +235,9 @@ class PLL_Model {
216
  *
217
  * @since 1.2
218
  *
219
- * @param string[] $clauses The list of sql clauses in terms query.
220
- * @param PLL_Language $lang PLL_Language object.
221
- * @return string[] Modified list of clauses.
222
  */
223
  public function terms_clauses( $clauses, $lang ) {
224
  if ( ! empty( $lang ) && false === strpos( $clauses['join'], 'pll_tr' ) ) {
@@ -411,11 +430,16 @@ class PLL_Model {
411
  * @param string $taxonomy Taxonomy name.
412
  * @param int $parent Parent term id.
413
  * @param string|PLL_Language $language The language slug or object.
414
- * @return null|int The term_id of the found term.
415
  */
416
  public function term_exists( $term_name, $taxonomy, $parent, $language ) {
417
  global $wpdb;
418
 
 
 
 
 
 
419
  $term_name = trim( wp_unslash( $term_name ) );
420
  $term_name = _wp_specialchars( $term_name );
421
 
@@ -423,7 +447,7 @@ class PLL_Model {
423
  $join = " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
424
  $join .= $this->term->join_clause();
425
  $where = $wpdb->prepare( ' WHERE tt.taxonomy = %s AND t.name = %s', $taxonomy, $term_name );
426
- $where .= $this->term->where_clause( $this->get_language( $language ) );
427
 
428
  if ( $parent > 0 ) {
429
  $where .= $wpdb->prepare( ' AND tt.parent = %d', $parent );
@@ -443,16 +467,21 @@ class PLL_Model {
443
  * @param string|PLL_Language $language The language slug or object.
444
  * @param string $taxonomy Optional taxonomy name.
445
  * @param int $parent Optional parent term id.
446
- * @return null|int The term_id of the found term.
447
  */
448
  public function term_exists_by_slug( $slug, $language, $taxonomy = '', $parent = 0 ) {
449
  global $wpdb;
450
 
 
 
 
 
 
451
  $select = "SELECT t.term_id FROM {$wpdb->terms} AS t";
452
  $join = " INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
453
  $join .= $this->term->join_clause();
454
  $where = $wpdb->prepare( ' WHERE t.slug = %s', $slug );
455
- $where .= $this->term->where_clause( $this->get_language( $language ) );
456
 
457
  if ( ! empty( $taxonomy ) ) {
458
  $where .= $wpdb->prepare( ' AND tt.taxonomy = %s', $taxonomy );
@@ -699,7 +728,7 @@ class PLL_Model {
699
  SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (%s)
700
  )
701
  %s",
702
- implode( "','", array_map( 'esc_sql', $taxonomies ) ),
703
  implode( ',', array_map( 'intval', $languages ) ),
704
  $limit > 0 ? sprintf( 'LIMIT %d', intval( $limit ) ) : ''
705
  );
@@ -756,7 +785,7 @@ class PLL_Model {
756
  *
757
  * @since 3.2.3
758
  *
759
- * @return array<WP_Term>
760
  */
761
  protected function get_language_terms() {
762
  add_filter( 'get_terms_orderby', array( $this, 'filter_language_terms_orderby' ), 10, 3 );
65
  add_filter( 'language_description', '__return_empty_string' );
66
  }
67
 
68
+ /**
69
+ * Checks if there are languages or not.
70
+ *
71
+ * @since 3.3
72
+ *
73
+ * @return bool True if there are, false otherwise.
74
+ */
75
+ public function has_languages() {
76
+ if ( false !== $this->cache->get( 'languages' ) ) {
77
+ return true;
78
+ }
79
+
80
+ if ( false !== get_transient( 'pll_languages_list' ) ) {
81
+ return true;
82
+ }
83
+
84
+ return ! empty( $this->get_language_terms() );
85
+ }
86
+
87
  /**
88
  * Returns the list of available languages.
89
  * - Stores the list in a db transient ( except flags ), unless PLL_CACHE_LANGUAGES is set to false.
107
 
108
  $post_languages = $this->get_language_terms();
109
 
110
+ $term_languages = get_terms( array( 'taxonomy' => 'term_language', 'hide_empty' => false ) );
111
  $term_languages = empty( $term_languages ) || is_wp_error( $term_languages ) ?
112
  array() : array_combine( wp_list_pluck( $term_languages, 'slug' ), $term_languages );
113
 
235
  *
236
  * @since 1.2
237
  *
238
+ * @param string[] $clauses The list of sql clauses in terms query.
239
+ * @param PLL_Language|false $lang PLL_Language object.
240
+ * @return string[] Modified list of clauses.
241
  */
242
  public function terms_clauses( $clauses, $lang ) {
243
  if ( ! empty( $lang ) && false === strpos( $clauses['join'], 'pll_tr' ) ) {
430
  * @param string $taxonomy Taxonomy name.
431
  * @param int $parent Parent term id.
432
  * @param string|PLL_Language $language The language slug or object.
433
+ * @return int The `term_id` of the found term. 0 otherwise.
434
  */
435
  public function term_exists( $term_name, $taxonomy, $parent, $language ) {
436
  global $wpdb;
437
 
438
+ $language = $this->get_language( $language );
439
+ if ( empty( $language ) ) {
440
+ return 0;
441
+ }
442
+
443
  $term_name = trim( wp_unslash( $term_name ) );
444
  $term_name = _wp_specialchars( $term_name );
445
 
447
  $join = " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
448
  $join .= $this->term->join_clause();
449
  $where = $wpdb->prepare( ' WHERE tt.taxonomy = %s AND t.name = %s', $taxonomy, $term_name );
450
+ $where .= $this->term->where_clause( $language );
451
 
452
  if ( $parent > 0 ) {
453
  $where .= $wpdb->prepare( ' AND tt.parent = %d', $parent );
467
  * @param string|PLL_Language $language The language slug or object.
468
  * @param string $taxonomy Optional taxonomy name.
469
  * @param int $parent Optional parent term id.
470
+ * @return int The `term_id` of the found term. 0 otherwise.
471
  */
472
  public function term_exists_by_slug( $slug, $language, $taxonomy = '', $parent = 0 ) {
473
  global $wpdb;
474
 
475
+ $language = $this->get_language( $language );
476
+ if ( empty( $language ) ) {
477
+ return 0;
478
+ }
479
+
480
  $select = "SELECT t.term_id FROM {$wpdb->terms} AS t";
481
  $join = " INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
482
  $join .= $this->term->join_clause();
483
  $where = $wpdb->prepare( ' WHERE t.slug = %s', $slug );
484
+ $where .= $this->term->where_clause( $language );
485
 
486
  if ( ! empty( $taxonomy ) ) {
487
  $where .= $wpdb->prepare( ' AND tt.taxonomy = %s', $taxonomy );
728
  SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (%s)
729
  )
730
  %s",
731
+ implode( "','", esc_sql( $taxonomies ) ),
732
  implode( ',', array_map( 'intval', $languages ) ),
733
  $limit > 0 ? sprintf( 'LIMIT %d', intval( $limit ) ) : ''
734
  );
785
  *
786
  * @since 3.2.3
787
  *
788
+ * @return WP_Term[]
789
  */
790
  protected function get_language_terms() {
791
  add_filter( 'get_terms_orderby', array( $this, 'filter_language_terms_orderby' ), 10, 3 );
include/olt-manager.php CHANGED
@@ -16,14 +16,14 @@ class PLL_OLT_Manager {
16
  /**
17
  * Singleton instance
18
  *
19
- * @var PLL_OLT_Manager
20
  */
21
  protected static $instance;
22
 
23
  /**
24
  * Stores the default site locale before it is modified.
25
  *
26
- * @var string
27
  */
28
  protected $default_locale;
29
 
@@ -93,9 +93,9 @@ class PLL_OLT_Manager {
93
  */
94
  public function load_textdomains() {
95
  // Our load_textdomain_mofile filter has done its job. let's remove it before calling load_textdomain
96
- remove_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ), 10, 2 );
97
- remove_filter( 'gettext', array( $this, 'gettext' ), 10, 3 );
98
- remove_filter( 'gettext_with_context', array( $this, 'gettext_with_context' ), 10, 4 );
99
  $new_locale = get_locale();
100
 
101
  // Don't try to save time for en_US as some users have theme written in another language
16
  /**
17
  * Singleton instance
18
  *
19
+ * @var PLL_OLT_Manager|null
20
  */
21
  protected static $instance;
22
 
23
  /**
24
  * Stores the default site locale before it is modified.
25
  *
26
+ * @var string|null
27
  */
28
  protected $default_locale;
29
 
93
  */
94
  public function load_textdomains() {
95
  // Our load_textdomain_mofile filter has done its job. let's remove it before calling load_textdomain
96
+ remove_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ) );
97
+ remove_filter( 'gettext', array( $this, 'gettext' ) );
98
+ remove_filter( 'gettext_with_context', array( $this, 'gettext_with_context' ) );
99
  $new_locale = get_locale();
100
 
101
  // Don't try to save time for en_US as some users have theme written in another language
include/query.php CHANGED
@@ -92,7 +92,7 @@ class PLL_Query {
92
  * @return array queried taxonomies
93
  */
94
  public function get_queried_taxonomies() {
95
- return isset( $this->query->tax_query->queried_terms ) ? array_keys( wp_list_filter( $this->query->tax_query->queried_terms, array( 'operator' => 'NOT IN' ), 'NOT' ) ) : array();
96
  }
97
 
98
  /**
@@ -100,16 +100,22 @@ class PLL_Query {
100
  * Optimized for (and requires) WP 3.5+.
101
  *
102
  * @since 2.2
 
103
  *
104
- * @param PLL_Language $lang Language object.
105
  * @return void
106
  */
107
- public function set_language( $lang ) {
108
- // Defining directly the tax_query ( rather than setting 'lang' avoids transforming the query by WP )
 
 
 
 
 
109
  $lang_query = array(
110
  'taxonomy' => 'language',
111
  'field' => 'term_taxonomy_id', // Since WP 3.5
112
- 'terms' => $lang->term_taxonomy_id,
113
  'operator' => 'IN',
114
  );
115
 
@@ -118,7 +124,7 @@ class PLL_Query {
118
  if ( isset( $tax_query['relation'] ) && 'OR' === $tax_query['relation'] ) {
119
  $tax_query = array(
120
  $lang_query,
121
- array( $tax_query ),
122
  'relation' => 'AND',
123
  );
124
  } elseif ( is_array( $tax_query ) ) {
@@ -137,7 +143,7 @@ class PLL_Query {
137
  *
138
  * @since 2.2
139
  *
140
- * @param PLL_Language $lang Language.
141
  * @return void
142
  */
143
  public function filter_query( $lang ) {
@@ -174,6 +180,8 @@ class PLL_Query {
174
  }
175
  }
176
  } else {
 
 
177
  // Do not filter untranslatable post types such as nav_menu_item
178
  if ( isset( $qvars['post_type'] ) && ! $this->model->is_translated_post_type( $qvars['post_type'] ) && ( empty( $qvars['tax_query'] ) || ! $this->have_translated_taxonomy( $qvars['tax_query'] ) ) ) {
179
  unset( $qvars['lang'] );
@@ -185,4 +193,38 @@ class PLL_Query {
185
  }
186
  }
187
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  }
92
  * @return array queried taxonomies
93
  */
94
  public function get_queried_taxonomies() {
95
+ return ! empty( $this->query->tax_query->queried_terms ) ? array_keys( wp_list_filter( $this->query->tax_query->queried_terms, array( 'operator' => 'NOT IN' ), 'NOT' ) ) : array();
96
  }
97
 
98
  /**
100
  * Optimized for (and requires) WP 3.5+.
101
  *
102
  * @since 2.2
103
+ * @since 3.3 Accepts now an array of languages.
104
  *
105
+ * @param PLL_Language|PLL_Language[] $languages Language object(s).
106
  * @return void
107
  */
108
+ public function set_language( $languages ) {
109
+ if ( ! is_array( $languages ) ) {
110
+ $languages = array( $languages );
111
+ }
112
+ $tt_ids = wp_list_pluck( $languages, 'term_taxonomy_id' );
113
+
114
+ // Defining directly the tax_query (rather than setting 'lang' avoids transforming the query by WP).
115
  $lang_query = array(
116
  'taxonomy' => 'language',
117
  'field' => 'term_taxonomy_id', // Since WP 3.5
118
+ 'terms' => $tt_ids,
119
  'operator' => 'IN',
120
  );
121
 
124
  if ( isset( $tax_query['relation'] ) && 'OR' === $tax_query['relation'] ) {
125
  $tax_query = array(
126
  $lang_query,
127
+ $tax_query,
128
  'relation' => 'AND',
129
  );
130
  } elseif ( is_array( $tax_query ) ) {
143
  *
144
  * @since 2.2
145
  *
146
+ * @param PLL_Language|false $lang Language.
147
  * @return void
148
  */
149
  public function filter_query( $lang ) {
180
  }
181
  }
182
  } else {
183
+ $this->maybe_set_language_for_or_relation();
184
+
185
  // Do not filter untranslatable post types such as nav_menu_item
186
  if ( isset( $qvars['post_type'] ) && ! $this->model->is_translated_post_type( $qvars['post_type'] ) && ( empty( $qvars['tax_query'] ) || ! $this->have_translated_taxonomy( $qvars['tax_query'] ) ) ) {
187
  unset( $qvars['lang'] );
193
  }
194
  }
195
  }
196
+
197
+ /**
198
+ * Sets the language correctly if the current query is a 'OR' relation,
199
+ * since WordPress merges the language with the other query vars when the relation is OR.
200
+ *
201
+ * @since 3.3
202
+ *
203
+ * @return void
204
+ */
205
+ protected function maybe_set_language_for_or_relation() {
206
+ if ( ! $this->query->tax_query instanceof WP_Tax_Query ) {
207
+ return;
208
+ }
209
+
210
+ if ( 'OR' !== $this->query->tax_query->relation ) {
211
+ return;
212
+ }
213
+
214
+ if ( ! isset( $this->query->tax_query->queried_terms['language'] ) ) {
215
+ return;
216
+ }
217
+
218
+ $langs = $this->query->tax_query->queried_terms['language']['terms'];
219
+ if ( is_string( $langs ) ) {
220
+ $langs = explode( ',', $langs );
221
+ }
222
+ $langs = array_map( array( $this->model, 'get_language' ), $langs );
223
+ $langs = array_filter( $langs );
224
+
225
+ if ( ! empty( $langs ) ) {
226
+ $this->set_language( $langs );
227
+ unset( $this->query->query_vars['lang'] ); // Unset the language query var otherwise WordPress would add the language query by slug in WP_Query::parse_tax_query().
228
+ }
229
+ }
230
  }
include/rest-request.php CHANGED
@@ -59,10 +59,37 @@ class PLL_REST_Request extends PLL_Base {
59
  return;
60
  }
61
 
62
- if ( ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
63
- if ( is_string( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
64
- $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
65
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  if ( empty( $this->curlang ) && ! empty( $this->options['default_lang'] ) && is_string( $this->options['default_lang'] ) ) {
68
  // A lang has been requested but it is invalid, let's fall back to the default one.
@@ -78,16 +105,6 @@ class PLL_REST_Request extends PLL_Base {
78
  do_action( 'pll_no_language_defined' ); // To load overridden textdomains.
79
  }
80
 
81
- $this->filters_links = new PLL_Filters_Links( $this );
82
- $this->filters = new PLL_Filters( $this );
83
- $this->filters_widgets_options = new PLL_Filters_Widgets_Options( $this );
84
-
85
- // Static front page and page for posts.
86
- if ( 'page' === get_option( 'show_on_front' ) ) {
87
- $this->static_pages = new PLL_Static_Pages( $this );
88
- }
89
-
90
- $this->links = new PLL_Admin_Links( $this );
91
- $this->nav_menu = new PLL_Nav_Menu( $this ); // For auto added pages to menu.
92
  }
93
  }
59
  return;
60
  }
61
 
62
+ add_filter( 'rest_pre_dispatch', array( $this, 'set_language' ), 10, 3 );
63
+
64
+ $this->filters_links = new PLL_Filters_Links( $this );
65
+ $this->filters = new PLL_Filters( $this );
66
+ $this->filters_widgets_options = new PLL_Filters_Widgets_Options( $this );
67
+
68
+ // Static front page and page for posts.
69
+ if ( 'page' === get_option( 'show_on_front' ) ) {
70
+ $this->static_pages = new PLL_Static_Pages( $this );
71
+ }
72
+
73
+ $this->links = new PLL_Admin_Links( $this );
74
+ $this->nav_menu = new PLL_Frontend_Nav_Menu( $this ); // For auto added pages to menu.
75
+ }
76
+
77
+ /**
78
+ * Sets the current language during a REST request if sent.
79
+ *
80
+ * @since 3.3
81
+ *
82
+ * @param mixed $result Response to replace the requested version with. Remains untouched.
83
+ * @param WP_REST_Server $server Server instance.
84
+ * @param WP_REST_Request $request Request used to generate the response.
85
+ *
86
+ * @return mixed Untouched $result.
87
+ */
88
+ public function set_language( $result, $server, $request ) {
89
+ $lang = $request->get_param( 'lang' );
90
+
91
+ if ( ! empty( $lang ) && is_string( $lang ) ) {
92
+ $this->curlang = $this->model->get_language( sanitize_key( $lang ) );
93
 
94
  if ( empty( $this->curlang ) && ! empty( $this->options['default_lang'] ) && is_string( $this->options['default_lang'] ) ) {
95
  // A lang has been requested but it is invalid, let's fall back to the default one.
105
  do_action( 'pll_no_language_defined' ); // To load overridden textdomains.
106
  }
107
 
108
+ return $result;
 
 
 
 
 
 
 
 
 
 
109
  }
110
  }
include/static-pages.php CHANGED
@@ -12,14 +12,14 @@ class PLL_Static_Pages {
12
  /**
13
  * Id of the page on front.
14
  *
15
- * @var int
16
  */
17
  public $page_on_front;
18
 
19
  /**
20
  * Id of the page for posts.
21
  *
22
- * @var int
23
  */
24
  public $page_for_posts;
25
 
@@ -38,7 +38,7 @@ class PLL_Static_Pages {
38
  /**
39
  * Current language.
40
  *
41
- * @var PLL_Language
42
  */
43
  protected $curlang;
44
 
@@ -56,6 +56,8 @@ class PLL_Static_Pages {
56
 
57
  $this->init();
58
 
 
 
59
  // Modifies the page link in case the front page is not in the default language
60
  add_filter( 'page_link', array( $this, 'page_link' ), 20, 2 );
61
 
@@ -88,6 +90,19 @@ class PLL_Static_Pages {
88
  }
89
  }
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  /**
92
  * Modifies the page link in case the front page is not in the default language
93
  *
@@ -127,16 +142,30 @@ class PLL_Static_Pages {
127
  }
128
 
129
  /**
130
- * Translates page for posts
131
  *
132
  * @since 1.8
 
133
  *
134
- * @param int $v page for posts page id
135
  * @return int
136
  */
137
- public function translate_page_for_posts( $v ) {
138
- // Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache
139
- return isset( $this->curlang->page_for_posts ) && ( $this->curlang->page_for_posts ) && ! doing_action( 'switch_blog' ) ? $this->curlang->page_for_posts : $v;
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
 
142
  /**
@@ -151,10 +180,11 @@ class PLL_Static_Pages {
151
  */
152
  public function oembed_request_post_id( $post_id, $url ) {
153
  foreach ( $this->model->get_languages_list() as $lang ) {
154
- if ( trailingslashit( $url ) === trailingslashit( $lang->home_url ) ) {
155
- $post_id = $lang->page_on_front;
156
  }
157
  }
 
158
  return $post_id;
159
  }
160
  }
12
  /**
13
  * Id of the page on front.
14
  *
15
+ * @var int|null
16
  */
17
  public $page_on_front;
18
 
19
  /**
20
  * Id of the page for posts.
21
  *
22
+ * @var int|null
23
  */
24
  public $page_for_posts;
25
 
38
  /**
39
  * Current language.
40
  *
41
+ * @var PLL_Language|null
42
  */
43
  protected $curlang;
44
 
56
 
57
  $this->init();
58
 
59
+ add_action( 'pll_language_defined', array( $this, 'pll_language_defined' ) );
60
+
61
  // Modifies the page link in case the front page is not in the default language
62
  add_filter( 'page_link', array( $this, 'page_link' ), 20, 2 );
63
 
90
  }
91
  }
92
 
93
+ /**
94
+ * Init the hooks that filter the "page on front" and "page for posts" options.
95
+ *
96
+ * @since 3.3
97
+ *
98
+ * @return void
99
+ */
100
+ public function pll_language_defined() {
101
+ // Translates page for posts and page on front.
102
+ add_filter( 'option_page_on_front', array( $this, 'translate_page_on_front' ) );
103
+ add_filter( 'option_page_for_posts', array( $this, 'translate_page_for_posts' ) );
104
+ }
105
+
106
  /**
107
  * Modifies the page link in case the front page is not in the default language
108
  *
142
  }
143
 
144
  /**
145
+ * Translates the page on front option.
146
  *
147
  * @since 1.8
148
+ * @since 3.3 Was previously defined in PLL_Frontend_Static_Pages.
149
  *
150
+ * @param int $page_id ID of the page on front.
151
  * @return int
152
  */
153
+ public function translate_page_on_front( $page_id ) {
154
+ // Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache.
155
+ return ! empty( $this->curlang->page_on_front ) && ! doing_action( 'switch_blog' ) ? $this->curlang->page_on_front : $page_id;
156
+ }
157
+
158
+ /**
159
+ * Translates the page for posts option.
160
+ *
161
+ * @since 1.8
162
+ *
163
+ * @param int $page_id ID of the page for posts.
164
+ * @return int
165
+ */
166
+ public function translate_page_for_posts( $page_id ) {
167
+ // Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache.
168
+ return ! empty( $this->curlang->page_for_posts ) && ! doing_action( 'switch_blog' ) ? $this->curlang->page_for_posts : $page_id;
169
  }
170
 
171
  /**
180
  */
181
  public function oembed_request_post_id( $post_id, $url ) {
182
  foreach ( $this->model->get_languages_list() as $lang ) {
183
+ if ( is_string( $lang->home_url ) && trailingslashit( $url ) === trailingslashit( $lang->home_url ) ) {
184
+ return (int) $lang->page_on_front;
185
  }
186
  }
187
+
188
  return $post_id;
189
  }
190
  }
include/switcher.php CHANGED
@@ -27,7 +27,7 @@ class PLL_Switcher {
27
  );
28
 
29
  /**
30
- * @var PLL_Links
31
  */
32
  protected $links;
33
 
27
  );
28
 
29
  /**
30
+ * @var PLL_Links|null
31
  */
32
  protected $links;
33
 
include/translate-option.php CHANGED
@@ -192,7 +192,7 @@ class PLL_Translate_Option {
192
  */
193
  public function pre_update_option( $value, $old_value, $name ) {
194
  // Stores the unfiltered old option value before it is updated in DB.
195
- remove_filter( 'option_' . $name, array( $this, 'translate' ), 10, 2 );
196
  $unfiltered_old_value = get_option( $name );
197
  add_filter( 'option_' . $name, array( $this, 'translate' ), 20, 2 );
198
 
192
  */
193
  public function pre_update_option( $value, $old_value, $name ) {
194
  // Stores the unfiltered old option value before it is updated in DB.
195
+ remove_filter( 'option_' . $name, array( $this, 'translate' ) );
196
  $unfiltered_old_value = get_option( $name );
197
  add_filter( 'option_' . $name, array( $this, 'translate' ), 20, 2 );
198
 
include/translated-object.php CHANGED
@@ -171,8 +171,8 @@ abstract class PLL_Translated_Object {
171
  *
172
  * @since 3.2
173
  *
174
- * @param int $term_id Term ID.
175
- * @return array<int> An associative array of translations with language code as key and translation id as value.
176
  */
177
  public function get_translations_from_term_id( $term_id ) {
178
  $term_id = $this->sanitize_int_id( $term_id );
@@ -216,7 +216,7 @@ abstract class PLL_Translated_Object {
216
  *
217
  * @param int $id Object id ( typically a post_id or term_id ).
218
  * @param int[] $translations An associative array of translations with language code as key and translation id as value.
219
- * @return int[] An associative array with language codes as key and post ids as values.
220
  */
221
  public function save_translations( $id, $translations ) {
222
  $id = $this->sanitize_int_id( $id );
@@ -410,7 +410,7 @@ abstract class PLL_Translated_Object {
410
  *
411
  * @since 1.2
412
  *
413
- * @param PLL_Language|string|string[] $lang PLL_Language object or a comma separated list of language slug or an array of language slugs.
414
  * @return string Where clause.
415
  */
416
  public function where_clause( $lang ) {
@@ -425,16 +425,24 @@ abstract class PLL_Translated_Object {
425
  }
426
 
427
  /*
428
- * $lang is a comma separated list of slugs ( or an array of slugs ).
429
- * This is generally the case is the query is coming from outside with a 'lang' parameter.
430
  */
431
- $slugs = is_array( $lang ) ? $lang : explode( ',', $lang );
432
- $languages = array();
433
- foreach ( $slugs as $slug ) {
434
- $languages[] = absint( $this->model->get_language( $slug )->$tt_id );
 
 
 
 
435
  }
436
 
437
- return ' AND pll_tr.term_taxonomy_id IN ( ' . implode( ',', $languages ) . ' )';
 
 
 
 
438
  }
439
 
440
  /**
@@ -488,11 +496,13 @@ abstract class PLL_Translated_Object {
488
  *
489
  * @since 3.2
490
  *
491
- * @param mixed $id A supposedly numeric ID.
492
- * @return int A positive integer. `0` for non numeric values and negative integers.
 
 
493
  */
494
  public function sanitize_int_id( $id ) {
495
- return is_numeric( $id ) && $id >= 1 ? (int) $id : 0;
496
  }
497
 
498
  /**
@@ -501,8 +511,8 @@ abstract class PLL_Translated_Object {
501
  *
502
  * @since 3.2
503
  *
504
- * @param mixed $ids An associative array of translations with language code as key and translation ID as value.
505
- * @return array<int> An associative array of translations with language code as key and translation ID as value.
506
  */
507
  public function sanitize_int_ids_list( $ids ) {
508
  if ( empty( $ids ) || ! is_array( $ids ) ) {
@@ -526,13 +536,13 @@ abstract class PLL_Translated_Object {
526
  * @since 3.2 Doesn't return `0` ID values.
527
  * @since 3.2 Added parameters `$id` and `$context`.
528
  *
529
- * @param int[] $translations An associative array of translations with language code as key and translation ID as
530
- * value.
531
- * @param int $id Optional. The object ID for which the translations are validated. When provided, the
532
- * process makes sure it is added to the list. Default 0.
533
- * @param string $context Optional. The operation for which the translations are validated. When set to
534
- * 'save', a check is done to verify that the IDs and langs correspond.
535
- * 'display' should be used otherwise. Default 'save'.
536
  * @return int[]
537
  */
538
  protected function validate_translations( $translations, $id = 0, $context = 'save' ) {
171
  *
172
  * @since 3.2
173
  *
174
+ * @param int $term_id Term ID.
175
+ * @return int[] An associative array of translations with language code as key and translation id as value.
176
  */
177
  public function get_translations_from_term_id( $term_id ) {
178
  $term_id = $this->sanitize_int_id( $term_id );
216
  *
217
  * @param int $id Object id ( typically a post_id or term_id ).
218
  * @param int[] $translations An associative array of translations with language code as key and translation id as value.
219
+ * @return int[] An associative array with language codes as key and post ids as values.
220
  */
221
  public function save_translations( $id, $translations ) {
222
  $id = $this->sanitize_int_id( $id );
410
  *
411
  * @since 1.2
412
  *
413
+ * @param PLL_Language|PLL_Language[]|string|string[] $lang PLL_Language object or a comma separated list of language slug or an array of language slugs or objects.
414
  * @return string Where clause.
415
  */
416
  public function where_clause( $lang ) {
425
  }
426
 
427
  /*
428
+ * $lang is an array of objects, an array of slugs, or a comma separated list of slugs.
429
+ * The comma separated list of slugs can happen if the query is coming from outside with a 'lang' parameter.
430
  */
431
+ $languages = is_array( $lang ) ? $lang : explode( ',', $lang );
432
+ $languages_tt_ids = array();
433
+ foreach ( $languages as $language ) {
434
+ $language = $this->model->get_language( $language );
435
+
436
+ if ( ! empty( $language ) ) {
437
+ $languages_tt_ids[] = absint( $language->$tt_id );
438
+ }
439
  }
440
 
441
+ if ( empty( $languages_tt_ids ) ) {
442
+ return '';
443
+ }
444
+
445
+ return ' AND pll_tr.term_taxonomy_id IN ( ' . implode( ',', $languages_tt_ids ) . ' )';
446
  }
447
 
448
  /**
496
  *
497
  * @since 3.2
498
  *
499
+ * @param mixed $id A supposedly numeric ID.
500
+ * @return int A positive integer. `0` for non numeric values and negative integers.
501
+ *
502
+ * @phpstan-return int<0,max>
503
  */
504
  public function sanitize_int_id( $id ) {
505
+ return is_numeric( $id ) && $id >= 1 ? abs( (int) $id ) : 0;
506
  }
507
 
508
  /**
511
  *
512
  * @since 3.2
513
  *
514
+ * @param mixed $ids An associative array of translations with language code as key and translation ID as value.
515
+ * @return int[] An associative array of translations with language code as key and translation ID as value.
516
  */
517
  public function sanitize_int_ids_list( $ids ) {
518
  if ( empty( $ids ) || ! is_array( $ids ) ) {
536
  * @since 3.2 Doesn't return `0` ID values.
537
  * @since 3.2 Added parameters `$id` and `$context`.
538
  *
539
+ * @param int[] $translations An associative array of translations with language code as key and translation ID as
540
+ * value.
541
+ * @param int $id Optional. The object ID for which the translations are validated. When provided, the
542
+ * process makes sure it is added to the list. Default 0.
543
+ * @param string $context Optional. The operation for which the translations are validated. When set to
544
+ * 'save', a check is done to verify that the IDs and langs correspond.
545
+ * 'display' should be used otherwise. Default 'save'.
546
  * @return int[]
547
  */
548
  protected function validate_translations( $translations, $id = 0, $context = 'save' ) {
include/translated-term.php CHANGED
@@ -26,10 +26,7 @@ class PLL_Translated_Term extends PLL_Translated_Object {
26
 
27
  parent::__construct( $model );
28
 
29
- // Filters to prime terms cache
30
  add_filter( 'get_terms', array( $this, '_prime_terms_cache' ), 10, 2 );
31
- add_filter( 'get_object_terms', array( $this, 'wp_get_object_terms' ), 10, 3 );
32
-
33
  add_action( 'clean_term_cache', array( $this, 'clean_term_cache' ) );
34
  }
35
 
@@ -205,23 +202,6 @@ class PLL_Translated_Term extends PLL_Translated_Object {
205
  return $terms;
206
  }
207
 
208
- /**
209
- * When terms are found for posts, add their language and translations to cache.
210
- *
211
- * @since 1.2
212
- *
213
- * @param WP_Term[] $terms Array of terms for the given object or objects.
214
- * @param int[] $object_ids Array of object IDs for which terms were retrieved.
215
- * @param string[] $taxonomies Array of taxonomy names from which terms were retrieved.
216
- * @return WP_Term[] Unmodified $terms.
217
- */
218
- public function wp_get_object_terms( $terms, $object_ids, $taxonomies ) {
219
- if ( ! in_array( $this->tax_translations, $taxonomies ) ) {
220
- $this->_prime_terms_cache( $terms, $taxonomies );
221
- }
222
- return $terms;
223
- }
224
-
225
  /**
226
  * When the term cache is cleaned, cleans the object term cache too.
227
  *
26
 
27
  parent::__construct( $model );
28
 
 
29
  add_filter( 'get_terms', array( $this, '_prime_terms_cache' ), 10, 2 );
 
 
30
  add_action( 'clean_term_cache', array( $this, 'clean_term_cache' ) );
31
  }
32
 
202
  return $terms;
203
  }
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  /**
206
  * When the term cache is cleaned, cleans the object term cache too.
207
  *
include/walker-dropdown.php CHANGED
@@ -14,7 +14,7 @@ class PLL_Walker_Dropdown extends Walker {
14
  *
15
  * @see https://developer.wordpress.org/reference/classes/walker/#properties Walker::$db_fields.
16
  *
17
- * @var array
18
  */
19
  public $db_fields = array( 'parent' => 'parent', 'id' => 'id' );
20
 
14
  *
15
  * @see https://developer.wordpress.org/reference/classes/walker/#properties Walker::$db_fields.
16
  *
17
+ * @var string[]
18
  */
19
  public $db_fields = array( 'parent' => 'parent', 'id' => 'id' );
20
 
include/walker-list.php CHANGED
@@ -14,7 +14,7 @@ class PLL_Walker_List extends Walker {
14
  *
15
  * @see https://developer.wordpress.org/reference/classes/walker/#properties Walker::$db_fields.
16
  *
17
- * @var array
18
  */
19
  public $db_fields = array( 'parent' => 'parent', 'id' => 'id' );
20
 
14
  *
15
  * @see https://developer.wordpress.org/reference/classes/walker/#properties Walker::$db_fields.
16
  *
17
+ * @var string[]
18
  */
19
  public $db_fields = array( 'parent' => 'parent', 'id' => 'id' );
20
 
include/widget-languages.php CHANGED
@@ -38,8 +38,11 @@ class PLL_Widget_Languages extends WP_Widget {
38
  public function widget( $args, $instance ) {
39
  // Sets a unique id for dropdown.
40
  $instance['dropdown'] = empty( $instance['dropdown'] ) ? 0 : $this->id;
 
 
 
41
 
42
- if ( $list = pll_the_languages( array_merge( $instance, array( 'echo' => 0 ) ) ) ) {
43
  $title = empty( $instance['title'] ) ? '' : $instance['title'];
44
 
45
  /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
38
  public function widget( $args, $instance ) {
39
  // Sets a unique id for dropdown.
40
  $instance['dropdown'] = empty( $instance['dropdown'] ) ? 0 : $this->id;
41
+ $instance['echo'] = 0;
42
+ $instance['raw'] = 0;
43
+ $list = pll_the_languages( $instance );
44
 
45
+ if ( $list ) {
46
  $title = empty( $instance['title'] ) ? '' : $instance['title'];
47
 
48
  /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
install/t15s.php CHANGED
@@ -35,14 +35,14 @@ class PLL_T15S {
35
  /**
36
  * Installed translations.
37
  *
38
- * @var array
39
  */
40
  private static $installed_translations;
41
 
42
  /**
43
  * Available languages.
44
  *
45
- * @var array
46
  */
47
  private static $available_languages;
48
 
35
  /**
36
  * Installed translations.
37
  *
38
+ * @var array|null
39
  */
40
  private static $installed_translations;
41
 
42
  /**
43
  * Available languages.
44
  *
45
+ * @var array|null
46
  */
47
  private static $available_languages;
48
 
install/upgrade.php CHANGED
@@ -105,7 +105,10 @@ class PLL_Upgrade {
105
  public function _upgrade() {
106
  foreach ( array( '2.0.8', '2.1', '2.7', '2.8.1' ) as $version ) {
107
  if ( version_compare( $this->options['version'], $version, '<' ) ) {
108
- call_user_func( array( $this, 'upgrade_' . str_replace( '.', '_', $version ) ) );
 
 
 
109
  }
110
  }
111
 
105
  public function _upgrade() {
106
  foreach ( array( '2.0.8', '2.1', '2.7', '2.8.1' ) as $version ) {
107
  if ( version_compare( $this->options['version'], $version, '<' ) ) {
108
+ $method_to_call = array( $this, 'upgrade_' . str_replace( '.', '_', $version ) );
109
+ if ( is_callable( $method_to_call ) ) {
110
+ call_user_func( $method_to_call );
111
+ }
112
  }
113
  }
114
 
integrations/integrations.php CHANGED
@@ -10,11 +10,12 @@
10
  * @since 1.0
11
  * @since 2.8 Renamed from PLL_Plugins_Compat to PLL_Integrations.
12
  */
 
13
  class PLL_Integrations {
14
  /**
15
  * Singleton instance.
16
  *
17
- * @var PLL_Integrations
18
  */
19
  protected static $instance;
20
 
10
  * @since 1.0
11
  * @since 2.8 Renamed from PLL_Plugins_Compat to PLL_Integrations.
12
  */
13
+ #[AllowDynamicProperties]
14
  class PLL_Integrations {
15
  /**
16
  * Singleton instance.
17
  *
18
+ * @var PLL_Integrations|null
19
  */
20
  protected static $instance;
21
 
integrations/wp-importer/wp-import.php CHANGED
@@ -41,7 +41,7 @@ class PLL_WP_Import extends WP_Import {
41
 
42
  // Assign the default language in case the importer created the first language.
43
  if ( empty( PLL()->options['default_lang'] ) ) {
44
- $languages = get_terms( 'language', array( 'hide_empty' => false, 'orderby' => 'term_id' ) );
45
  $default_lang = reset( $languages );
46
  PLL()->options['default_lang'] = $default_lang->slug;
47
  update_option( 'polylang', PLL()->options );
41
 
42
  // Assign the default language in case the importer created the first language.
43
  if ( empty( PLL()->options['default_lang'] ) ) {
44
+ $languages = get_terms( array( 'taxonomy' => 'language', 'hide_empty' => false, 'orderby' => 'term_id' ) );
45
  $default_lang = reset( $languages );
46
  PLL()->options['default_lang'] = $default_lang->slug;
47
  update_option( 'polylang', PLL()->options );
integrations/wp-offload-media/as3cf.php CHANGED
@@ -15,7 +15,7 @@ class PLL_AS3CF {
15
  *
16
  * @var bool[]
17
  */
18
- private $is_media_translated;
19
 
20
  /**
21
  * Initializes filters and actions.
15
  *
16
  * @var bool[]
17
  */
18
+ private $is_media_translated = array();
19
 
20
  /**
21
  * Initializes filters and actions.
integrations/wpseo/wpseo.php CHANGED
@@ -46,6 +46,7 @@ class PLL_WPSEO {
46
  } else {
47
  add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 10, 4 );
48
  add_filter( 'pll_translate_post_meta', array( $this, 'translate_post_meta' ), 10, 3 );
 
49
 
50
  // Yoast SEO adds the columns hooks only for the 'inline-save' action. We need them for 'pll_update_post_rows' too.
51
  if ( wp_doing_ajax() && isset( $_POST['action'] ) && 'pll_update_post_rows' === $_POST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
@@ -127,40 +128,43 @@ class PLL_WPSEO {
127
  }
128
 
129
  /**
130
- * Modifies the sql request for posts sitemaps
131
- * Only when using multiple domains or subdomains or if some languages are not active
132
  *
133
  * @since 1.6.4
134
  *
135
- * @param string $sql JOIN clause
136
- * @param string $post_type
137
  * @return string
138
  */
139
  public function wpseo_posts_join( $sql, $post_type ) {
140
- return pll_is_translated_post_type( $post_type ) && ( PLL()->options['force_lang'] > 1 || $this->wpseo_get_active_languages() ) ? $sql . PLL()->model->post->join_clause() : $sql;
141
  }
142
 
143
  /**
144
- * Modifies the sql request for posts sitemaps
145
- * Only when using multiple domains or subdomains or if some languages are not active
146
  *
147
  * @since 1.6.4
148
  *
149
- * @param string $sql WHERE clause
150
- * @param string $post_type
151
  * @return string
152
  */
153
  public function wpseo_posts_where( $sql, $post_type ) {
154
- if ( pll_is_translated_post_type( $post_type ) ) {
155
- if ( PLL()->options['force_lang'] > 1 ) {
156
- return $sql . PLL()->model->post->where_clause( PLL()->curlang );
157
- }
158
 
159
- if ( $languages = $this->wpseo_get_active_languages() ) {
160
- return $sql . PLL()->model->post->where_clause( $languages );
161
- }
 
 
 
 
 
162
  }
163
- return $sql;
 
164
  }
165
 
166
  /**
@@ -425,23 +429,16 @@ class PLL_WPSEO {
425
  public function copy_post_metas( $keys, $sync, $from, $to ) {
426
  if ( ! $sync ) {
427
  // Text requiring translation.
428
- $keys[] = '_yoast_wpseo_title';
429
- $keys[] = '_yoast_wpseo_metadesc';
430
- $keys[] = '_yoast_wpseo_bctitle';
431
- $keys[] = '_yoast_wpseo_focuskw';
432
- $keys[] = '_yoast_wpseo_opengraph-title';
433
- $keys[] = '_yoast_wpseo_opengraph-description';
434
- $keys[] = '_yoast_wpseo_twitter-title';
435
- $keys[] = '_yoast_wpseo_twitter-description';
436
 
437
  // Copy the image urls.
438
  $keys[] = '_yoast_wpseo_opengraph-image';
439
  $keys[] = '_yoast_wpseo_twitter-image';
440
- }
441
 
442
- $keys[] = '_yoast_wpseo_meta-robots-noindex';
443
- $keys[] = '_yoast_wpseo_meta-robots-nofollow';
444
- $keys[] = '_yoast_wpseo_meta-robots-adv';
 
445
 
446
  $taxonomies = get_taxonomies(
447
  array(
@@ -483,4 +480,36 @@ class PLL_WPSEO {
483
 
484
  return pll_get_term( $value, $lang );
485
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  }
46
  } else {
47
  add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 10, 4 );
48
  add_filter( 'pll_translate_post_meta', array( $this, 'translate_post_meta' ), 10, 3 );
49
+ add_filter( 'pll_post_metas_to_export', array( $this, 'export_post_metas' ) );
50
 
51
  // Yoast SEO adds the columns hooks only for the 'inline-save' action. We need them for 'pll_update_post_rows' too.
52
  if ( wp_doing_ajax() && isset( $_POST['action'] ) && 'pll_update_post_rows' === $_POST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
128
  }
129
 
130
  /**
131
+ * Modifies the sql request for posts sitemaps.
 
132
  *
133
  * @since 1.6.4
134
  *
135
+ * @param string $sql JOIN clause.
136
+ * @param string $post_type Post type.
137
  * @return string
138
  */
139
  public function wpseo_posts_join( $sql, $post_type ) {
140
+ return pll_is_translated_post_type( $post_type ) ? $sql . PLL()->model->post->join_clause() : $sql;
141
  }
142
 
143
  /**
144
+ * Modifies the sql request for posts sitemaps.
 
145
  *
146
  * @since 1.6.4
147
  *
148
+ * @param string $sql WHERE clause.
149
+ * @param string $post_type Post type.
150
  * @return string
151
  */
152
  public function wpseo_posts_where( $sql, $post_type ) {
153
+ if ( ! pll_is_translated_post_type( $post_type ) ) {
154
+ return $sql;
155
+ }
 
156
 
157
+ if ( PLL()->options['force_lang'] > 1 && PLL()->curlang instanceof PLL_Language ) {
158
+ return $sql . PLL()->model->post->where_clause( PLL()->curlang );
159
+ }
160
+
161
+ $languages = $this->wpseo_get_active_languages();
162
+
163
+ if ( empty( $languages ) ) { // Empty when all languages are active.
164
+ $languages = pll_languages_list();
165
  }
166
+
167
+ return $sql . PLL()->model->post->where_clause( $languages );
168
  }
169
 
170
  /**
429
  public function copy_post_metas( $keys, $sync, $from, $to ) {
430
  if ( ! $sync ) {
431
  // Text requiring translation.
432
+ $keys = array_merge( $keys, $this->get_translatable_meta_keys() );
 
 
 
 
 
 
 
433
 
434
  // Copy the image urls.
435
  $keys[] = '_yoast_wpseo_opengraph-image';
436
  $keys[] = '_yoast_wpseo_twitter-image';
 
437
 
438
+ $keys[] = '_yoast_wpseo_meta-robots-noindex';
439
+ $keys[] = '_yoast_wpseo_meta-robots-nofollow';
440
+ $keys[] = '_yoast_wpseo_meta-robots-adv';
441
+ }
442
 
443
  $taxonomies = get_taxonomies(
444
  array(
480
 
481
  return pll_get_term( $value, $lang );
482
  }
483
+
484
+ /**
485
+ * Adds the yoast translatable metas to export.
486
+ *
487
+ * @param array $metas An array of post metas (keyed with meta keys) to export.
488
+ * @return array The modified array of post metas to export.
489
+ */
490
+ public function export_post_metas( $metas ) {
491
+ $metas_to_export = array_fill_keys( $this->get_translatable_meta_keys(), 1 );
492
+
493
+ return array_merge( $metas, $metas_to_export );
494
+ }
495
+
496
+ /**
497
+ * Returns the meta keys with translatable text.
498
+ *
499
+ * @since 3.3
500
+ *
501
+ * @return string[]
502
+ */
503
+ protected function get_translatable_meta_keys() {
504
+ return array(
505
+ '_yoast_wpseo_title',
506
+ '_yoast_wpseo_metadesc',
507
+ '_yoast_wpseo_bctitle',
508
+ '_yoast_wpseo_focuskw',
509
+ '_yoast_wpseo_opengraph-title',
510
+ '_yoast_wpseo_opengraph-description',
511
+ '_yoast_wpseo_twitter-title',
512
+ '_yoast_wpseo_twitter-description',
513
+ );
514
+ }
515
  }
js/build/block-editor.js CHANGED
@@ -102,6 +102,49 @@ const initializeLanguageOldValue = () => {
102
  languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() );
103
  };
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  ;// CONCATENATED MODULE: ./js/src/block-editor.js
106
  /**
107
  * @package Polylang
@@ -109,6 +152,8 @@ const initializeLanguageOldValue = () => {
109
 
110
 
111
 
 
 
112
  /**
113
  * Filter REST API requests to add the language in the request
114
  *
@@ -131,14 +176,20 @@ wp.apiFetch.use(
131
  );
132
 
133
  /**
134
- * Get the language from the HTML form
135
  *
136
  * @since 2.5
137
  *
138
  * @return {Element.value}
139
  */
140
  function getCurrentLanguage() {
141
- return document.querySelector( '[name=post_lang_choice]' ).value;
 
 
 
 
 
 
142
  }
143
 
144
  /**
@@ -197,7 +248,7 @@ jQuery(
197
 
198
  dialogResult.then(
199
  () => {
200
- var data = { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
201
  action: 'post_lang_choice',
202
  lang: selectedOption.value,
203
  post_type: $( '#post_type' ).val(),
@@ -205,28 +256,12 @@ jQuery(
205
  _pll_nonce: $( '#_pll_nonce' ).val()
206
  }
207
 
 
 
208
  $.post(
209
  ajaxurl,
210
  data,
211
- function( response ) {
212
- // Target a non existing WP HTML id to avoid a conflict with WP ajax requests.
213
- var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' );
214
- $.each(
215
- res.responses,
216
- function() {
217
- switch ( this.what ) {
218
- case 'translations': // Translations fields
219
- // Data is built and come from server side and is well escaped when necessary
220
- $( '.translations' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
221
- init_translations();
222
- break;
223
- case 'flag': // Flag in front of the select dropdown
224
- // Data is built and come from server side and is well escaped when necessary
225
- $( '.pll-select-flag' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
226
- break;
227
- }
228
- }
229
- );
230
  blockEditorSavePostAndReloadPage();
231
  }
232
  );
@@ -310,47 +345,7 @@ jQuery(
310
  }
311
  );
312
 
313
- // Translations autocomplete input box
314
- function init_translations() {
315
- $( '.tr_lang' ).each(
316
- function(){
317
- var tr_lang = $( this ).attr( 'id' ).substring( 8 );
318
- var td = $( this ).parent().parent().siblings( '.pll-edit-column' );
319
-
320
- $( this ).autocomplete(
321
- {
322
- minLength: 0,
323
-
324
- source: ajaxurl + '?action=pll_posts_not_translated' +
325
- '&post_language=' + $( '.post_lang_choice' ).val() +
326
- '&translation_language=' + tr_lang +
327
- '&post_type=' + $( '#post_type' ).val() +
328
- '&_pll_nonce=' + $( '#_pll_nonce' ).val(),
329
-
330
- select: function( event, ui ) {
331
- $( '#htr_lang_' + tr_lang ).val( ui.item.id );
332
- // ui.item.link is built and come from server side and is well escaped when necessary
333
- td.html( ui.item.link ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
334
- },
335
- }
336
- );
337
-
338
- // When the input box is emptied
339
- $( this ).on(
340
- 'blur',
341
- function() {
342
- if ( ! $( this ).val() ) {
343
- $( '#htr_lang_' + tr_lang ).val( 0 );
344
- // Value is retrieved from HTML already generated server side
345
- td.html( td.siblings( '.hidden' ).children().clone() ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
346
- }
347
- }
348
- );
349
- }
350
- );
351
- }
352
-
353
- init_translations();
354
  }
355
  );
356
 
102
  languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() );
103
  };
104
 
105
+ ;// CONCATENATED MODULE: ./js/src/lib/metabox-autocomplete.js
106
+ /**
107
+ * @package Polylang
108
+ */
109
+
110
+ // Translations autocomplete input box.
111
+ function initMetaboxAutoComplete() {
112
+ jQuery('.tr_lang').each(
113
+ function () {
114
+ var tr_lang = jQuery(this).attr('id').substring(8);
115
+ var td = jQuery(this).parent().parent().siblings('.pll-edit-column');
116
+
117
+ jQuery(this).autocomplete(
118
+ {
119
+ minLength: 0,
120
+ source: ajaxurl + '?action=pll_posts_not_translated' +
121
+ '&post_language=' + jQuery('.post_lang_choice').val() +
122
+ '&translation_language=' + tr_lang +
123
+ '&post_type=' + jQuery('#post_type').val() +
124
+ '&_pll_nonce=' + jQuery('#_pll_nonce').val(),
125
+ select: function (event, ui) {
126
+ jQuery('#htr_lang_' + tr_lang).val(ui.item.id);
127
+ // ui.item.link is built and come from server side and is well escaped when necessary
128
+ td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
129
+ },
130
+ }
131
+ );
132
+
133
+ // when the input box is emptied
134
+ jQuery(this).on(
135
+ 'blur',
136
+ function () {
137
+ if ( ! jQuery(this).val() ) {
138
+ jQuery('#htr_lang_' + tr_lang).val(0);
139
+ // Value is retrieved from HTML already generated server side
140
+ td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
141
+ }
142
+ }
143
+ );
144
+ }
145
+ );
146
+ }
147
+
148
  ;// CONCATENATED MODULE: ./js/src/block-editor.js
149
  /**
150
  * @package Polylang
152
 
153
 
154
 
155
+
156
+
157
  /**
158
  * Filter REST API requests to add the language in the request
159
  *
176
  );
177
 
178
  /**
179
+ * Gets the language of the currently edited post, fallback to default language if none is found.
180
  *
181
  * @since 2.5
182
  *
183
  * @return {Element.value}
184
  */
185
  function getCurrentLanguage() {
186
+ const lang = document.querySelector( '[name=post_lang_choice]' );
187
+
188
+ if ( null === lang ) {
189
+ return pllDefaultLanguage;
190
+ }
191
+
192
+ return lang.value;
193
  }
194
 
195
  /**
248
 
249
  dialogResult.then(
250
  () => {
251
+ let data = { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
252
  action: 'post_lang_choice',
253
  lang: selectedOption.value,
254
  post_type: $( '#post_type' ).val(),
256
  _pll_nonce: $( '#_pll_nonce' ).val()
257
  }
258
 
259
+ // Update post language in database as soon as possible.
260
+ // Because, in addition of the block editor save process, the legacy metabox uses a post.php process to update the language and is too late compared to the page reload.
261
  $.post(
262
  ajaxurl,
263
  data,
264
+ function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  blockEditorSavePostAndReloadPage();
266
  }
267
  );
345
  }
346
  );
347
 
348
+ initMetaboxAutoComplete();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  }
350
  );
351
 
js/build/block-editor.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";var __webpack_exports__={};const languagesList=jQuery(".post_lang_choice"),initializeConfimationModal=()=>{const{__:t}=wp.i18n,a=jQuery("<div/>",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(a);const e=new Promise(((e,n)=>{const l=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),e();break;case"no":languagesList.val(languagesList.data("old-value")),n("Cancel")}a.dialog("close")},i={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,a){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,a){l("no")},buttons:[{text:t("OK","polylang"),click:function(t){l("yes")}},{text:t("Cancel","polylang"),click:function(t){l("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(i,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(i,{dialogClass:"pll-confirmation-modal"}),a.dialog(i)}));return{dialogContainer:a,dialogResult:e}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function getCurrentLanguage(){return document.querySelector("[name=post_lang_choice]").value}wp.apiFetch.use((function(t,a){return void 0===t.url&&(void 0===t.data||null===t.data?t.path+=(t.path.indexOf("?")>=0?"&lang=":"?lang=")+getCurrentLanguage():t.data.lang=getCurrentLanguage()),a(t)})),jQuery((function(t){function a(){t(".tr_lang").each((function(){var a=t(this).attr("id").substring(8),e=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,l){t("#htr_lang_"+a).val(l.item.id),e.html(l.item.link)}}),t(this).on("blur",(function(){t(this).val()||(t("#htr_lang_"+a).val(0),e.html(e.siblings(".hidden").children().clone()))}))}))}initializeLanguageOldValue(),t(".post_lang_choice").on("change",(function(e){const n=wp.data.select,l=wp.data.dispatch,i=wp.data.subscribe,o=function(){const t=wp.data.select("core/editor"),a=t.getEditedPostAttribute("title").trim(),e=t.getEditedPostAttribute("content").trim(),n=t.getEditedPostAttribute("excerpt").trim();return!a&&!e&&!n}(),s=initializeConfimationModal(),{dialogContainer:c}=s;let{dialogResult:r}=s;const u=e.target;var g;location.pathname.match(/post-new.php/gi)&&o&&(g=u.value,-1!=location.search.indexOf("new_lang")?window.location.search=window.location.search.replace(/(?:new_lang=[^&]*)(&)?(.*)/,"new_lang="+g+"$1$2"):window.location.search=window.location.search+(-1!=window.location.search.indexOf("?")?"&":"?")+"new_lang="+g),t(this).data("old-value")===u.value||o?(initializeLanguageOldValue(),r=Promise.resolve()):c.dialog("open"),r.then((()=>{var e={action:"post_lang_choice",lang:u.value,post_type:t("#post_type").val(),post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,e,(function(e){var o=wpAjax.parseAjaxResponse(e,"pll-ajax-response");t.each(o.responses,(function(){switch(this.what){case"translations":t(".translations").html(this.data),a();break;case"flag":t(".pll-select-flag").html(this.data)}})),function(){let t=null;const a=new Promise((function(a,e){t=i((function(){const t=n("core/editor").didPostSaveRequestSucceed(),l=n("core/editor").didPostSaveRequestFail();(t||l)&&(l?e():a())}))}));l("core/editor").savePost(),a.then((function(){window.location.reload()}),(function(){t()})).catch((function(){t()}))}()}))}),(()=>{}))})),a()}));
1
+ "use strict";var __webpack_exports__={};const languagesList=jQuery(".post_lang_choice"),initializeConfimationModal=()=>{const{__:t}=wp.i18n,e=jQuery("<div/>",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(e);const a=new Promise(((a,n)=>{const l=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),a();break;case"no":languagesList.val(languagesList.data("old-value")),n("Cancel")}e.dialog("close")},i={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,e){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,e){l("no")},buttons:[{text:t("OK","polylang"),click:function(t){l("yes")}},{text:t("Cancel","polylang"),click:function(t){l("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(i,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(i,{dialogClass:"pll-confirmation-modal"}),e.dialog(i)}));return{dialogContainer:e,dialogResult:a}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function initMetaboxAutoComplete(){jQuery(".tr_lang").each((function(){var t=jQuery(this).attr("id").substring(8),e=jQuery(this).parent().parent().siblings(".pll-edit-column");jQuery(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+jQuery(".post_lang_choice").val()+"&translation_language="+t+"&post_type="+jQuery("#post_type").val()+"&_pll_nonce="+jQuery("#_pll_nonce").val(),select:function(a,n){jQuery("#htr_lang_"+t).val(n.item.id),e.html(n.item.link)}}),jQuery(this).on("blur",(function(){jQuery(this).val()||(jQuery("#htr_lang_"+t).val(0),e.html(e.siblings(".hidden").children().clone()))}))}))}function getCurrentLanguage(){const t=document.querySelector("[name=post_lang_choice]");return null===t?pllDefaultLanguage:t.value}wp.apiFetch.use((function(t,e){return void 0===t.url&&(void 0===t.data||null===t.data?t.path+=(t.path.indexOf("?")>=0?"&lang=":"?lang=")+getCurrentLanguage():t.data.lang=getCurrentLanguage()),e(t)})),jQuery((function(t){initializeLanguageOldValue(),t(".post_lang_choice").on("change",(function(e){const a=wp.data.select,n=wp.data.dispatch,l=wp.data.subscribe,i=function(){const t=wp.data.select("core/editor"),e=t.getEditedPostAttribute("title").trim(),a=t.getEditedPostAttribute("content").trim(),n=t.getEditedPostAttribute("excerpt").trim();return!e&&!a&&!n}(),o=initializeConfimationModal(),{dialogContainer:s}=o;let{dialogResult:c}=o;const u=e.target;var r;location.pathname.match(/post-new.php/gi)&&i&&(r=u.value,-1!=location.search.indexOf("new_lang")?window.location.search=window.location.search.replace(/(?:new_lang=[^&]*)(&)?(.*)/,"new_lang="+r+"$1$2"):window.location.search=window.location.search+(-1!=window.location.search.indexOf("?")?"&":"?")+"new_lang="+r),t(this).data("old-value")===u.value||i?(initializeLanguageOldValue(),c=Promise.resolve()):s.dialog("open"),c.then((()=>{let e={action:"post_lang_choice",lang:u.value,post_type:t("#post_type").val(),post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,e,(function(){!function(){let t=null;const e=new Promise((function(e,n){t=l((function(){const t=a("core/editor").didPostSaveRequestSucceed(),l=a("core/editor").didPostSaveRequestFail();(t||l)&&(l?n():e())}))}));n("core/editor").savePost(),e.then((function(){window.location.reload()}),(function(){t()})).catch((function(){t()}))}()}))}),(()=>{}))})),initMetaboxAutoComplete()}));
js/build/classic-editor.js CHANGED
@@ -102,6 +102,49 @@ const initializeLanguageOldValue = () => {
102
  languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() );
103
  };
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  ;// CONCATENATED MODULE: ./js/src/classic-editor.js
106
  /**
107
  * @package Polylang
@@ -109,6 +152,8 @@ const initializeLanguageOldValue = () => {
109
 
110
 
111
 
 
 
112
  // tag suggest in metabox
113
  jQuery(
114
  function( $ ) {
@@ -247,7 +292,7 @@ jQuery(
247
  case 'translations': // translations fields
248
  // Data is built and come from server side and is well escaped when necessary
249
  $( '.translations' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
250
- init_translations();
251
  break;
252
  case 'taxonomy': // categories metabox for posts
253
  var tax = this.data;
@@ -314,45 +359,7 @@ jQuery(
314
  }
315
  );
316
 
317
- // translations autocomplete input box
318
- function init_translations() {
319
- $( '.tr_lang' ).each(
320
- function(){
321
- var tr_lang = $( this ).attr( 'id' ).substring( 8 );
322
- var td = $( this ).parent().parent().siblings( '.pll-edit-column' );
323
-
324
- $( this ).autocomplete(
325
- {
326
- minLength: 0,
327
- source: ajaxurl + '?action=pll_posts_not_translated' +
328
- '&post_language=' + $( '.post_lang_choice' ).val() +
329
- '&translation_language=' + tr_lang +
330
- '&post_type=' + $( '#post_type' ).val() +
331
- '&_pll_nonce=' + $( '#_pll_nonce' ).val(),
332
- select: function( event, ui ) {
333
- $( '#htr_lang_' + tr_lang ).val( ui.item.id );
334
- // ui.item.link is built and come from server side and is well escaped when necessary
335
- td.html( ui.item.link ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
336
- },
337
- }
338
- );
339
-
340
- // when the input box is emptied
341
- $( this ).on(
342
- 'blur',
343
- function() {
344
- if ( ! $( this ).val() ) {
345
- $( '#htr_lang_' + tr_lang ).val( 0 );
346
- // Value is retrieved from HTML already generated server side
347
- td.html( td.siblings( '.hidden' ).children().clone() ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
348
- }
349
- }
350
- );
351
- }
352
- );
353
- }
354
-
355
- init_translations();
356
  }
357
  );
358
 
102
  languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() );
103
  };
104
 
105
+ ;// CONCATENATED MODULE: ./js/src/lib/metabox-autocomplete.js
106
+ /**
107
+ * @package Polylang
108
+ */
109
+
110
+ // Translations autocomplete input box.
111
+ function initMetaboxAutoComplete() {
112
+ jQuery('.tr_lang').each(
113
+ function () {
114
+ var tr_lang = jQuery(this).attr('id').substring(8);
115
+ var td = jQuery(this).parent().parent().siblings('.pll-edit-column');
116
+
117
+ jQuery(this).autocomplete(
118
+ {
119
+ minLength: 0,
120
+ source: ajaxurl + '?action=pll_posts_not_translated' +
121
+ '&post_language=' + jQuery('.post_lang_choice').val() +
122
+ '&translation_language=' + tr_lang +
123
+ '&post_type=' + jQuery('#post_type').val() +
124
+ '&_pll_nonce=' + jQuery('#_pll_nonce').val(),
125
+ select: function (event, ui) {
126
+ jQuery('#htr_lang_' + tr_lang).val(ui.item.id);
127
+ // ui.item.link is built and come from server side and is well escaped when necessary
128
+ td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
129
+ },
130
+ }
131
+ );
132
+
133
+ // when the input box is emptied
134
+ jQuery(this).on(
135
+ 'blur',
136
+ function () {
137
+ if ( ! jQuery(this).val() ) {
138
+ jQuery('#htr_lang_' + tr_lang).val(0);
139
+ // Value is retrieved from HTML already generated server side
140
+ td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
141
+ }
142
+ }
143
+ );
144
+ }
145
+ );
146
+ }
147
+
148
  ;// CONCATENATED MODULE: ./js/src/classic-editor.js
149
  /**
150
  * @package Polylang
152
 
153
 
154
 
155
+
156
+
157
  // tag suggest in metabox
158
  jQuery(
159
  function( $ ) {
292
  case 'translations': // translations fields
293
  // Data is built and come from server side and is well escaped when necessary
294
  $( '.translations' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
295
+ initMetaboxAutoComplete();
296
  break;
297
  case 'taxonomy': // categories metabox for posts
298
  var tax = this.data;
359
  }
360
  );
361
 
362
+ initMetaboxAutoComplete();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  }
364
  );
365
 
js/build/classic-editor.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";var __webpack_exports__={};const languagesList=jQuery(".post_lang_choice"),initializeConfimationModal=()=>{const{__:t}=wp.i18n,a=jQuery("<div/>",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(a);const e=new Promise(((e,l)=>{const n=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),e();break;case"no":languagesList.val(languagesList.data("old-value")),l("Cancel")}a.dialog("close")},i={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,a){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,a){n("no")},buttons:[{text:t("OK","polylang"),click:function(t){n("yes")}},{text:t("Cancel","polylang"),click:function(t){n("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(i,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(i,{dialogClass:"pll-confirmation-modal"}),a.dialog(i)}));return{dialogContainer:a,dialogResult:e}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};jQuery((function(t){t.ajaxPrefilter((function(a,e,l){var n=t(".post_lang_choice").val();"string"==typeof a.data&&-1!==a.url.indexOf("action=ajax-tag-search")&&n&&(a.data="lang="+n+"&"+a.data)}))})),jQuery((function(t){tagBox.get=function(a){var e=a.substr(a.indexOf("-")+1),l={action:"get-tagcloud",lang:t(".post_lang_choice").val(),tax:e};t.post(ajaxurl,l,(function(l,n){0!=l&&"success"==n||(l=wpAjax.broken),l=t("<div />").addClass("the-tagcloud").attr("id","tagcloud-"+e).html(l),t("a",l).on("click",(function(){return tagBox.flushTags(t(this).closest(".inside").children(".tagsdiv"),this),!1}));var i=t("#tagcloud-"+e).css("display");i?(t("#tagcloud-"+e).replaceWith(l),t("#tagcloud-"+e).css("display",i)):t("#"+a).after(l)}))}})),jQuery((function(t){var a=new Array;function e(){t(".tr_lang").each((function(){var a=t(this).attr("id").substring(8),e=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(l,n){t("#htr_lang_"+a).val(n.item.id),e.html(n.item.link)}}),t(this).on("blur",(function(){t(this).val()||(t("#htr_lang_"+a).val(0),e.html(e.siblings(".hidden").children().clone()))}))}))}t(".categorydiv").each((function(){var e,l;(e=t(this).attr("id").split("-")).shift(),l=e.join("-"),a.push(l),t("#"+l+"-add-submit").before(t("<input />").attr("type","hidden").attr("id",l+"-lang").attr("name","term_lang_choice").attr("value",t(".post_lang_choice").val()))})),initializeLanguageOldValue(),t(".post_lang_choice").on("change",(function(l){const n=initializeConfimationModal(),{dialogContainer:i}=n;let{dialogResult:s}=n;const o=l.target;t(this).data("old-value")===o.value||function(){const a=t("input#title").val(),e=t("textarea#content").val(),l=t("textarea#excerpt").val();return!a&&!e&&!l}()?s=Promise.resolve():i.dialog("open"),s.then((()=>{var l=o.options[o.options.selectedIndex].lang,n=t('.pll-translation-column > span[lang="'+l+'"]').attr("dir"),i={action:"post_lang_choice",lang:o.value,post_type:t("#post_type").val(),taxonomies:a,post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,i,(function(a){var i=wpAjax.parseAjaxResponse(a,"pll-ajax-response");t.each(i.responses,(function(){switch(this.what){case"translations":t(".translations").html(this.data),e();break;case"taxonomy":var a=this.data;t("#"+a+"checklist").html(this.supplemental.all),t("#"+a+"checklist-pop").html(this.supplemental.populars),t("#new"+a+"_parent").replaceWith(this.supplemental.dropdown),t("#"+a+"-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 l=t("#edit-slug-box");"-1"!=this.data&&l.children().length&&l.html(this.data)}})),initializeLanguageOldValue(),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-"+n),t("#content_ifr").contents().find("html").attr("lang",l).attr("dir",n),t("#content_ifr").contents().find("body").attr("dir",n),pll.media.resetAllAttachmentsCollections()}))}),(()=>{}))})),e()}));var pll=window.pll||{};_.extend(pll,{media:{}});var media=_.extend(pll.media,{attachmentsCollections:[],query:function(t){var a=pll.media.query.delegate(t);return pll.media.attachmentsCollections.push(a),a},resetAllAttachmentsCollections:function(){this.attachmentsCollections.forEach((function(t){t.reset(),t.mirroring&&(t.mirroring._hasMore=!0,t.mirroring.reset())}))}});"undefined"!=typeof wp&&void 0!==wp.media&&(media.query=_.extend(media.query,{delegate:wp.media.query}),wp.media.query=media.query);
1
+ "use strict";var __webpack_exports__={};const languagesList=jQuery(".post_lang_choice"),initializeConfimationModal=()=>{const{__:t}=wp.i18n,a=jQuery("<div/>",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(a);const e=new Promise(((e,l)=>{const n=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),e();break;case"no":languagesList.val(languagesList.data("old-value")),l("Cancel")}a.dialog("close")},i={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,a){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,a){n("no")},buttons:[{text:t("OK","polylang"),click:function(t){n("yes")}},{text:t("Cancel","polylang"),click:function(t){n("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(i,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(i,{dialogClass:"pll-confirmation-modal"}),a.dialog(i)}));return{dialogContainer:a,dialogResult:e}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function initMetaboxAutoComplete(){jQuery(".tr_lang").each((function(){var t=jQuery(this).attr("id").substring(8),a=jQuery(this).parent().parent().siblings(".pll-edit-column");jQuery(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+jQuery(".post_lang_choice").val()+"&translation_language="+t+"&post_type="+jQuery("#post_type").val()+"&_pll_nonce="+jQuery("#_pll_nonce").val(),select:function(e,l){jQuery("#htr_lang_"+t).val(l.item.id),a.html(l.item.link)}}),jQuery(this).on("blur",(function(){jQuery(this).val()||(jQuery("#htr_lang_"+t).val(0),a.html(a.siblings(".hidden").children().clone()))}))}))}jQuery((function(t){t.ajaxPrefilter((function(a,e,l){var n=t(".post_lang_choice").val();"string"==typeof a.data&&-1!==a.url.indexOf("action=ajax-tag-search")&&n&&(a.data="lang="+n+"&"+a.data)}))})),jQuery((function(t){tagBox.get=function(a){var e=a.substr(a.indexOf("-")+1),l={action:"get-tagcloud",lang:t(".post_lang_choice").val(),tax:e};t.post(ajaxurl,l,(function(l,n){0!=l&&"success"==n||(l=wpAjax.broken),l=t("<div />").addClass("the-tagcloud").attr("id","tagcloud-"+e).html(l),t("a",l).on("click",(function(){return tagBox.flushTags(t(this).closest(".inside").children(".tagsdiv"),this),!1}));var i=t("#tagcloud-"+e).css("display");i?(t("#tagcloud-"+e).replaceWith(l),t("#tagcloud-"+e).css("display",i)):t("#"+a).after(l)}))}})),jQuery((function(t){var a=new Array;t(".categorydiv").each((function(){var e,l;(e=t(this).attr("id").split("-")).shift(),l=e.join("-"),a.push(l),t("#"+l+"-add-submit").before(t("<input />").attr("type","hidden").attr("id",l+"-lang").attr("name","term_lang_choice").attr("value",t(".post_lang_choice").val()))})),initializeLanguageOldValue(),t(".post_lang_choice").on("change",(function(e){const l=initializeConfimationModal(),{dialogContainer:n}=l;let{dialogResult:i}=l;const o=e.target;t(this).data("old-value")===o.value||function(){const a=t("input#title").val(),e=t("textarea#content").val(),l=t("textarea#excerpt").val();return!a&&!e&&!l}()?i=Promise.resolve():n.dialog("open"),i.then((()=>{var e=o.options[o.options.selectedIndex].lang,l=t('.pll-translation-column > span[lang="'+e+'"]').attr("dir"),n={action:"post_lang_choice",lang:o.value,post_type:t("#post_type").val(),taxonomies:a,post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,n,(function(a){var n=wpAjax.parseAjaxResponse(a,"pll-ajax-response");t.each(n.responses,(function(){switch(this.what){case"translations":t(".translations").html(this.data),initMetaboxAutoComplete();break;case"taxonomy":var a=this.data;t("#"+a+"checklist").html(this.supplemental.all),t("#"+a+"checklist-pop").html(this.supplemental.populars),t("#new"+a+"_parent").replaceWith(this.supplemental.dropdown),t("#"+a+"-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 e=t("#edit-slug-box");"-1"!=this.data&&e.children().length&&e.html(this.data)}})),initializeLanguageOldValue(),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-"+l),t("#content_ifr").contents().find("html").attr("lang",e).attr("dir",l),t("#content_ifr").contents().find("body").attr("dir",l),pll.media.resetAllAttachmentsCollections()}))}),(()=>{}))})),initMetaboxAutoComplete()}));var pll=window.pll||{};_.extend(pll,{media:{}});var media=_.extend(pll.media,{attachmentsCollections:[],query:function(t){var a=pll.media.query.delegate(t);return pll.media.attachmentsCollections.push(a),a},resetAllAttachmentsCollections:function(){this.attachmentsCollections.forEach((function(t){t.reset(),t.mirroring&&(t.mirroring._hasMore=!0,t.mirroring.reset())}))}});"undefined"!=typeof wp&&void 0!==wp.media&&(media.query=_.extend(media.query,{delegate:wp.media.query}),wp.media.query=media.query);
js/build/post.js CHANGED
@@ -23,81 +23,84 @@ jQuery(
23
  */
24
  jQuery(
25
  function( $ ) {
26
- $( document ).on(
27
- 'DOMNodeInserted',
28
- function( e ) {
29
- var t = $( e.target );
30
-
31
- // WP inserts the quick edit from
32
- if ( 'inline-edit' == t.attr( 'id' ) ) {
33
- var post_id = t.prev().attr( 'id' ).replace( "post-", "" );
34
 
35
  if ( post_id > 0 ) {
36
- // language dropdown
37
- var select = t.find( ':input[name="inline_lang_choice"]' );
38
- var lang = $( '#lang_' + post_id ).html();
39
- select.val( lang ); // populates the dropdown
40
 
41
- filter_terms( lang ); // initial filter for category checklist
42
- filter_pages( lang ); // initial filter for parent dropdown
43
 
44
- // modify category checklist an parent dropdown on language change
45
- select.on(
46
  'change',
47
- function() {
48
- filter_terms( $( this ).val() );
49
- filter_pages( $( this ).val() );
 
50
  }
51
- );
 
52
  }
53
- }
54
-
55
- /**
56
- * Filters the category checklist.
57
- */
58
- function filter_terms( lang ) {
59
- if ( "undefined" != typeof( pll_term_languages ) ) {
60
- $.each(
61
- pll_term_languages,
62
- function( lg, term_tax ) {
63
- $.each(
64
- term_tax,
65
- function( tax, terms ) {
66
- $.each(
67
- terms,
68
- function( i ) {
69
- id = '#' + tax + '-' + pll_term_languages[ lg ][ tax ][ i ];
70
- lang == lg ? $( id ).show() : $( id ).hide();
71
- }
72
- );
73
- }
74
- );
75
- }
76
- );
77
  }
78
- }
79
 
80
- /**
81
- * Filters the parent page dropdown list.
82
- */
83
- function filter_pages( lang ) {
84
- if ( "undefined" != typeof( pll_page_languages ) ) {
85
- $.each(
86
- pll_page_languages,
87
- function( lg, pages ) {
88
- $.each(
89
- pages,
90
- function( i ) {
91
- v = $( '#post_parent option[value="' + pll_page_languages[ lg ][ i ] + '"]' );
92
- lang == lg ? v.show() : v.hide();
93
- }
94
- );
95
- }
96
- );
 
97
  }
98
  }
99
- }
100
- );
 
 
 
 
101
  }
102
  );
103
 
23
  */
24
  jQuery(
25
  function( $ ) {
26
+ const handleQuickEditInsertion = ( mutationsList ) => {
27
+ for ( const mutation of mutationsList ) {
28
+ const form = mutation.addedNodes[0];
29
+ if ( 0 < mutation.addedNodes.length && form.classList.contains( 'inline-editor' ) ) {
30
+ // WordPress has inserted the quick edit form.
31
+ const post_id = Number( form.id.substring( 5 ) );
 
 
32
 
33
  if ( post_id > 0 ) {
34
+ // Get the language dropdown.
35
+ const select = form.querySelector( 'select[name="inline_lang_choice"]' );
36
+ const lang = document.querySelector( '#lang_' + String( post_id ) ).innerHTML;
37
+ select.value = lang; // Populates the dropdown with the post language.
38
 
39
+ filter_terms( lang ); // Initial filter for category checklist.
40
+ filter_pages( lang ); // Initial filter for parent dropdown.
41
 
42
+ // Modify category checklist and parent dropdown on language change.
43
+ select.addEventListener(
44
  'change',
45
+ function( event ) {
46
+ const newLang = event.target.value;
47
+ filter_terms( newLang );
48
+ filter_pages( newLang );
49
  }
50
+ );
51
+ }
52
  }
53
+ /**
54
+ * Filters the category checklist.
55
+ */
56
+ function filter_terms( lang ) {
57
+ if ( "undefined" != typeof( pll_term_languages ) ) {
58
+ $.each(
59
+ pll_term_languages,
60
+ function( lg, term_tax ) {
61
+ $.each(
62
+ term_tax,
63
+ function( tax, terms ) {
64
+ $.each(
65
+ terms,
66
+ function( i ) {
67
+ id = '#' + tax + '-' + pll_term_languages[ lg ][ tax ][ i ];
68
+ lang == lg ? $( id ).show() : $( id ).hide();
69
+ }
70
+ );
71
+ }
72
+ );
73
+ }
74
+ );
75
+ }
 
76
  }
 
77
 
78
+ /**
79
+ * Filters the parent page dropdown list.
80
+ */
81
+ function filter_pages( lang ) {
82
+ if ( "undefined" != typeof( pll_page_languages ) ) {
83
+ $.each(
84
+ pll_page_languages,
85
+ function( lg, pages ) {
86
+ $.each(
87
+ pages,
88
+ function( i ) {
89
+ v = $( '#post_parent option[value="' + pll_page_languages[ lg ][ i ] + '"]' );
90
+ lang == lg ? v.show() : v.hide();
91
+ }
92
+ );
93
+ }
94
+ );
95
+ }
96
  }
97
  }
98
+ }
99
+ const table = document.getElementById( 'the-list' );
100
+ const config = { childList: true, subtree: true };
101
+ const observer = new MutationObserver( handleQuickEditInsertion );
102
+
103
+ observer.observe( table, config);
104
  }
105
  );
106
 
js/build/post.min.js CHANGED
@@ -1 +1 @@
1
- var __webpack_exports__={};jQuery((function(a){a.ajaxPrefilter((function(n,t,e){"string"==typeof n.data&&-1!==n.data.indexOf("action=ajax-tag-search")&&(lang=a(':input[name="inline_lang_choice"]').val())&&(n.data="lang="+lang+"&"+n.data)}))})),jQuery((function(a){a(document).on("DOMNodeInserted",(function(n){var t=a(n.target);if("inline-edit"==t.attr("id")){var e=t.prev().attr("id").replace("post-","");if(e>0){var i=t.find(':input[name="inline_lang_choice"]'),o=a("#lang_"+e).html();i.val(o),l(o),p(o),i.on("change",(function(){l(a(this).val()),p(a(this).val())}))}}function l(n){"undefined"!=typeof pll_term_languages&&a.each(pll_term_languages,(function(t,e){a.each(e,(function(e,i){a.each(i,(function(i){id="#"+e+"-"+pll_term_languages[t][e][i],n==t?a(id).show():a(id).hide()}))}))}))}function p(n){"undefined"!=typeof pll_page_languages&&a.each(pll_page_languages,(function(t,e){a.each(e,(function(e){v=a('#post_parent option[value="'+pll_page_languages[t][e]+'"]'),n==t?v.show():v.hide()}))}))}}))})),jQuery((function(a){a(document).ajaxSuccess((function(n,t,e){if("string"==typeof e.data){var i=wpAjax.unserialize(e.data);void 0!==i.action&&"inline-save"==i.action&&function(n){var t=new Array;a(".translation_"+n).each((function(){t.push(a(this).parent().parent().attr("id").substring(5))}));var e={action:"pll_update_post_rows",post_id:n,translations:t.join(","),post_type:a("input[name='post_type']").val(),screen:a("input[name='screen']").val(),_pll_nonce:a("input[name='_inline_edit']").val()};a.post(ajaxurl,e,(function(n){if(n){var t=wpAjax.parseAjaxResponse(n,"pll-ajax-response");a.each(t.responses,(function(){"row"==this.what&&a("#post-"+this.supplemental.post_id).replaceWith(this.data)}))}}))}(i.post_ID)}}))})),jQuery((function(a){a.ajaxPrefilter((function(n,t,e){"string"==typeof n.data&&-1!==n.data.indexOf("action=find_posts")&&(n.data="pll_post_id="+a("#affected").val()+"&"+n.data)}))}));
1
+ var __webpack_exports__={};jQuery((function(n){n.ajaxPrefilter((function(e,t,a){"string"==typeof e.data&&-1!==e.data.indexOf("action=ajax-tag-search")&&(lang=n(':input[name="inline_lang_choice"]').val())&&(e.data="lang="+lang+"&"+e.data)}))})),jQuery((function(n){const e=document.getElementById("the-list");new MutationObserver((e=>{for(const i of e){const o=i.addedNodes[0];if(0<i.addedNodes.length&&o.classList.contains("inline-editor")){const s=Number(o.id.substring(5));if(s>0){const l=o.querySelector('select[name="inline_lang_choice"]'),c=document.querySelector("#lang_"+String(s)).innerHTML;l.value=c,t(c),a(c),l.addEventListener("change",(function(n){const e=n.target.value;t(e),a(e)}))}}function t(e){"undefined"!=typeof pll_term_languages&&n.each(pll_term_languages,(function(t,a){n.each(a,(function(a,i){n.each(i,(function(i){id="#"+a+"-"+pll_term_languages[t][a][i],e==t?n(id).show():n(id).hide()}))}))}))}function a(e){"undefined"!=typeof pll_page_languages&&n.each(pll_page_languages,(function(t,a){n.each(a,(function(a){v=n('#post_parent option[value="'+pll_page_languages[t][a]+'"]'),e==t?v.show():v.hide()}))}))}}})).observe(e,{childList:!0,subtree:!0})})),jQuery((function(n){n(document).ajaxSuccess((function(e,t,a){if("string"==typeof a.data){var i=wpAjax.unserialize(a.data);void 0!==i.action&&"inline-save"==i.action&&function(e){var t=new Array;n(".translation_"+e).each((function(){t.push(n(this).parent().parent().attr("id").substring(5))}));var a={action:"pll_update_post_rows",post_id:e,translations:t.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,a,(function(e){if(e){var t=wpAjax.parseAjaxResponse(e,"pll-ajax-response");n.each(t.responses,(function(){"row"==this.what&&n("#post-"+this.supplemental.post_id).replaceWith(this.data)}))}}))}(i.post_ID)}}))})),jQuery((function(n){n.ajaxPrefilter((function(e,t,a){"string"==typeof e.data&&-1!==e.data.indexOf("action=find_posts")&&(e.data="pll_post_id="+n("#affected").val()+"&"+e.data)}))}));
js/build/term.js CHANGED
@@ -8,30 +8,36 @@ var __webpack_exports__ = {};
8
  */
9
  jQuery(
10
  function( $ ) {
11
- $( document ).on(
12
- 'DOMNodeInserted',
13
- function( e ) {
14
- var t = $( e.target );
15
-
16
- // WP inserts the quick edit from
17
- if ( 'inline-edit' == t.attr( 'id' ) ) {
18
- var term_id = t.prev().attr( 'id' ).replace( "tag-", "" );
19
 
20
  if ( term_id > 0 ) {
21
- // language dropdown
22
- var select = t.find( ':input[name="inline_lang_choice"]' );
23
- var lang = $( '#lang_' + term_id ).html();
24
- select.val( lang ); // populates the dropdown
25
 
26
- // disable the language dropdown for default categories
27
- var default_cat = $( '#default_cat_' + term_id ).html();
28
  if ( term_id == default_cat ) {
29
- select.prop( 'disabled', true );
30
  }
31
  }
32
  }
33
  }
34
- );
 
 
 
 
 
 
 
 
35
  }
36
  );
37
 
8
  */
9
  jQuery(
10
  function( $ ) {
11
+ const handleQuickEditInsertion = ( mutationsList ) => {
12
+ for ( const mutation of mutationsList ) {
13
+ const form = mutation.addedNodes[0];
14
+ if ( 0 < mutation.addedNodes.length && form.classList.contains( 'inline-edit-row' ) ) {
15
+ // WordPress has inserted the quick edit form.
16
+ const term_id = Number( form.id.substring( 5 ) );
 
 
17
 
18
  if ( term_id > 0 ) {
19
+ // Get the language dropdown.
20
+ const select = form.querySelector( 'select[name="inline_lang_choice"]' );
21
+ const lang = document.querySelector( '#lang_' + String( term_id ) ).innerHTML;
22
+ select.value = lang; // Populates the dropdown with the post language.
23
 
24
+ // Disable the language dropdown for default categories.
25
+ const default_cat = document.querySelector( `#default_cat_${term_id}` ).innerHTML;
26
  if ( term_id == default_cat ) {
27
+ select.disabled = true;
28
  }
29
  }
30
  }
31
  }
32
+ }
33
+ const table = document.getElementById( 'the-list' );
34
+ if ( null !== table ) {
35
+ // Ensure the table is displayed before listening to any change.
36
+ const config = { childList: true, subtree: true };
37
+ const observer = new MutationObserver( handleQuickEditInsertion );
38
+
39
+ observer.observe( table, config);
40
+ }
41
  }
42
  );
43
 
js/build/term.min.js CHANGED
@@ -1 +1 @@
1
- var __webpack_exports__={};jQuery((function(a){a(document).on("DOMNodeInserted",(function(t){var n=a(t.target);if("inline-edit"==n.attr("id")){var e=n.prev().attr("id").replace("tag-","");if(e>0){var l=n.find(':input[name="inline_lang_choice"]'),i=a("#lang_"+e).html();l.val(i),e==a("#default_cat_"+e).html()&&l.prop("disabled",!0)}}}))})),jQuery((function(a){a(document).ajaxSuccess((function(t,n,e){function l(t){var n=new Array;a(".translation_"+t).each((function(){n.push(a(this).parent().parent().attr("id").substring(4))}));var e={action:"pll_update_term_rows",term_id:t,translations:n.join(","),taxonomy:a("input[name='taxonomy']").val(),post_type:a("input[name='post_type']").val(),screen:a("input[name='screen']").val(),_pll_nonce:a("#_pll_nonce").val()};a.post(ajaxurl,e,(function(t){if(t){var n=wpAjax.parseAjaxResponse(t,"pll-ajax-response");a.each(n.responses,(function(){"row"==this.what&&a("#tag-"+this.supplemental.term_id).replaceWith(this.data)}))}}))}var i=wpAjax.unserialize(e.data);if(void 0!==i.action)switch(i.action){case"add-tag":res=wpAjax.parseAjaxResponse(n.responseXML,"pll-ajax-response"),a.each(res.responses,(function(){"term"==this.what&&l(this.supplemental.term_id)})),a(".htr_lang").val(0);break;case"delete-tag":l(i.tag_ID);break;case"inline-save-tax":l(i.tax_ID)}}))})),jQuery((function(a){function t(){a(".tr_lang").each((function(){var t=a(this).attr("id").substring(8),n=a(this).parent().parent().siblings(".pll-edit-column");a(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_terms_not_translated&term_language="+a("#term_lang_choice").val()+"&term_id="+a("input[name='tag_ID']").val()+"&taxonomy="+a("input[name='taxonomy']").val()+"&translation_language="+t+"&post_type="+typenow+"&_pll_nonce="+a("#_pll_nonce").val(),select:function(e,l){a("#htr_lang_"+t).val(l.item.id),n.html(l.item.link)}}),a(this).on("blur",(function(){a(this).val()||(a("#htr_lang_"+t).val(0),n.html(n.siblings(".hidden").children().clone()))}))}))}t(),a("#term_lang_choice").on("change",(function(){var n=a(this).val(),e=a(this).children('option[value="'+n+'"]').attr("lang"),l=a('.pll-translation-column > span[lang="'+e+'"]').attr("dir"),i={action:"term_lang_choice",lang:n,from_tag:a("input[name='from_tag']").val(),term_id:a("input[name='tag_ID']").val(),taxonomy:a("input[name='taxonomy']").val(),post_type:typenow,_pll_nonce:a("#_pll_nonce").val()};a.post(ajaxurl,i,(function(n){var e=wpAjax.parseAjaxResponse(n,"pll-ajax-response");a.each(e.responses,(function(){switch(this.what){case"translations":a("#term-translations").html(this.data),t();break;case"parent":a("#parent").replaceWith(this.data);break;case"tag_cloud":a(".tagcloud").replaceWith(this.data);break;case"flag":a(".pll-select-flag").html(this.data)}})),a("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+l)}))}))}));
1
+ var __webpack_exports__={};jQuery((function(t){const a=t=>{for(const a of t){const t=a.addedNodes[0];if(0<a.addedNodes.length&&t.classList.contains("inline-edit-row")){const a=Number(t.id.substring(5));if(a>0){const e=t.querySelector('select[name="inline_lang_choice"]'),n=document.querySelector("#lang_"+String(a)).innerHTML;e.value=n;a==document.querySelector(`#default_cat_${a}`).innerHTML&&(e.disabled=!0)}}}},e=document.getElementById("the-list");if(null!==e){const t={childList:!0,subtree:!0};new MutationObserver(a).observe(e,t)}})),jQuery((function(t){t(document).ajaxSuccess((function(a,e,n){function l(a){var e=new Array;t(".translation_"+a).each((function(){e.push(t(this).parent().parent().attr("id").substring(4))}));var n={action:"pll_update_term_rows",term_id:a,translations:e.join(","),taxonomy:t("input[name='taxonomy']").val(),post_type:t("input[name='post_type']").val(),screen:t("input[name='screen']").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,n,(function(a){if(a){var e=wpAjax.parseAjaxResponse(a,"pll-ajax-response");t.each(e.responses,(function(){"row"==this.what&&t("#tag-"+this.supplemental.term_id).replaceWith(this.data)}))}}))}var s=wpAjax.unserialize(n.data);if(void 0!==s.action)switch(s.action){case"add-tag":res=wpAjax.parseAjaxResponse(e.responseXML,"pll-ajax-response"),t.each(res.responses,(function(){"term"==this.what&&l(this.supplemental.term_id)})),t(".htr_lang").val(0);break;case"delete-tag":l(s.tag_ID);break;case"inline-save-tax":l(s.tax_ID)}}))})),jQuery((function(t){function a(){t(".tr_lang").each((function(){var a=t(this).attr("id").substring(8),e=t(this).parent().parent().siblings(".pll-edit-column");t(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_terms_not_translated&term_language="+t("#term_lang_choice").val()+"&term_id="+t("input[name='tag_ID']").val()+"&taxonomy="+t("input[name='taxonomy']").val()+"&translation_language="+a+"&post_type="+typenow+"&_pll_nonce="+t("#_pll_nonce").val(),select:function(n,l){t("#htr_lang_"+a).val(l.item.id),e.html(l.item.link)}}),t(this).on("blur",(function(){t(this).val()||(t("#htr_lang_"+a).val(0),e.html(e.siblings(".hidden").children().clone()))}))}))}a(),t("#term_lang_choice").on("change",(function(){var e=t(this).val(),n=t(this).children('option[value="'+e+'"]').attr("lang"),l=t('.pll-translation-column > span[lang="'+n+'"]').attr("dir"),s={action:"term_lang_choice",lang:e,from_tag:t("input[name='from_tag']").val(),term_id:t("input[name='tag_ID']").val(),taxonomy:t("input[name='taxonomy']").val(),post_type:typenow,_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,s,(function(e){var n=wpAjax.parseAjaxResponse(e,"pll-ajax-response");t.each(n.responses,(function(){switch(this.what){case"translations":t("#term-translations").html(this.data),a();break;case"parent":t("#parent").replaceWith(this.data);break;case"tag_cloud":t(".tagcloud").replaceWith(this.data);break;case"flag":t(".pll-select-flag").html(this.data)}})),t("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+l)}))}))}));
modules/site-health/admin-site-health.php CHANGED
@@ -25,7 +25,7 @@ class PLL_Admin_Site_Health {
25
  *
26
  * @since 2.8
27
  *
28
- * @var PLL_Admin_Static_Pages
29
  */
30
  protected $static_pages;
31
 
@@ -104,10 +104,83 @@ class PLL_Admin_Site_Health {
104
  }
105
  }
106
  );
107
-
108
  return implode( ' | ', $array );
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  /**
112
  * Add Polylang Options to Site Health Informations tab.
113
  *
@@ -118,56 +191,59 @@ class PLL_Admin_Site_Health {
118
  */
119
  public function info_options( $debug_info ) {
120
  $fields = array();
 
121
  foreach ( $this->model->options as $key => $value ) {
122
  if ( in_array( $key, $this->exclude_options_keys() ) ) {
123
  continue;
124
  }
125
 
126
- if ( ! is_array( $value ) ) {
127
- if ( empty( $value ) ) {
128
- $value = '0';
129
- }
 
 
 
130
 
131
- $fields[ $key ]['label'] = $key;
132
- $fields[ $key ]['value'] = $value;
133
- } elseif ( empty( $value ) ) {
134
- $fields[ $key ]['label'] = $key;
135
- $fields[ $key ]['value'] = '0';
136
- } else {
137
- switch ( $key ) {
138
- case 'post_types':
139
- $fields[ $key ]['label'] = $key;
140
- $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_post_types() );
141
- break;
142
- case 'taxonomies':
143
- $fields[ $key ]['label'] = $key;
144
- $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_taxonomies() );
145
- break;
146
- case 'domains':
147
  $fields[ $key ]['label'] = $key;
148
- $fields[ $key ]['value'] = $this->format_array( $value );
149
- break;
150
- case 'nav_menus':
151
- $current_theme = get_stylesheet();
152
- if ( isset( $value[ $current_theme ] ) ) {
153
- foreach ( $value[ $current_theme ] as $location => $lang ) {
154
- /* translators: placeholder is the menu location name */
155
- $fields[ $location ]['label'] = sprintf( 'menu: %s', $location );
156
- $fields[ $location ]['value'] = $this->format_array( $lang );
157
- }
158
- }
159
- break;
160
- case 'media':
161
- foreach ( $value as $sub_key => $sub_value ) {
162
- $fields[ "$key-$sub_key" ]['label'] = "$key $sub_key";
163
- $fields[ "$key-$sub_key" ]['value'] = $sub_value;
164
  }
165
- break;
166
- default:
167
- $fields[ $key ]['label'] = $key;
168
- $fields[ $key ]['value'] = implode( ', ', $value );
169
- break;
170
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
  }
173
 
@@ -192,7 +268,7 @@ class PLL_Admin_Site_Health {
192
  foreach ( $this->model->get_languages_list() as $language ) {
193
  $fields = array();
194
 
195
- foreach ( $language as $key => $value ) {
196
  if ( in_array( $key, $this->exclude_language_keys(), true ) ) {
197
  continue;
198
  }
25
  *
26
  * @since 2.8
27
  *
28
+ * @var PLL_Admin_Static_Pages|null
29
  */
30
  protected $static_pages;
31
 
104
  }
105
  }
106
  );
 
107
  return implode( ' | ', $array );
108
  }
109
 
110
+ /**
111
+ * Transforms the option value to readable human sentence.
112
+ *
113
+ * @since 3.3
114
+ *
115
+ * @param string $key Option name.
116
+ * @param mixed $value Option value.
117
+ * @return mixed Option value.
118
+ */
119
+ public function format_value( $key, $value ) {
120
+ switch ( $key ) {
121
+ case 'browser':
122
+ if ( ! $value ) {
123
+ $value = '0: ' . esc_html__( 'Detect browser language deactivated', 'polylang' );
124
+ break;
125
+ }
126
+ $value = '1: ' . esc_html__( 'Detect browser language activated', 'polylang' );
127
+ break;
128
+ case 'rewrite':
129
+ if ( $value ) {
130
+ $value = '1: ' . esc_html__( 'Remove /language/ in pretty permalinks', 'polylang' );
131
+ break;
132
+ }
133
+ $value = '0: ' . esc_html__( 'Keep /language/ in pretty permalinks', 'polylang' );
134
+ break;
135
+ case 'hide_default':
136
+ if ( $value ) {
137
+ $value = '1: ' . esc_html__( 'Hide URL language information for default language', 'polylang' );
138
+ break;
139
+ }
140
+ $value = '0: ' . esc_html__( 'Display URL language information for default language', 'polylang' );
141
+ break;
142
+ case 'force_lang':
143
+ switch ( $value ) {
144
+ case '0':
145
+ $value = '0: ' . esc_html__( 'The language is set from content', 'polylang' );
146
+ break;
147
+ case '1':
148
+ $value = '1: ' . esc_html__( 'The language is set from the directory name in pretty permalinks', 'polylang' );
149
+ break;
150
+ case '2':
151
+ $value = '2: ' . esc_html__( 'The language is set from the subdomain name in pretty permalinks', 'polylang' );
152
+ break;
153
+ case '3':
154
+ $value = '3: ' . esc_html__( 'The language is set from different domains', 'polylang' );
155
+ break;
156
+ }
157
+ break;
158
+ case 'redirect_lang':
159
+ if ( $value ) {
160
+ $value = '1: ' . esc_html__( 'The front page URL contains the language code instead of the page name or page id', 'polylang' );
161
+ break;
162
+ }
163
+ $value = '0: ' . esc_html__( 'The front page URL contains the page name or page id instead of the language code', 'polylang' );
164
+
165
+ break;
166
+ case 'media_support':
167
+ if ( ! $value ) {
168
+ $value = '0: ' . esc_html__( 'The media are not translated', 'polylang' );
169
+ break;
170
+ }
171
+ $value = '1: ' . esc_html__( 'The media are translated', 'polylang' );
172
+ break;
173
+
174
+ case 'sync':
175
+ if ( empty( $value ) ) {
176
+ $value = '0: ' . esc_html__( 'Synchronization disabled', 'polylang' );
177
+ }
178
+ break;
179
+ }
180
+
181
+ return $value;
182
+ }
183
+
184
  /**
185
  * Add Polylang Options to Site Health Informations tab.
186
  *
191
  */
192
  public function info_options( $debug_info ) {
193
  $fields = array();
194
+
195
  foreach ( $this->model->options as $key => $value ) {
196
  if ( in_array( $key, $this->exclude_options_keys() ) ) {
197
  continue;
198
  }
199
 
200
+ $value = $this->format_value( $key, $value );
201
+
202
+ switch ( $key ) {
203
+ case 'domains':
204
+ if ( 3 === $this->model->options['force_lang'] ) {
205
+ $value = is_array( $value ) ? $value : array();
206
+ $value = $this->format_array( $value );
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  $fields[ $key ]['label'] = $key;
209
+ $fields[ $key ]['value'] = $value;
210
+ }
211
+ break;
212
+
213
+ case 'nav_menus':
214
+ $current_theme = get_stylesheet();
215
+ if ( is_array( $value ) && isset( $value[ $current_theme ] ) ) {
216
+ foreach ( $value[ $current_theme ] as $location => $lang ) {
217
+ $lang = is_array( $lang ) ? $lang : array();
218
+
219
+ $fields[ $location ]['label'] = sprintf( 'menu: %s', $location );
220
+ $fields[ $location ]['value'] = $this->format_array( $lang );
 
 
 
 
221
  }
222
+ }
223
+ break;
224
+
225
+ case 'media':
226
+ $value = is_array( $value ) ? $value : array();
227
+ foreach ( $value as $sub_key => $sub_value ) {
228
+ $fields[ "$key-$sub_key" ]['label'] = "$key $sub_key";
229
+ $fields[ "$key-$sub_key" ]['value'] = $sub_value;
230
+ }
231
+ break;
232
+
233
+ case 'post_types':
234
+ $fields[ $key ]['label'] = $key;
235
+ $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_post_types() );
236
+ break;
237
+
238
+ case 'taxonomies':
239
+ $fields[ $key ]['label'] = $key;
240
+ $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_taxonomies() );
241
+ break;
242
+
243
+ default:
244
+ $fields[ $key ]['label'] = $key;
245
+ $fields[ $key ]['value'] = empty( $value ) ? '0' : $value;
246
+ break;
247
  }
248
  }
249
 
268
  foreach ( $this->model->get_languages_list() as $language ) {
269
  $fields = array();
270
 
271
+ foreach ( get_object_vars( $language ) as $key => $value ) {
272
  if ( in_array( $key, $this->exclude_language_keys(), true ) ) {
273
  continue;
274
  }
modules/sitemaps/sitemaps.php CHANGED
@@ -71,19 +71,6 @@ class PLL_Sitemaps extends PLL_Abstract_Sitemaps {
71
  return $lang;
72
  }
73
 
74
- /**
75
- * Whitelists the home url filter for the sitemaps
76
- *
77
- * @since 2.8
78
- *
79
- * @param array $whitelist White list.
80
- * @return array
81
- */
82
- public function home_url_white_list( $whitelist ) {
83
- $whitelist[] = array( 'file' => 'class-wp-sitemaps-posts' );
84
- return $whitelist;
85
- }
86
-
87
  /**
88
  * Filters the sitemaps rewrite rules to take the languages into account.
89
  *
71
  return $lang;
72
  }
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  /**
75
  * Filters the sitemaps rewrite rules to take the languages into account.
76
  *
modules/sync/sync-metas.php CHANGED
@@ -26,14 +26,14 @@ abstract class PLL_Sync_Metas {
26
  *
27
  * @var array
28
  */
29
- protected $prev_value;
30
 
31
  /**
32
  * Stores the metas to synchronize before deleting them.
33
  *
34
  * @var array
35
  */
36
- protected $to_copy;
37
 
38
  /**
39
  * Constructor
@@ -62,7 +62,7 @@ abstract class PLL_Sync_Metas {
62
  * @return void
63
  */
64
  protected function remove_add_meta_action() {
65
- remove_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 );
66
  }
67
 
68
  /**
@@ -75,11 +75,11 @@ abstract class PLL_Sync_Metas {
75
  protected function remove_all_meta_actions() {
76
  $this->remove_add_meta_action();
77
 
78
- remove_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 );
79
- remove_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 );
80
 
81
- remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
82
- remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
83
  }
84
 
85
  /**
@@ -255,6 +255,7 @@ abstract class PLL_Sync_Metas {
255
  */
256
  public function update_meta( $mid, $id, $meta_key, $meta_value ) {
257
  static $avoid_recursion = false;
 
258
 
259
  if ( ! $avoid_recursion ) {
260
  $avoid_recursion = true;
26
  *
27
  * @var array
28
  */
29
+ protected $prev_value = array();
30
 
31
  /**
32
  * Stores the metas to synchronize before deleting them.
33
  *
34
  * @var array
35
  */
36
+ protected $to_copy = array();
37
 
38
  /**
39
  * Constructor
62
  * @return void
63
  */
64
  protected function remove_add_meta_action() {
65
+ remove_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ) );
66
  }
67
 
68
  /**
75
  protected function remove_all_meta_actions() {
76
  $this->remove_add_meta_action();
77
 
78
+ remove_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999 );
79
+ remove_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ) );
80
 
81
+ remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ) );
82
+ remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ) );
83
  }
84
 
85
  /**
255
  */
256
  public function update_meta( $mid, $id, $meta_key, $meta_value ) {
257
  static $avoid_recursion = false;
258
+ $id = (int) $id;
259
 
260
  if ( ! $avoid_recursion ) {
261
  $avoid_recursion = true;
modules/sync/sync-tax.php CHANGED
@@ -211,7 +211,7 @@ class PLL_Sync_Tax {
211
  * @return void
212
  */
213
  public function copy( $from, $to, $lang ) {
214
- remove_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 6 );
215
 
216
  // Get taxonomies to sync for this post type
217
  $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) );
@@ -295,7 +295,7 @@ class PLL_Sync_Tax {
295
  * @return void
296
  */
297
  public function pre_delete_term() {
298
- remove_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
299
  }
300
 
301
  /**
211
  * @return void
212
  */
213
  public function copy( $from, $to, $lang ) {
214
+ remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) );
215
 
216
  // Get taxonomies to sync for this post type
217
  $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) );
295
  * @return void
296
  */
297
  public function pre_delete_term() {
298
+ remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) );
299
  }
300
 
301
  /**
modules/sync/sync.php CHANGED
@@ -212,7 +212,7 @@ class PLL_Sync {
212
  $tr_parent = $this->model->term->get_translation( $term->parent, $lang );
213
  $tr_term = get_term( (int) $tr_id, $taxonomy );
214
 
215
- if ( $tr_term instanceof WP_Term ) {
216
  $wpdb->update(
217
  $wpdb->term_taxonomy,
218
  array( 'parent' => $tr_parent ? $tr_parent : 0 ),
212
  $tr_parent = $this->model->term->get_translation( $term->parent, $lang );
213
  $tr_term = get_term( (int) $tr_id, $taxonomy );
214
 
215
+ if ( $tr_term instanceof WP_Term && ! ( $term->parent && empty( $tr_parent ) ) ) {
216
  $wpdb->update(
217
  $wpdb->term_taxonomy,
218
  array( 'parent' => $tr_parent ? $tr_parent : 0 ),
modules/wizard/wizard.php CHANGED
@@ -39,7 +39,7 @@ class PLL_Wizard {
39
  /**
40
  * The current step.
41
  *
42
- * @var string
43
  */
44
  protected $step;
45
 
39
  /**
40
  * The current step.
41
  *
42
+ * @var string|null
43
  */
44
  protected $step;
45
 
modules/wpml/wpml-api.php CHANGED
@@ -15,7 +15,7 @@ class PLL_WPML_API {
15
  /**
16
  * Stores the original language when the language is switched.
17
  *
18
- * @var PLL_Language
19
  */
20
  private static $original_language = null;
21
 
15
  /**
16
  * Stores the original language when the language is switched.
17
  *
18
+ * @var PLL_Language|null
19
  */
20
  private static $original_language = null;
21
 
modules/wpml/wpml-compat.php CHANGED
@@ -14,7 +14,7 @@ class PLL_WPML_Compat {
14
  /**
15
  * Singleton instance
16
  *
17
- * @var PLL_WPML_Compat
18
  */
19
  protected static $instance;
20
 
14
  /**
15
  * Singleton instance
16
  *
17
+ * @var PLL_WPML_Compat|null
18
  */
19
  protected static $instance;
20
 
modules/wpml/wpml-config.php CHANGED
@@ -14,7 +14,7 @@ class PLL_WPML_Config {
14
  /**
15
  * Singleton instance
16
  *
17
- * @var PLL_WPML_Config
18
  */
19
  protected static $instance;
20
 
@@ -23,15 +23,24 @@ class PLL_WPML_Config {
23
  *
24
  * @var SimpleXMLElement[]
25
  */
26
- protected $xmls;
27
 
28
  /**
29
- * The list of xml files.
30
  *
31
- * @var string[]
 
 
32
  */
33
  protected $files;
34
 
 
 
 
 
 
 
 
35
  /**
36
  * Constructor
37
  *
@@ -66,96 +75,94 @@ class PLL_WPML_Config {
66
  */
67
  public function init() {
68
  $this->xmls = array();
69
- $files = $this->get_files();
70
 
71
- if ( ! empty( $files ) ) {
 
 
72
 
73
- // Read all files.
74
- if ( extension_loaded( 'simplexml' ) ) {
75
- foreach ( $files as $context => $file ) {
76
- $xml = simplexml_load_file( $file );
77
- if ( false !== $xml ) {
78
- $this->xmls[ $context ] = $xml;
79
- }
80
- }
 
81
  }
82
  }
83
 
84
- if ( ! empty( $this->xmls ) ) {
85
- add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 20, 2 );
86
- add_filter( 'pll_copy_term_metas', array( $this, 'copy_term_metas' ), 20, 2 );
87
- add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 );
88
- add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 );
89
-
90
- foreach ( $this->xmls as $context => $xml ) {
91
- $keys = $xml->xpath( 'admin-texts/key' );
92
- if ( is_array( $keys ) ) {
93
- foreach ( $keys as $key ) {
94
- $attributes = $key->attributes();
95
- $name = (string) $attributes['name'];
96
-
97
- if ( false !== strpos( $name, '*' ) ) {
98
- $pattern = '#^' . str_replace( '*', '(?:.+)', $name ) . '$#';
99
- $names = preg_grep( $pattern, array_keys( wp_load_alloptions() ) );
100
-
101
- if ( is_array( $names ) ) {
102
- foreach ( $names as $_name ) {
103
- $this->register_or_translate_option( $context, $_name, $key );
104
- }
105
- }
106
- } else {
107
- $this->register_or_translate_option( $context, $name, $key );
108
- }
109
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
  }
112
  }
113
  }
114
 
115
  /**
116
- * Get all wpml-config.xml files in plugins, theme, child theme and Polylang custom directory.
117
  *
118
  * @since 3.1
119
  *
120
- * @return array
 
 
121
  */
122
  public function get_files() {
123
-
124
- if ( ! empty( $this->files ) ) {
125
  return $this->files;
126
  }
127
 
128
- $files = array();
129
-
130
- // Plugins
131
- // Don't forget sitewide active plugins thanks to Reactorshop http://wordpress.org/support/topic/polylang-and-yoast-seo-plugin/page/2?replies=38#post-4801829
132
- $plugins = ( is_multisite() && $sitewide_plugins = get_site_option( 'active_sitewide_plugins' ) ) && is_array( $sitewide_plugins ) ? array_keys( $sitewide_plugins ) : array();
133
- $plugins = array_merge( $plugins, get_option( 'active_plugins', array() ) );
134
-
135
- foreach ( $plugins as $plugin ) {
136
- if ( file_exists( $file = WP_PLUGIN_DIR . '/' . dirname( $plugin ) . '/wpml-config.xml' ) ) {
137
- $files[ dirname( $plugin ) ] = $file;
138
- }
139
- }
140
-
141
- // Theme
142
- if ( file_exists( $file = ( $template = get_template_directory() ) . '/wpml-config.xml' ) ) {
143
- $files[ get_template() ] = $file;
144
- }
145
-
146
- // Child theme
147
- if ( ( $stylesheet = get_stylesheet_directory() ) !== $template && file_exists( $file = $stylesheet . '/wpml-config.xml' ) ) {
148
- $files[ get_stylesheet() ] = $file;
149
- }
150
-
151
- // Custom
152
- if ( file_exists( $file = PLL_LOCAL_DIR . '/wpml-config.xml' ) ) {
153
- $files['Polylang'] = $file;
154
- }
155
-
156
- $this->files = $files;
157
 
158
- return $files;
159
  }
160
 
161
  /**
@@ -170,17 +177,22 @@ class PLL_WPML_Config {
170
  public function copy_post_metas( $metas, $sync ) {
171
  foreach ( $this->xmls as $xml ) {
172
  $cfs = $xml->xpath( 'custom-fields/custom-field' );
173
- if ( is_array( $cfs ) ) {
174
- foreach ( $cfs as $cf ) {
175
- $attributes = $cf->attributes();
176
- if ( 'copy' == $attributes['action'] || ( ! $sync && in_array( $attributes['action'], array( 'translate', 'copy-once' ) ) ) ) {
177
- $metas[] = (string) $cf;
178
- } else {
179
- $metas = array_diff( $metas, array( (string) $cf ) );
180
- }
 
 
 
 
181
  }
182
  }
183
  }
 
184
  return $metas;
185
  }
186
 
@@ -196,20 +208,143 @@ class PLL_WPML_Config {
196
  public function copy_term_metas( $metas, $sync ) {
197
  foreach ( $this->xmls as $xml ) {
198
  $cfs = $xml->xpath( 'custom-term-fields/custom-term-field' );
199
- if ( is_array( $cfs ) ) {
200
- foreach ( $cfs as $cf ) {
201
- $attributes = $cf->attributes();
202
- if ( 'copy' == $attributes['action'] || ( ! $sync && in_array( $attributes['action'], array( 'translate', 'copy-once' ) ) ) ) {
203
- $metas[] = (string) $cf;
204
- } else {
205
- $metas = array_diff( $metas, array( (string) $cf ) );
206
- }
 
 
 
 
207
  }
208
  }
209
  }
 
210
  return $metas;
211
  }
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  /**
214
  * Language and translation management for custom post types.
215
  *
@@ -222,17 +357,22 @@ class PLL_WPML_Config {
222
  public function translate_types( $types, $hide ) {
223
  foreach ( $this->xmls as $xml ) {
224
  $pts = $xml->xpath( 'custom-types/custom-type' );
225
- if ( is_array( $pts ) ) {
226
- foreach ( $pts as $pt ) {
227
- $attributes = $pt->attributes();
228
- if ( 1 == $attributes['translate'] && ! $hide ) {
229
- $types[ (string) $pt ] = (string) $pt;
230
- } else {
231
- unset( $types[ (string) $pt ] ); // The theme/plugin author decided what to do with the post type so don't allow the user to change this
232
- }
 
 
 
 
233
  }
234
  }
235
  }
 
236
  return $types;
237
  }
238
 
@@ -248,20 +388,126 @@ class PLL_WPML_Config {
248
  public function translate_taxonomies( $taxonomies, $hide ) {
249
  foreach ( $this->xmls as $xml ) {
250
  $taxos = $xml->xpath( 'taxonomies/taxonomy' );
251
- if ( is_array( $taxos ) ) {
252
- foreach ( $taxos as $tax ) {
253
- $attributes = $tax->attributes();
254
- if ( 1 == $attributes['translate'] && ! $hide ) {
255
- $taxonomies[ (string) $tax ] = (string) $tax;
256
- } else {
257
- unset( $taxonomies[ (string) $tax ] ); // the theme/plugin author decided what to do with the taxonomy so don't allow the user to change this
258
- }
 
 
 
 
259
  }
260
  }
261
  }
 
262
  return $taxonomies;
263
  }
264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  /**
266
  * Registers or translates the strings for an option
267
  *
@@ -281,23 +527,193 @@ class PLL_WPML_Config {
281
  * Recursively transforms xml nodes to an array, ready for PLL_Translate_Option.
282
  *
283
  * @since 2.9
 
 
 
284
  *
285
- * @param SimpleXMLElement $key XML node.
286
- * @param array $arr Array of option keys to translate.
 
287
  * @return array
288
  */
289
- protected function xml_to_array( $key, &$arr = array() ) {
290
- $attributes = $key->attributes();
291
- $name = (string) $attributes['name'];
 
 
 
 
292
  $children = $key->children();
293
 
294
  if ( count( $children ) ) {
295
  foreach ( $children as $child ) {
296
- $arr[ $name ] = $this->xml_to_array( $child, $arr[ $name ] );
 
 
 
 
297
  }
298
  } else {
299
- $arr[ $name ] = true; // Multiline as in WPML.
300
  }
 
301
  return $arr;
302
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
14
  /**
15
  * Singleton instance
16
  *
17
+ * @var PLL_WPML_Config|null
18
  */
19
  protected static $instance;
20
 
23
  *
24
  * @var SimpleXMLElement[]
25
  */
26
+ protected $xmls = array();
27
 
28
  /**
29
+ * The list of xml file paths.
30
  *
31
+ * @var string[]|null
32
+ *
33
+ * @phpstan-var array<string, string>|null
34
  */
35
  protected $files;
36
 
37
+ /**
38
+ * List of rules to extract strings to translate from blocks.
39
+ *
40
+ * @var string[][][]|null
41
+ */
42
+ protected $parsing_rules = null;
43
+
44
  /**
45
  * Constructor
46
  *
75
  */
76
  public function init() {
77
  $this->xmls = array();
78
+ $files = $this->get_files();
79
 
80
+ if ( empty( $files ) ) {
81
+ return;
82
+ }
83
 
84
+ if ( ! extension_loaded( 'simplexml' ) ) {
85
+ return;
86
+ }
87
+
88
+ // Read all files.
89
+ foreach ( $files as $context => $file ) {
90
+ $xml = simplexml_load_file( $file );
91
+ if ( false !== $xml ) {
92
+ $this->xmls[ $context ] = $xml;
93
  }
94
  }
95
 
96
+ if ( empty( $this->xmls ) ) {
97
+ return;
98
+ }
99
+
100
+ add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 20, 2 );
101
+ add_filter( 'pll_copy_term_metas', array( $this, 'copy_term_metas' ), 20, 2 );
102
+ add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 );
103
+ add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 );
104
+ add_filter( 'pll_blocks_xpath_rules', array( $this, 'translate_blocks' ) );
105
+ add_filter( 'pll_blocks_rules_for_attributes', array( $this, 'translate_blocks_attributes' ) );
106
+
107
+ // Export.
108
+ add_filter( 'pll_post_metas_to_export', array( $this, 'post_metas_to_export' ) );
109
+ add_filter( 'pll_term_metas_to_export', array( $this, 'term_metas_to_export' ) );
110
+
111
+ foreach ( $this->xmls as $context => $xml ) {
112
+ $keys = $xml->xpath( 'admin-texts/key' );
113
+
114
+ if ( ! is_array( $keys ) ) {
115
+ continue;
116
+ }
117
+
118
+ foreach ( $keys as $key ) {
119
+ $name = $this->get_field_attribute( $key, 'name' );
120
+
121
+ if ( false === strpos( $name, '*' ) ) {
122
+ $this->register_or_translate_option( $context, $name, $key );
123
+ continue;
124
+ }
125
+
126
+ $pattern = '#^' . str_replace( '*', '(?:.+)', $name ) . '$#';
127
+ $names = preg_grep( $pattern, array_keys( wp_load_alloptions() ) );
128
+
129
+ if ( ! is_array( $names ) ) {
130
+ continue;
131
+ }
132
+
133
+ foreach ( $names as $_name ) {
134
+ $this->register_or_translate_option( $context, $_name, $key );
135
  }
136
  }
137
  }
138
  }
139
 
140
  /**
141
+ * Returns all wpml-config.xml files in MU plugins, plugins, theme, child theme, and Polylang custom directory.
142
  *
143
  * @since 3.1
144
  *
145
+ * @return string[] A context identifier as array key, a file path as array value.
146
+ *
147
+ * @phpstan-return array<string, string>
148
  */
149
  public function get_files() {
150
+ if ( is_array( $this->files ) ) {
 
151
  return $this->files;
152
  }
153
 
154
+ $this->files = array_merge(
155
+ // Plugins.
156
+ $this->get_plugin_files(),
157
+ // Theme and child theme.
158
+ $this->get_theme_files(),
159
+ // MU Plugins.
160
+ $this->get_mu_plugin_files(),
161
+ // Custom.
162
+ $this->get_custom_files()
163
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
+ return $this->files;
166
  }
167
 
168
  /**
177
  public function copy_post_metas( $metas, $sync ) {
178
  foreach ( $this->xmls as $xml ) {
179
  $cfs = $xml->xpath( 'custom-fields/custom-field' );
180
+
181
+ if ( ! is_array( $cfs ) ) {
182
+ continue;
183
+ }
184
+
185
+ foreach ( $cfs as $cf ) {
186
+ $action = $this->get_field_attribute( $cf, 'action' );
187
+
188
+ if ( 'copy' === $action || ( ! $sync && in_array( $action, array( 'translate', 'copy-once' ), true ) ) ) {
189
+ $metas[] = (string) $cf;
190
+ } else {
191
+ $metas = array_diff( $metas, array( (string) $cf ) );
192
  }
193
  }
194
  }
195
+
196
  return $metas;
197
  }
198
 
208
  public function copy_term_metas( $metas, $sync ) {
209
  foreach ( $this->xmls as $xml ) {
210
  $cfs = $xml->xpath( 'custom-term-fields/custom-term-field' );
211
+
212
+ if ( ! is_array( $cfs ) ) {
213
+ continue;
214
+ }
215
+
216
+ foreach ( $cfs as $cf ) {
217
+ $action = $this->get_field_attribute( $cf, 'action' );
218
+
219
+ if ( 'copy' === $action || ( ! $sync && in_array( $action, array( 'translate', 'copy-once' ), true ) ) ) {
220
+ $metas[] = (string) $cf;
221
+ } else {
222
+ $metas = array_diff( $metas, array( (string) $cf ) );
223
  }
224
  }
225
  }
226
+
227
  return $metas;
228
  }
229
 
230
+ /**
231
+ * Adds post meta keys to export.
232
+ *
233
+ * @since 3.3
234
+ * @see PLL_Export_Metas
235
+ *
236
+ * @param array $keys {
237
+ * A recursive array containing nested meta sub-keys to translate.
238
+ * Ex: array(
239
+ * 'meta_to_translate_1' => 1,
240
+ * 'meta_to_translate_2' => 1,
241
+ * 'meta_to_translate_3' => array(
242
+ * 'sub_key_to_translate_1' => 1,
243
+ * 'sub_key_to_translate_2' => array(
244
+ * 'sub_sub_key_to_translate_1' => 1,
245
+ * ),
246
+ * ),
247
+ * )
248
+ * }
249
+ * @return array
250
+ *
251
+ * @phpstan-param array<string, mixed> $keys
252
+ * @phpstan-return array<string, mixed>
253
+ */
254
+ public function post_metas_to_export( $keys ) {
255
+ // Add keys that have the `action` attribute set to `translate`.
256
+ foreach ( $this->xmls as $xml ) {
257
+ $fields = $xml->xpath( 'custom-fields/custom-field' );
258
+
259
+ if ( ! is_array( $fields ) ) {
260
+ // No custom fields.
261
+ continue;
262
+ }
263
+
264
+ foreach ( $fields as $field ) {
265
+ $action = $this->get_field_attribute( $field, 'action' );
266
+
267
+ if ( 'translate' !== $action ) {
268
+ continue;
269
+ }
270
+
271
+ $keys[ (string) $field ] = 1;
272
+ }
273
+ }
274
+
275
+ // Deal with sub-field translations.
276
+ foreach ( $this->xmls as $xml ) {
277
+ $fields = $xml->xpath( 'custom-fields-texts/key' );
278
+
279
+ if ( ! is_array( $fields ) ) {
280
+ // No 'custom-fields-texts' nodes.
281
+ continue;
282
+ }
283
+
284
+ foreach ( $fields as $field ) {
285
+ $name = $this->get_field_attribute( $field, 'name' );
286
+
287
+ if ( '' === $name ) {
288
+ // Wrong configuration: empty `name` attribute (meta name).
289
+ continue;
290
+ }
291
+
292
+ if ( ! array_key_exists( $name, $keys ) ) {
293
+ // Wrong configuration: the field is not in `custom-fields/custom-field`.
294
+ continue;
295
+ }
296
+
297
+ $keys = $this->xml_to_array( $field, $keys, 1 );
298
+ }
299
+ }
300
+
301
+ return $keys;
302
+ }
303
+
304
+ /**
305
+ * Adds term meta keys to export.
306
+ * Note: sub-key translations are not currently supported by WPML.
307
+ *
308
+ * @since 3.3
309
+ * @see PLL_Export_Metas
310
+ *
311
+ * @param array $keys {
312
+ * An array containing meta keys to translate.
313
+ * Ex: array(
314
+ * 'meta_to_translate_1' => 1,
315
+ * 'meta_to_translate_2' => 1,
316
+ * 'meta_to_translate_3' => 1,
317
+ * )
318
+ * }
319
+ * @return array
320
+ *
321
+ * @phpstan-param array<string, mixed> $keys
322
+ * @phpstan-return array<string, mixed>
323
+ */
324
+ public function term_metas_to_export( $keys ) {
325
+ // Add keys that have the `action` attribute set to `translate`.
326
+ foreach ( $this->xmls as $xml ) {
327
+ $fields = $xml->xpath( 'custom-term-fields/custom-term-field' );
328
+
329
+ if ( ! is_array( $fields ) ) {
330
+ // No custom fields.
331
+ continue;
332
+ }
333
+
334
+ foreach ( $fields as $field ) {
335
+ $action = $this->get_field_attribute( $field, 'action' );
336
+
337
+ if ( 'translate' !== $action ) {
338
+ continue;
339
+ }
340
+
341
+ $keys[ (string) $field ] = 1;
342
+ }
343
+ }
344
+
345
+ return $keys;
346
+ }
347
+
348
  /**
349
  * Language and translation management for custom post types.
350
  *
357
  public function translate_types( $types, $hide ) {
358
  foreach ( $this->xmls as $xml ) {
359
  $pts = $xml->xpath( 'custom-types/custom-type' );
360
+
361
+ if ( ! is_array( $pts ) ) {
362
+ continue;
363
+ }
364
+
365
+ foreach ( $pts as $pt ) {
366
+ $translate = $this->get_field_attribute( $pt, 'translate' );
367
+
368
+ if ( '1' === $translate && ! $hide ) {
369
+ $types[ (string) $pt ] = (string) $pt;
370
+ } else {
371
+ unset( $types[ (string) $pt ] ); // The theme/plugin author decided what to do with the post type so don't allow the user to change this
372
  }
373
  }
374
  }
375
+
376
  return $types;
377
  }
378
 
388
  public function translate_taxonomies( $taxonomies, $hide ) {
389
  foreach ( $this->xmls as $xml ) {
390
  $taxos = $xml->xpath( 'taxonomies/taxonomy' );
391
+
392
+ if ( ! is_array( $taxos ) ) {
393
+ continue;
394
+ }
395
+
396
+ foreach ( $taxos as $tax ) {
397
+ $translate = $this->get_field_attribute( $tax, 'translate' );
398
+
399
+ if ( '1' === $translate && ! $hide ) {
400
+ $taxonomies[ (string) $tax ] = (string) $tax;
401
+ } else {
402
+ unset( $taxonomies[ (string) $tax ] ); // the theme/plugin author decided what to do with the taxonomy so don't allow the user to change this
403
  }
404
  }
405
  }
406
+
407
  return $taxonomies;
408
  }
409
 
410
+ /**
411
+ * Translation management for strings in blocks content.
412
+ *
413
+ * @since 3.3
414
+ *
415
+ * @param string[][] $parsing_rules Rules as Xpath expressions to evaluate in the blocks content.
416
+ * @return string[][] Rules completed with ones from wpml-config file.
417
+ *
418
+ * @phpstan-param array<string,array<string>> $parsing_rules
419
+ * @phpstan-return array<string,array<string>>
420
+ */
421
+ public function translate_blocks( $parsing_rules ) {
422
+ return array_merge( $parsing_rules, $this->get_blocks_parsing_rules( 'xpath' ) );
423
+ }
424
+
425
+ /**
426
+ * Translation management for blocks attributes.
427
+ *
428
+ * @since 3.3
429
+ *
430
+ * @param string[][] $parsing_rules Rules for blocks attributes to translate.
431
+ * @return string[][] Rules completed with ones from wpml-config file.
432
+ *
433
+ * @phpstan-param array<string,array<string>> $parsing_rules
434
+ * @phpstan-return array<string,array<string>>
435
+ */
436
+ public function translate_blocks_attributes( $parsing_rules ) {
437
+ return array_merge( $parsing_rules, $this->get_blocks_parsing_rules( 'key' ) );
438
+ }
439
+
440
+ /**
441
+ * Returns rules to extract translatable strings from blocks.
442
+ *
443
+ * @since 3.3
444
+ *
445
+ * @param string $rule_tag Tag name to extract.
446
+ * @return string[][] The rules.
447
+ */
448
+ protected function get_blocks_parsing_rules( $rule_tag ) {
449
+
450
+ if ( null === $this->parsing_rules ) {
451
+ $this->parsing_rules = $this->extract_blocks_parsing_rules();
452
+ }
453
+
454
+ return isset( $this->parsing_rules[ $rule_tag ] ) ? $this->parsing_rules[ $rule_tag ] : array();
455
+ }
456
+
457
+ /**
458
+ * Extract all rules from WPML config file to translate strings for blocks.
459
+ *
460
+ * @since 3.3
461
+ *
462
+ * @return string[][][] Rules completed with ones from wpml-config file.
463
+ */
464
+ protected function extract_blocks_parsing_rules() {
465
+ $parsing_rules = array();
466
+
467
+ foreach ( $this->xmls as $xml ) {
468
+ $blocks = $xml->xpath( 'gutenberg-blocks/gutenberg-block' );
469
+
470
+ if ( ! is_array( $blocks ) ) {
471
+ continue;
472
+ }
473
+
474
+ foreach ( $blocks as $block ) {
475
+ $translate = $this->get_field_attribute( $block, 'translate' );
476
+
477
+ if ( '1' !== $translate ) {
478
+ continue;
479
+ }
480
+
481
+ $block_name = $this->get_field_attribute( $block, 'type' );
482
+
483
+ if ( '' === $block_name ) {
484
+ continue;
485
+ }
486
+
487
+ foreach ( $block->children() as $child ) {
488
+ $rule = '';
489
+ $child_tag = $child->getName();
490
+
491
+ switch ( $child_tag ) {
492
+ case 'xpath':
493
+ $rule = trim( (string) $child );
494
+ break;
495
+
496
+ case 'key':
497
+ $rule = $this->get_field_attribute( $child, 'name' );
498
+ break;
499
+ }
500
+
501
+ if ( '' !== $rule ) {
502
+ $parsing_rules[ $child_tag ][ $block_name ][] = $rule;
503
+ }
504
+ }
505
+ }
506
+ }
507
+
508
+ return $parsing_rules;
509
+ }
510
+
511
  /**
512
  * Registers or translates the strings for an option
513
  *
527
  * Recursively transforms xml nodes to an array, ready for PLL_Translate_Option.
528
  *
529
  * @since 2.9
530
+ * @since 3.3 Type-hinted the parameters `$key` and `$arr`.
531
+ * @since 3.3 `$arr` is not passed by reference anymore.
532
+ * @since 3.3 Added the parameter `$fill_value`.
533
  *
534
+ * @param SimpleXMLElement $key XML node.
535
+ * @param array $arr Array of option keys to translate.
536
+ * @param mixed $fill_value Value to use when filling entries. Default is true.
537
  * @return array
538
  */
539
+ protected function xml_to_array( SimpleXMLElement $key, array $arr = array(), $fill_value = true ) {
540
+ $name = $this->get_field_attribute( $key, 'name' );
541
+
542
+ if ( '' === $name ) {
543
+ return $arr;
544
+ }
545
+
546
  $children = $key->children();
547
 
548
  if ( count( $children ) ) {
549
  foreach ( $children as $child ) {
550
+ if ( ! isset( $arr[ $name ] ) || ! is_array( $arr[ $name ] ) ) {
551
+ $arr[ $name ] = array();
552
+ }
553
+
554
+ $arr[ $name ] = $this->xml_to_array( $child, $arr[ $name ], $fill_value );
555
  }
556
  } else {
557
+ $arr[ $name ] = $fill_value; // Multiline as in WPML.
558
  }
559
+
560
  return $arr;
561
  }
562
+
563
+ /**
564
+ * Get the value of an attribute.
565
+ *
566
+ * @since 3.3
567
+ *
568
+ * @param SimpleXMLElement $field A XML node.
569
+ * @param string $attribute_name Node of the attribute.
570
+ * @return string
571
+ */
572
+ private function get_field_attribute( SimpleXMLElement $field, $attribute_name ) {
573
+ $attributes = $field->attributes();
574
+
575
+ if ( empty( $attributes ) || ! isset( $attributes[ $attribute_name ] ) ) {
576
+ return '';
577
+ }
578
+
579
+ return trim( (string) $attributes[ $attribute_name ] );
580
+ }
581
+
582
+ /**
583
+ * Returns all wpml-config.xml files in MU plugins.
584
+ *
585
+ * @since 3.3
586
+ *
587
+ * @return string[] A context identifier as array key, a file path as array value.
588
+ *
589
+ * @phpstan-return array<string, string>
590
+ */
591
+ private function get_mu_plugin_files() {
592
+ if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
593
+ return array();
594
+ }
595
+
596
+ $files = array();
597
+
598
+ // Search for top level wpml-config.xml file.
599
+ $file_path = WPMU_PLUGIN_DIR . '/wpml-config.xml';
600
+
601
+ if ( file_exists( $file_path ) ) {
602
+ $files['mu-plugins'] = $file_path;
603
+ }
604
+
605
+ // Search in proxy loaded MU plugins.
606
+ foreach ( new DirectoryIterator( WPMU_PLUGIN_DIR ) as $file_info ) {
607
+ if ( $file_info->isDot() || ! $file_info->isDir() ) {
608
+ continue;
609
+ }
610
+
611
+ $file_path = $file_info->getPathname() . '/wpml-config.xml';
612
+
613
+ if ( file_exists( $file_path ) ) {
614
+ $files[ 'mu-plugins/' . $file_info->getFilename() ] = $file_path;
615
+ }
616
+ }
617
+
618
+ return $files;
619
+ }
620
+
621
+ /**
622
+ * Returns all wpml-config.xml files in plugins.
623
+ *
624
+ * @since 3.3
625
+ *
626
+ * @return string[] A context identifier as array key, a file path as array value.
627
+ *
628
+ * @phpstan-return array<string, string>
629
+ */
630
+ private function get_plugin_files() {
631
+ $files = array();
632
+ $plugins = array();
633
+
634
+ if ( is_multisite() ) {
635
+ // Don't forget sitewide active plugins thanks to Reactorshop http://wordpress.org/support/topic/polylang-and-yoast-seo-plugin/page/2?replies=38#post-4801829.
636
+ $sitewide_plugins = get_site_option( 'active_sitewide_plugins', array() );
637
+
638
+ if ( ! empty( $sitewide_plugins ) && is_array( $sitewide_plugins ) ) {
639
+ $plugins = array_keys( $sitewide_plugins );
640
+ }
641
+ }
642
+
643
+ // By-site plugins.
644
+ $active_plugins = get_option( 'active_plugins', array() );
645
+
646
+ if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
647
+ $plugins = array_merge( $plugins, $active_plugins );
648
+ }
649
+
650
+ $plugin_path = trailingslashit( WP_PLUGIN_DIR ) . '%s/wpml-config.xml';
651
+
652
+ foreach ( $plugins as $plugin ) {
653
+ if ( ! is_string( $plugin ) || '' === $plugin ) {
654
+ continue;
655
+ }
656
+
657
+ $file_dir = dirname( $plugin );
658
+ $file_path = sprintf( $plugin_path, $file_dir );
659
+
660
+ if ( file_exists( $file_path ) ) {
661
+ $files[ "plugins/{$file_dir}" ] = $file_path;
662
+ }
663
+ }
664
+
665
+ return $files;
666
+ }
667
+
668
+ /**
669
+ * Returns all wpml-config.xml files in theme and child theme.
670
+ *
671
+ * @since 3.3
672
+ *
673
+ * @return string[] A context identifier as array key, a file path as array value.
674
+ *
675
+ * @phpstan-return array<string, string>
676
+ */
677
+ private function get_theme_files() {
678
+ $files = array();
679
+
680
+ // Theme.
681
+ $template_path = get_template_directory();
682
+ $file_path = "{$template_path}/wpml-config.xml";
683
+
684
+ if ( file_exists( $file_path ) ) {
685
+ $files[ 'themes/' . get_template() ] = $file_path;
686
+ }
687
+
688
+ // Child theme.
689
+ $stylesheet_path = get_stylesheet_directory();
690
+ $file_path = "{$stylesheet_path}/wpml-config.xml";
691
+
692
+ if ( $stylesheet_path !== $template_path && file_exists( $file_path ) ) {
693
+ $files[ 'themes/' . get_stylesheet() ] = $file_path;
694
+ }
695
+
696
+ return $files;
697
+ }
698
+
699
+ /**
700
+ * Returns the wpml-config.xml file in Polylang custom directory.
701
+ *
702
+ * @since 3.3
703
+ *
704
+ * @return string[] A context identifier as array key, a file path as array value.
705
+ *
706
+ * @phpstan-return array<string, string>
707
+ */
708
+ private function get_custom_files() {
709
+ $file_path = PLL_LOCAL_DIR . '/wpml-config.xml';
710
+
711
+ if ( ! file_exists( $file_path ) ) {
712
+ return array();
713
+ }
714
+
715
+ return array(
716
+ 'Polylang' => $file_path,
717
+ );
718
+ }
719
  }
modules/wpml/wpml-legacy-api.php CHANGED
@@ -359,10 +359,13 @@ if ( ! function_exists( 'wpml_get_copied_fields_for_post_edit' ) ) {
359
 
360
  $arr = array( 'original_post_id' => (int) $_GET['from_post'] ); // phpcs:ignore WordPress.Security.NonceVerification
361
 
362
- // Don't know what WPML does but Polylang does copy all public meta keys by default
363
- foreach ( $keys = array_unique( array_keys( get_post_custom( $arr['original_post_id'] ) ) ) as $k => $meta_key ) {
364
- if ( is_protected_meta( $meta_key ) ) {
365
- unset( $keys[ $k ] );
 
 
 
366
  }
367
  }
368
 
359
 
360
  $arr = array( 'original_post_id' => (int) $_GET['from_post'] ); // phpcs:ignore WordPress.Security.NonceVerification
361
 
362
+ // Don't know what WPML does but Polylang does copy all public meta keys by default.
363
+ $keys = get_post_custom_keys( $arr['original_post_id'] );
364
+ if ( is_array( $keys ) ) {
365
+ foreach ( $keys as $k => $meta_key ) {
366
+ if ( is_protected_meta( $meta_key ) ) {
367
+ unset( $keys[ $k ] );
368
+ }
369
  }
370
  }
371
 
polylang.php CHANGED
@@ -10,8 +10,8 @@
10
  * Plugin Name: Polylang
11
  * Plugin URI: https://polylang.pro
12
  * Description: Adds multilingual capability to WordPress
13
- * Version: 3.2.8
14
- * Requires at least: 5.6
15
  * Requires PHP: 5.6
16
  * Author: WP SYNTEX
17
  * Author URI: https://polylang.pro
@@ -53,8 +53,8 @@ if ( defined( 'POLYLANG_VERSION' ) ) {
53
  }
54
  } else {
55
  // Go on loading the plugin
56
- define( 'POLYLANG_VERSION', '3.2.8' );
57
- define( 'PLL_MIN_WP_VERSION', '5.6' );
58
  define( 'PLL_MIN_PHP_VERSION', '5.6' );
59
 
60
  define( 'POLYLANG_FILE', __FILE__ );
10
  * Plugin Name: Polylang
11
  * Plugin URI: https://polylang.pro
12
  * Description: Adds multilingual capability to WordPress
13
+ * Version: 3.3
14
+ * Requires at least: 5.7
15
  * Requires PHP: 5.6
16
  * Author: WP SYNTEX
17
  * Author URI: https://polylang.pro
53
  }
54
  } else {
55
  // Go on loading the plugin
56
+ define( 'POLYLANG_VERSION', '3.3' );
57
+ define( 'PLL_MIN_WP_VERSION', '5.7' );
58
  define( 'PLL_MIN_PHP_VERSION', '5.6' );
59
 
60
  define( 'POLYLANG_FILE', __FILE__ );
readme.txt CHANGED
@@ -2,10 +2,10 @@
2
  Contributors: Chouby, manooweb, raaaahman, marianne38, sebastienserre, greglone, hugod
3
  Donate link: https://polylang.pro
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
- Requires at least: 5.6
6
  Tested up to: 6.1
7
  Requires PHP: 5.6
8
- Stable tag: 3.2.8
9
  License: GPLv3 or later
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
 
@@ -70,7 +70,7 @@ Wherever third party code has been used, credit has been given in the code’s c
70
 
71
  == Installation ==
72
 
73
- 1. Make sure you are using WordPress 5.6 or later and that your server is running PHP 5.6 or later (same requirement as WordPress itself).
74
  1. If you tried other multilingual plugins, deactivate them before activating Polylang, otherwise, you may get unexpected results!
75
  1. Install and activate the plugin as usual from the 'Plugins' menu in WordPress.
76
  1. The [setup wizard](https://polylang.pro/doc/setup-wizard/) is automatically launched to help you get started more easily with Polylang by configuring the main features.
@@ -103,6 +103,35 @@ Wherever third party code has been used, credit has been given in the code’s c
103
 
104
  == Changelog ==
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  = 3.2.8 (2022-10-17) =
107
 
108
  * Fix PHP warning when a filtered taxonomy has no query var #1124
2
  Contributors: Chouby, manooweb, raaaahman, marianne38, sebastienserre, greglone, hugod
3
  Donate link: https://polylang.pro
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
+ Requires at least: 5.7
6
  Tested up to: 6.1
7
  Requires PHP: 5.6
8
+ Stable tag: 3.3
9
  License: GPLv3 or later
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
 
70
 
71
  == Installation ==
72
 
73
+ 1. Make sure you are using WordPress 5.7 or later and that your server is running PHP 5.6 or later (same requirement as WordPress itself).
74
  1. If you tried other multilingual plugins, deactivate them before activating Polylang, otherwise, you may get unexpected results!
75
  1. Install and activate the plugin as usual from the 'Plugins' menu in WordPress.
76
  1. The [setup wizard](https://polylang.pro/doc/setup-wizard/) is automatically launched to help you get started more easily with Polylang by configuring the main features.
103
 
104
  == Changelog ==
105
 
106
+ = 3.3 (2022-11-28) =
107
+
108
+ * Requires WP 5.7 as minimum version
109
+ * Pro: Allow to export and import XLIFF files for posts
110
+ * Pro: Honor the provided context for the navigation language switcher block.
111
+ * Pro: Remove the parent hyperlink in the navigation language switcher block.
112
+ * Pro: Add spacing between flag and name in the navigation language switcher block.
113
+ * Pro: Disallow some special characters in translated slugs to avoid 404 errors.
114
+ * Pro: Fix string translation not imported when the original is registered but has never been saved in database.
115
+ * Pro: Fix string translation not imported when it includes an html entity.
116
+ * Pro: Fix navigation language switcher block rendering in block editor.
117
+ * Pro: Fix navigation language switcher may be displayed wrong color.
118
+ * Translate the post pages in get_post_type_archive_link() on admin side too. #1000
119
+ * Enable the block editor in page for posts translations to match the WordPress behavior since version 5.8 #1002
120
+ * Improve the site health report #1062 #1076
121
+ * Set the current language when saving a post #1065
122
+ * The search block is now filtered by language #1081
123
+ * Display slug of CPT and taxonomies in Custom post types and Taxonomies settings. Props @nicomollet #1112
124
+ * Add support for wpml-config.xml to MU plugins #1140 Props Jeremy Simkins
125
+ * Fix some deprecated notices fired by PHP 8.1 #975
126
+ * Fix some missing canonical redirect taxonomies #1074
127
+ * Fix redirect when permalink structure has no trailing slash #1080
128
+ * Fix language switcher in legacy navigation menu widget not rendered in widgets block editor #1083
129
+ * Fix language in tax query when an OR relation is used #1098
130
+ * Fix parent of translated category removed when assigning an untranslated parent #1105
131
+ * Fix is_front_page() when a static front page is not translated #1123
132
+ * Yoast SEO: Fix posts without language displayed in the sitemap #1103
133
+ * Yoast SEO: Avoid syncing robots meta. #1118
134
+
135
  = 3.2.8 (2022-10-17) =
136
 
137
  * Fix PHP warning when a filtered taxonomy has no query var #1124
settings/settings-browser.php CHANGED
@@ -24,7 +24,9 @@ class PLL_Settings_Browser extends PLL_Settings_Module {
24
  * @param object $polylang polylang object
25
  */
26
  public function __construct( &$polylang ) {
 
27
  $this->options = &$polylang->options;
 
28
  parent::__construct(
29
  $polylang,
30
  array(
24
  * @param object $polylang polylang object
25
  */
26
  public function __construct( &$polylang ) {
27
+ // Needed for `$this->is_available()`, which is used before calling the parent's constructor.
28
  $this->options = &$polylang->options;
29
+
30
  parent::__construct(
31
  $polylang,
32
  array(
settings/settings-cpt.php CHANGED
@@ -107,9 +107,16 @@ class PLL_Settings_CPT extends PLL_Settings_Module {
107
  printf(
108
  '<li><label><input name="post_types[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
109
  esc_attr( $post_type ),
110
- checked( in_array( $post_type, $this->options['post_types'] ) || $disabled, true, false ),
111
  disabled( $disabled, true, false ),
112
- esc_html( $pt->labels->name )
 
 
 
 
 
 
 
113
  );
114
  }
115
  }
@@ -131,9 +138,16 @@ class PLL_Settings_CPT extends PLL_Settings_Module {
131
  printf(
132
  '<li><label><input name="taxonomies[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
133
  esc_attr( $taxonomy ),
134
- checked( in_array( $taxonomy, $this->options['taxonomies'] ) || $disabled, true, false ),
135
  disabled( $disabled, true, false ),
136
- esc_html( $tax->labels->name )
 
 
 
 
 
 
 
137
  );
138
  }
139
  }
107
  printf(
108
  '<li><label><input name="post_types[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
109
  esc_attr( $post_type ),
110
+ checked( $disabled || in_array( $post_type, $this->options['post_types'], true ), true, false ),
111
  disabled( $disabled, true, false ),
112
+ esc_html(
113
+ sprintf(
114
+ /* translators: 1 is a post type or taxonomy label, 2 is a post type or taxonomy key. */
115
+ _x( '%1$s (%2$s)', 'content type setting choice', 'polylang' ),
116
+ $pt->labels->name,
117
+ $pt->name
118
+ )
119
+ )
120
  );
121
  }
122
  }
138
  printf(
139
  '<li><label><input name="taxonomies[%s]" type="checkbox" value="1" %s %s/> %s</label></li>',
140
  esc_attr( $taxonomy ),
141
+ checked( $disabled || in_array( $taxonomy, $this->options['taxonomies'], true ), true, false ),
142
  disabled( $disabled, true, false ),
143
+ esc_html(
144
+ sprintf(
145
+ /* translators: 1 is a post type or taxonomy label, 2 is a post type or taxonomy key. */
146
+ _x( '%1$s (%2$s)', 'content type setting choice', 'polylang' ),
147
+ $tax->labels->name,
148
+ $tax->name
149
+ )
150
+ )
151
  );
152
  }
153
  }
settings/settings-module.php CHANGED
@@ -46,7 +46,7 @@ class PLL_Settings_Module {
46
  * Stores the module name.
47
  * It must be unique.
48
  *
49
- * @var string
50
  */
51
  public $module;
52
 
46
  * Stores the module name.
47
  * It must be unique.
48
  *
49
+ * @var string|null
50
  */
51
  public $module;
52
 
settings/settings-url.php CHANGED
@@ -19,7 +19,7 @@ class PLL_Settings_Url extends PLL_Settings_Module {
19
  /**
20
  * The page id of the static front page.
21
  *
22
- * @var int
23
  */
24
  protected $page_on_front;
25
 
@@ -37,7 +37,6 @@ class PLL_Settings_Url extends PLL_Settings_Module {
37
  'module' => 'url',
38
  'title' => __( 'URL modifications', 'polylang' ),
39
  'description' => __( 'Decide how your URLs will look like.', 'polylang' ),
40
- 'configure' => true,
41
  )
42
  );
43
 
@@ -257,6 +256,7 @@ class PLL_Settings_Url extends PLL_Settings_Module {
257
  }
258
 
259
  if ( 3 == $options['force_lang'] && isset( $options['domains'] ) && is_array( $options['domains'] ) ) {
 
260
  foreach ( $options['domains'] as $key => $domain ) {
261
  if ( empty( $domain ) ) {
262
  $lang = $this->model->get_language( $key );
19
  /**
20
  * The page id of the static front page.
21
  *
22
+ * @var int|null
23
  */
24
  protected $page_on_front;
25
 
37
  'module' => 'url',
38
  'title' => __( 'URL modifications', 'polylang' ),
39
  'description' => __( 'Decide how your URLs will look like.', 'polylang' ),
 
40
  )
41
  );
42
 
256
  }
257
 
258
  if ( 3 == $options['force_lang'] && isset( $options['domains'] ) && is_array( $options['domains'] ) ) {
259
+ $newoptions['domains'] = array();
260
  foreach ( $options['domains'] as $key => $domain ) {
261
  if ( empty( $domain ) ) {
262
  $lang = $this->model->get_language( $key );
settings/settings.php CHANGED
@@ -18,14 +18,14 @@ class PLL_Settings extends PLL_Admin_Base {
18
  /**
19
  * Name of the active module.
20
  *
21
- * @var string
22
  */
23
  protected $active_tab;
24
 
25
  /**
26
  * Array of modules classes.
27
  *
28
- * @var PLL_Settings_Module[]
29
  */
30
  protected $modules;
31
 
18
  /**
19
  * Name of the active module.
20
  *
21
+ * @var string|null
22
  */
23
  protected $active_tab;
24
 
25
  /**
26
  * Array of modules classes.
27
  *
28
+ * @var PLL_Settings_Module[]|null
29
  */
30
  protected $modules;
31
 
settings/table-string.php CHANGED
@@ -125,8 +125,12 @@ class PLL_Table_String extends WP_List_Table {
125
  * @return string
126
  */
127
  public function column_translations( $item ) {
128
- $languages = array_combine( wp_list_pluck( $this->languages, 'slug' ), wp_list_pluck( $this->languages, 'name' ) );
129
- $out = '';
 
 
 
 
130
 
131
  foreach ( $item['translations'] as $key => $translation ) {
132
  $input_type = $item['multiline'] ?
@@ -382,7 +386,7 @@ class PLL_Table_String extends WP_List_Table {
382
  continue;
383
  }
384
 
385
- $translations = array_map( 'trim', wp_unslash( $_POST['translation'][ $language->slug ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
386
 
387
  $mo = new PLL_MO();
388
  $mo->import_from_db( $language );
125
  * @return string
126
  */
127
  public function column_translations( $item ) {
128
+ $out = '';
129
+ $languages = array();
130
+
131
+ foreach ( $this->languages as $language ) {
132
+ $languages[ $language->slug ] = $language->name;
133
+ }
134
 
135
  foreach ( $item['translations'] as $key => $translation ) {
136
  $input_type = $item['multiline'] ?
386
  continue;
387
  }
388
 
389
+ $translations = array_map( 'trim', (array) wp_unslash( $_POST['translation'][ $language->slug ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
390
 
391
  $mo = new PLL_MO();
392
  $mo->import_from_db( $language );
uninstall.php CHANGED
@@ -58,7 +58,7 @@ class PLL_Uninstall {
58
  register_taxonomy( $taxonomy, null, array( 'label' => false, 'public' => false, 'query_var' => false, 'rewrite' => false ) );
59
  }
60
 
61
- $languages = get_terms( 'language', array( 'hide_empty' => false ) );
62
 
63
  // Delete users options
64
  foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
@@ -103,7 +103,7 @@ class PLL_Uninstall {
103
  $term_ids = array();
104
  $tt_ids = array();
105
 
106
- foreach ( get_terms( $pll_taxonomies, array( 'hide_empty' => false ) ) as $term ) {
107
  $term_ids[] = (int) $term->term_id;
108
  $tt_ids[] = (int) $term->term_taxonomy_id;
109
  }
58
  register_taxonomy( $taxonomy, null, array( 'label' => false, 'public' => false, 'query_var' => false, 'rewrite' => false ) );
59
  }
60
 
61
+ $languages = get_terms( array( 'taxonomy' => 'language', 'hide_empty' => false ) );
62
 
63
  // Delete users options
64
  foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
103
  $term_ids = array();
104
  $tt_ids = array();
105
 
106
+ foreach ( get_terms( array( 'taxonomy' => $pll_taxonomies, 'hide_empty' => false ) ) as $term ) {
107
  $term_ids[] = (int) $term->term_id;
108
  $tt_ids[] = (int) $term->term_taxonomy_id;
109
  }
vendor/composer/InstalledVersions.php ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer;
14
+
15
+ use Composer\Autoload\ClassLoader;
16
+ use Composer\Semver\VersionParser;
17
+
18
+ /**
19
+ * This class is copied in every Composer installed project and available to all
20
+ *
21
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
22
+ *
23
+ * To require its presence, you can require `composer-runtime-api ^2.0`
24
+ *
25
+ * @final
26
+ */
27
+ class InstalledVersions
28
+ {
29
+ /**
30
+ * @var mixed[]|null
31
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
32
+ */
33
+ private static $installed;
34
+
35
+ /**
36
+ * @var bool|null
37
+ */
38
+ private static $canGetVendors;
39
+
40
+ /**
41
+ * @var array[]
42
+ * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
43
+ */
44
+ private static $installedByVendor = array();
45
+
46
+ /**
47
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
48
+ *
49
+ * @return string[]
50
+ * @psalm-return list<string>
51
+ */
52
+ public static function getInstalledPackages()
53
+ {
54
+ $packages = array();
55
+ foreach (self::getInstalled() as $installed) {
56
+ $packages[] = array_keys($installed['versions']);
57
+ }
58
+
59
+ if (1 === \count($packages)) {
60
+ return $packages[0];
61
+ }
62
+
63
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
64
+ }
65
+
66
+ /**
67
+ * Returns a list of all package names with a specific type e.g. 'library'
68
+ *
69
+ * @param string $type
70
+ * @return string[]
71
+ * @psalm-return list<string>
72
+ */
73
+ public static function getInstalledPackagesByType($type)
74
+ {
75
+ $packagesByType = array();
76
+
77
+ foreach (self::getInstalled() as $installed) {
78
+ foreach ($installed['versions'] as $name => $package) {
79
+ if (isset($package['type']) && $package['type'] === $type) {
80
+ $packagesByType[] = $name;
81
+ }
82
+ }
83
+ }
84
+
85
+ return $packagesByType;
86
+ }
87
+
88
+ /**
89
+ * Checks whether the given package is installed
90
+ *
91
+ * This also returns true if the package name is provided or replaced by another package
92
+ *
93
+ * @param string $packageName
94
+ * @param bool $includeDevRequirements
95
+ * @return bool
96
+ */
97
+ public static function isInstalled($packageName, $includeDevRequirements = true)
98
+ {
99
+ foreach (self::getInstalled() as $installed) {
100
+ if (isset($installed['versions'][$packageName])) {
101
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
102
+ }
103
+ }
104
+
105
+ return false;
106
+ }
107
+
108
+ /**
109
+ * Checks whether the given package satisfies a version constraint
110
+ *
111
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
112
+ *
113
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
114
+ *
115
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
116
+ * @param string $packageName
117
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
118
+ * @return bool
119
+ */
120
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
121
+ {
122
+ $constraint = $parser->parseConstraints($constraint);
123
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
124
+
125
+ return $provided->matches($constraint);
126
+ }
127
+
128
+ /**
129
+ * Returns a version constraint representing all the range(s) which are installed for a given package
130
+ *
131
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
132
+ * whether a given version of a package is installed, and not just whether it exists
133
+ *
134
+ * @param string $packageName
135
+ * @return string Version constraint usable with composer/semver
136
+ */
137
+ public static function getVersionRanges($packageName)
138
+ {
139
+ foreach (self::getInstalled() as $installed) {
140
+ if (!isset($installed['versions'][$packageName])) {
141
+ continue;
142
+ }
143
+
144
+ $ranges = array();
145
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
146
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
147
+ }
148
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
149
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
150
+ }
151
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
152
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
153
+ }
154
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
155
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
156
+ }
157
+
158
+ return implode(' || ', $ranges);
159
+ }
160
+
161
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
162
+ }
163
+
164
+ /**
165
+ * @param string $packageName
166
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
167
+ */
168
+ public static function getVersion($packageName)
169
+ {
170
+ foreach (self::getInstalled() as $installed) {
171
+ if (!isset($installed['versions'][$packageName])) {
172
+ continue;
173
+ }
174
+
175
+ if (!isset($installed['versions'][$packageName]['version'])) {
176
+ return null;
177
+ }
178
+
179
+ return $installed['versions'][$packageName]['version'];
180
+ }
181
+
182
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
183
+ }
184
+
185
+ /**
186
+ * @param string $packageName
187
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
188
+ */
189
+ public static function getPrettyVersion($packageName)
190
+ {
191
+ foreach (self::getInstalled() as $installed) {
192
+ if (!isset($installed['versions'][$packageName])) {
193
+ continue;
194
+ }
195
+
196
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
197
+ return null;
198
+ }
199
+
200
+ return $installed['versions'][$packageName]['pretty_version'];
201
+ }
202
+
203
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
204
+ }
205
+
206
+ /**
207
+ * @param string $packageName
208
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
209
+ */
210
+ public static function getReference($packageName)
211
+ {
212
+ foreach (self::getInstalled() as $installed) {
213
+ if (!isset($installed['versions'][$packageName])) {
214
+ continue;
215
+ }
216
+
217
+ if (!isset($installed['versions'][$packageName]['reference'])) {
218
+ return null;
219
+ }
220
+
221
+ return $installed['versions'][$packageName]['reference'];
222
+ }
223
+
224
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
225
+ }
226
+
227
+ /**
228
+ * @param string $packageName
229
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
230
+ */
231
+ public static function getInstallPath($packageName)
232
+ {
233
+ foreach (self::getInstalled() as $installed) {
234
+ if (!isset($installed['versions'][$packageName])) {
235
+ continue;
236
+ }
237
+
238
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
239
+ }
240
+
241
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
242
+ }
243
+
244
+ /**
245
+ * @return array
246
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
247
+ */
248
+ public static function getRootPackage()
249
+ {
250
+ $installed = self::getInstalled();
251
+
252
+ return $installed[0]['root'];
253
+ }
254
+
255
+ /**
256
+ * Returns the raw installed.php data for custom implementations
257
+ *
258
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
259
+ * @return array[]
260
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
261
+ */
262
+ public static function getRawData()
263
+ {
264
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
265
+
266
+ if (null === self::$installed) {
267
+ // only require the installed.php file if this file is loaded from its dumped location,
268
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
269
+ if (substr(__DIR__, -8, 1) !== 'C') {
270
+ self::$installed = include __DIR__ . '/installed.php';
271
+ } else {
272
+ self::$installed = array();
273
+ }
274
+ }
275
+
276
+ return self::$installed;
277
+ }
278
+
279
+ /**
280
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
281
+ *
282
+ * @return array[]
283
+ * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
284
+ */
285
+ public static function getAllRawData()
286
+ {
287
+ return self::getInstalled();
288
+ }
289
+
290
+ /**
291
+ * Lets you reload the static array from another file
292
+ *
293
+ * This is only useful for complex integrations in which a project needs to use
294
+ * this class but then also needs to execute another project's autoloader in process,
295
+ * and wants to ensure both projects have access to their version of installed.php.
296
+ *
297
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
298
+ * the data it needs from this class, then call reload() with
299
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
300
+ * the project in which it runs can then also use this class safely, without
301
+ * interference between PHPUnit's dependencies and the project's dependencies.
302
+ *
303
+ * @param array[] $data A vendor/composer/installed.php data set
304
+ * @return void
305
+ *
306
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
307
+ */
308
+ public static function reload($data)
309
+ {
310
+ self::$installed = $data;
311
+ self::$installedByVendor = array();
312
+ }
313
+
314
+ /**
315
+ * @return array[]
316
+ * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
317
+ */
318
+ private static function getInstalled()
319
+ {
320
+ if (null === self::$canGetVendors) {
321
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
322
+ }
323
+
324
+ $installed = array();
325
+
326
+ if (self::$canGetVendors) {
327
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
328
+ if (isset(self::$installedByVendor[$vendorDir])) {
329
+ $installed[] = self::$installedByVendor[$vendorDir];
330
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
331
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
332
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
333
+ self::$installed = $installed[count($installed) - 1];
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ if (null === self::$installed) {
340
+ // only require the installed.php file if this file is loaded from its dumped location,
341
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
342
+ if (substr(__DIR__, -8, 1) !== 'C') {
343
+ self::$installed = require __DIR__ . '/installed.php';
344
+ } else {
345
+ self::$installed = array();
346
+ }
347
+ }
348
+ $installed[] = self::$installed;
349
+
350
+ return $installed;
351
+ }
352
+ }
vendor/composer/autoload_classmap.php CHANGED
@@ -37,6 +37,7 @@ return array(
37
  'PLL_CRUD_Terms' => $baseDir . '/include/crud-terms.php',
38
  'PLL_Cache' => $baseDir . '/include/cache.php',
39
  'PLL_Cache_Compat' => $baseDir . '/integrations/cache/cache-compat.php',
 
40
  'PLL_Cft' => $baseDir . '/integrations/custom-field-template/cft.php',
41
  'PLL_Choose_Lang' => $baseDir . '/frontend/choose-lang.php',
42
  'PLL_Choose_Lang_Content' => $baseDir . '/frontend/choose-lang-content.php',
37
  'PLL_CRUD_Terms' => $baseDir . '/include/crud-terms.php',
38
  'PLL_Cache' => $baseDir . '/include/cache.php',
39
  'PLL_Cache_Compat' => $baseDir . '/integrations/cache/cache-compat.php',
40
+ 'PLL_Canonical' => $baseDir . '/frontend/canonical.php',
41
  'PLL_Cft' => $baseDir . '/integrations/custom-field-template/cft.php',
42
  'PLL_Choose_Lang' => $baseDir . '/frontend/choose-lang.php',
43
  'PLL_Choose_Lang_Content' => $baseDir . '/frontend/choose-lang-content.php',
vendor/composer/autoload_static.php CHANGED
@@ -38,6 +38,7 @@ class ComposerStaticInit57e007bdf76a1fe336cb43b59389545b
38
  'PLL_CRUD_Terms' => __DIR__ . '/../..' . '/include/crud-terms.php',
39
  'PLL_Cache' => __DIR__ . '/../..' . '/include/cache.php',
40
  'PLL_Cache_Compat' => __DIR__ . '/../..' . '/integrations/cache/cache-compat.php',
 
41
  'PLL_Cft' => __DIR__ . '/../..' . '/integrations/custom-field-template/cft.php',
42
  'PLL_Choose_Lang' => __DIR__ . '/../..' . '/frontend/choose-lang.php',
43
  'PLL_Choose_Lang_Content' => __DIR__ . '/../..' . '/frontend/choose-lang-content.php',
38
  'PLL_CRUD_Terms' => __DIR__ . '/../..' . '/include/crud-terms.php',
39
  'PLL_Cache' => __DIR__ . '/../..' . '/include/cache.php',
40
  'PLL_Cache_Compat' => __DIR__ . '/../..' . '/integrations/cache/cache-compat.php',
41
+ 'PLL_Canonical' => __DIR__ . '/../..' . '/frontend/canonical.php',
42
  'PLL_Cft' => __DIR__ . '/../..' . '/integrations/custom-field-template/cft.php',
43
  'PLL_Choose_Lang' => __DIR__ . '/../..' . '/frontend/choose-lang.php',
44
  'PLL_Choose_Lang_Content' => __DIR__ . '/../..' . '/frontend/choose-lang-content.php',
vendor/composer/installed.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array(
2
+ 'root' => array(
3
+ 'name' => 'wpsyntex/polylang',
4
+ 'pretty_version' => '3.2.x-dev',
5
+ 'version' => '3.2.9999999.9999999-dev',
6
+ 'reference' => '9555976eb2dc703519184632f83fa6114d3a9ce3',
7
+ 'type' => 'wordpress-plugin',
8
+ 'install_path' => __DIR__ . '/../../',
9
+ 'aliases' => array(),
10
+ 'dev' => false,
11
+ ),
12
+ 'versions' => array(
13
+ 'wpsyntex/polylang' => array(
14
+ 'pretty_version' => '3.2.x-dev',
15
+ 'version' => '3.2.9999999.9999999-dev',
16
+ 'reference' => '9555976eb2dc703519184632f83fa6114d3a9ce3',
17
+ 'type' => 'wordpress-plugin',
18
+ 'install_path' => __DIR__ . '/../../',
19
+ 'aliases' => array(),
20
+ 'dev_requirement' => false,
21
+ ),
22
+ ),
23
+ );
vendor/composer/platform_check.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // platform_check.php @generated by Composer
4
+
5
+ $issues = array();
6
+
7
+ if (!(PHP_VERSION_ID >= 50600)) {
8
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
9
+ }
10
+
11
+ if ($issues) {
12
+ if (!headers_sent()) {
13
+ header('HTTP/1.1 500 Internal Server Error');
14
+ }
15
+ if (!ini_get('display_errors')) {
16
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
17
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
18
+ } elseif (!headers_sent()) {
19
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
20
+ }
21
+ }
22
+ trigger_error(
23
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
24
+ E_USER_ERROR
25
+ );
26
+ }