Polylang - Version 3.2

Version Description

(2022-04-12) =

  • Requires WP 5.6 as minimum version
  • Pro: Add compatibility with the full site editing introduced in WP 5.9
  • Pro: Add a language switcher block for the navigation block introduced in WP 5.9
  • Pro: Add compatibility with the new gallery block introduced in WP 5.9
  • Pro: Make the language switcher block available in the widget section of the customizer
  • Pro: Fix wrong category when translating the latest block
  • Pro: Fix the language switcher block when using the dropdown option
  • Pro: Fix some edge cases with locale fallback
  • Pro: Fix post template replacing the post content when duplicating a post
  • Pro: Fix synchronization groups not correctly cleaned up when a language is deleted
  • Pro: Fix incorrect sticky property when duplicating / synchronizing posts
  • Pro: Fix "Page for posts" label after the page has been bulk translated
  • Pro: Fix translated slug when the url includes a query string
  • Pro: Synchronize ACF layout fields if a child field is synchronized or translatable
  • Pro: Fix wrong field group translation displayed when using object cache with ACF
  • Update plugin updater to 1.9.1
  • Add compatibility with the block site title introduced in WP 5.9
  • Add the list of wpml-config.xml files in the site health information
  • Improve the performance of the get_pages() filter #980
  • Improve the compatibility of 'wpml_object_id' with the original filter #972
  • Prevent term_exists to be filtered by language in WP 6.0
  • Fix some PHP 8.1 deprecations #949 #985
  • Fix a fatal error in PHP 8.1 #987
  • Fix category feed not redirected when the langage code is wrong #887
  • Fix default category not created for secondary languages (introduced in 3.1) #997
  • Fix parent page when the parent post type is not translatable #1001
  • Fix the Yoast SEO breadcrumb when it includes a non-synchronized taxonomy #1005
  • Fix a PHP Notice when adding a new language and Yoast SEO is active #979
  • Fix a PHP warning in Yoast SEO compatibility #954
Download this release

Release Info

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

Code changes from version 3.1.4 to 3.2

Files changed (46) hide show
  1. admin/admin-base.php +43 -3
  2. admin/admin-filters-post.php +1 -1
  3. admin/admin-model.php +34 -3
  4. admin/admin-static-pages.php +3 -3
  5. css/build/dialog.min.css +1 -1
  6. css/build/selectmenu.min.css +1 -1
  7. frontend/choose-lang-url.php +1 -1
  8. frontend/frontend-filters-links.php +37 -15
  9. frontend/frontend.php +24 -0
  10. include/base.php +26 -0
  11. include/class-polylang.php +3 -3
  12. include/crud-posts.php +3 -1
  13. include/db-tools.php +47 -0
  14. include/filters-links.php +3 -1
  15. include/filters.php +70 -24
  16. include/license.php +8 -8
  17. include/links-directory.php +2 -2
  18. include/model.php +12 -8
  19. include/olt-manager.php +1 -1
  20. include/rest-request.php +39 -18
  21. include/switcher.php +5 -7
  22. include/translated-object.php +267 -146
  23. include/translated-post.php +43 -9
  24. include/translated-term.php +42 -21
  25. include/walker-dropdown.php +3 -3
  26. include/widget-languages.php +1 -1
  27. install/plugin-updater.php +257 -233
  28. integrations/integrations.php +1 -1
  29. integrations/wp-importer/wp-import.php +7 -8
  30. integrations/wpseo/wpseo.php +22 -30
  31. modules/site-health/admin-site-health.php +43 -5
  32. modules/sync/sync-metas.php +0 -3
  33. modules/sync/sync-post-metas.php +7 -5
  34. modules/sync/sync-tax.php +2 -1
  35. modules/wpml/wpml-api.php +17 -6
  36. modules/wpml/wpml-config.php +21 -24
  37. modules/wpml/wpml-legacy-api.php +41 -14
  38. polylang.php +4 -4
  39. readme.txt +77 -26
  40. settings/settings-module.php +3 -2
  41. settings/settings.php +2 -1
  42. settings/table-languages.php +6 -6
  43. settings/table-string.php +4 -4
  44. settings/view-tab-strings.php +1 -1
  45. vendor/composer/autoload_classmap.php +1 -0
  46. vendor/composer/autoload_static.php +1 -0
admin/admin-base.php CHANGED
@@ -68,6 +68,8 @@ abstract class PLL_Admin_Base extends PLL_Base {
68
  // Adds the link to the languages panel in the WordPress admin menu
69
  add_action( 'admin_menu', array( $this, 'add_menus' ) );
70
 
 
 
71
  // Setup js scripts and css styles
72
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
73
  add_action( 'admin_print_footer_scripts', array( $this, 'admin_print_footer_scripts' ), 0 ); // High priority in case an ajax request is sent by an immediately invoked function
@@ -86,6 +88,9 @@ abstract class PLL_Admin_Base extends PLL_Base {
86
 
87
  $this->notices = new PLL_Admin_Notices( $this );
88
 
 
 
 
89
  if ( ! $this->model->get_languages_list() ) {
90
  return;
91
  }
@@ -93,8 +98,6 @@ abstract class PLL_Admin_Base extends PLL_Base {
93
  $this->links = new PLL_Admin_Links( $this ); // FIXME needed here ?
94
  $this->static_pages = new PLL_Admin_Static_Pages( $this ); // FIXME needed here ?
95
  $this->filters_links = new PLL_Filters_Links( $this ); // FIXME needed here ?
96
- $this->default_term = new PLL_Admin_Default_Term( $this );
97
- $this->default_term->add_hooks();
98
 
99
  // Filter admin language for users
100
  // We must not call user info before WordPress defines user roles in wp-settings.php
@@ -366,8 +369,18 @@ abstract class PLL_Admin_Base extends PLL_Base {
366
  $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
367
  }
368
 
 
 
 
 
 
 
 
 
 
 
369
  // Inform that the admin language has been set.
370
- if ( $this->curlang ) {
371
  /** This action is documented in frontend/choose-lang.php */
372
  do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
373
  } else {
@@ -497,4 +510,31 @@ abstract class PLL_Admin_Base extends PLL_Base {
497
  );
498
  }
499
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  }
68
  // Adds the link to the languages panel in the WordPress admin menu
69
  add_action( 'admin_menu', array( $this, 'add_menus' ) );
70
 
71
+ add_action( 'admin_menu', array( $this, 'remove_customize_submenu' ) );
72
+
73
  // Setup js scripts and css styles
74
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
75
  add_action( 'admin_print_footer_scripts', array( $this, 'admin_print_footer_scripts' ), 0 ); // High priority in case an ajax request is sent by an immediately invoked function
88
 
89
  $this->notices = new PLL_Admin_Notices( $this );
90
 
91
+ $this->default_term = new PLL_Admin_Default_Term( $this );
92
+ $this->default_term->add_hooks();
93
+
94
  if ( ! $this->model->get_languages_list() ) {
95
  return;
96
  }
98
  $this->links = new PLL_Admin_Links( $this ); // FIXME needed here ?
99
  $this->static_pages = new PLL_Admin_Static_Pages( $this ); // FIXME needed here ?
100
  $this->filters_links = new PLL_Filters_Links( $this ); // FIXME needed here ?
 
 
101
 
102
  // Filter admin language for users
103
  // We must not call user info before WordPress defines user roles in wp-settings.php
369
  $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
370
  }
371
 
372
+ /**
373
+ * Filters the current language used by Polylang in the admin context.
374
+ *
375
+ * @since 3.2
376
+ *
377
+ * @param PLL_Language|false|null $curlang Instance of the current language.
378
+ * @param PLL_Admin_Base $polylang Instance of the main Polylang's object.
379
+ */
380
+ $this->curlang = apply_filters( 'pll_admin_current_language', $this->curlang, $this );
381
+
382
  // Inform that the admin language has been set.
383
+ if ( $this->curlang instanceof PLL_Language ) {
384
  /** This action is documented in frontend/choose-lang.php */
385
  do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
386
  } else {
510
  );
511
  }
512
  }
513
+
514
+ /**
515
+ * Remove the customize submenu when using a block theme.
516
+ *
517
+ * WordPress removes the Customizer menu if a block theme is activated and no other plugins interact with it.
518
+ * As Polylang interacts with the Customizer, we have to delete this menu ourselves in the case of a block theme,
519
+ * unless another plugin than Polylang interacts with the Customizer.
520
+ *
521
+ * @since 3.2
522
+ *
523
+ * @return void
524
+ */
525
+ public function remove_customize_submenu() {
526
+ if ( ! $this->should_customize_menu_be_removed() ) {
527
+ return;
528
+ }
529
+
530
+ global $submenu;
531
+
532
+ if ( ! empty( $submenu['themes.php'] ) ) {
533
+ foreach ( $submenu['themes.php'] as $submenu_item ) {
534
+ if ( 'customize' === $submenu_item[1] ) {
535
+ remove_submenu_page( 'themes.php', $submenu_item[2] );
536
+ }
537
+ }
538
+ }
539
+ }
540
  }
admin/admin-filters-post.php CHANGED
@@ -59,7 +59,7 @@ class PLL_Admin_Filters_Post extends PLL_Admin_Filters_Post_Base {
59
  }
60
 
61
  // Hierarchical taxonomies
62
- if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'object' ) ) {
63
  // Get translated hierarchical taxonomies
64
  $hierarchical_taxonomies = array();
65
  foreach ( $taxonomies as $taxonomy ) {
59
  }
60
 
61
  // Hierarchical taxonomies
62
+ if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'objects' ) ) {
63
  // Get translated hierarchical taxonomies
64
  $hierarchical_taxonomies = array();
65
  foreach ( $taxonomies as $taxonomy ) {
admin/admin-model.php CHANGED
@@ -238,13 +238,25 @@ class PLL_Admin_Model extends PLL_Model {
238
  }
239
 
240
  /**
241
- * Fires when a language is updated.
242
  *
243
  * @since 1.9
 
244
  *
245
- * @param array $args Arguments used to modify the language. @see PLL_Admin_Model::update_language().
 
 
 
 
 
 
 
 
 
 
 
246
  */
247
- do_action( 'pll_update_language', $args );
248
 
249
  $this->clean_languages_cache();
250
  flush_rewrite_rules(); // Refresh rewrite rules
@@ -454,6 +466,25 @@ class PLL_Admin_Model extends PLL_Model {
454
  foreach ( $terms as $term ) {
455
  $term_ids[ $term->taxonomy ][] = $term->term_id;
456
  $tr = maybe_unserialize( $term->description );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  if ( ! empty( $tr[ $old_slug ] ) ) {
458
  if ( $new_slug ) {
459
  $tr[ $new_slug ] = $tr[ $old_slug ]; // Suppress this for delete
238
  }
239
 
240
  /**
241
+ * Fires after a language is updated.
242
  *
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).
250
+ * @type string $slug Language code (ideally 2-letters ISO 639-1 language code).
251
+ * @type string $locale WordPress locale.
252
+ * @type int $rtl 1 if rtl language, 0 otherwise.
253
+ * @type int $term_group Language order when displayed.
254
+ * @type string $no_default_cat Optional, if set, no default category has been created for this language.
255
+ * @type string $flag Optional, country code, @see flags.php.
256
+ * }
257
+ * @param PLL_Language $lang Previous value of the language beeing edited.
258
  */
259
+ do_action( 'pll_update_language', $args, $lang );
260
 
261
  $this->clean_languages_cache();
262
  flush_rewrite_rules(); // Refresh rewrite rules
466
  foreach ( $terms as $term ) {
467
  $term_ids[ $term->taxonomy ][] = $term->term_id;
468
  $tr = maybe_unserialize( $term->description );
469
+
470
+ /**
471
+ * Filters the unserialized translation group description before it is
472
+ * updated when a language is deleted or a language slug is changed.
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
+
488
  if ( ! empty( $tr[ $old_slug ] ) ) {
489
  if ( $new_slug ) {
490
  $tr[ $new_slug ] = $tr[ $old_slug ]; // Suppress this for delete
admin/admin-static-pages.php CHANGED
@@ -33,7 +33,7 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
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
 
36
- // Refresh language cache when a static front page has been translated
37
  add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
38
 
39
  // Prevents WP resetting the option
@@ -106,7 +106,7 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
106
  }
107
 
108
  /**
109
- * Refreshes the language cache when a static front page has been translated.
110
  *
111
  * @since 1.8
112
  *
@@ -116,7 +116,7 @@ class PLL_Admin_Static_Pages extends PLL_Static_Pages {
116
  * @return void
117
  */
118
  public function pll_save_post( $post_id, $post, $translations ) {
119
- if ( in_array( $this->page_on_front, $translations ) ) {
120
  $this->model->clean_languages_cache();
121
  }
122
  }
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
 
36
+ // Refreshes the language cache when a static front page or page for for posts has been translated.
37
  add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
38
 
39
  // Prevents WP resetting the option
106
  }
107
 
108
  /**
109
+ * Refreshes the language cache when a static front page or page for for posts has been translated.
110
  *
111
  * @since 1.8
112
  *
116
  * @return void
117
  */
118
  public function pll_save_post( $post_id, $post, $translations ) {
119
+ if ( in_array( $this->page_on_front, $translations ) || in_array( $this->page_for_posts, $translations ) ) {
120
  $this->model->clean_languages_cache();
121
  }
122
  }
css/build/dialog.min.css CHANGED
@@ -1 +1 @@
1
- .pll-confirmation-modal.ui-widget,.pll-confirmation-modal .ui-widget,.pll-confirmation-modal.ui-widget .ui-widget{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px}.pll-confirmation-modal.ui-dialog{background:#fff;border:0;border-radius:0;color:#444;padding:0;z-index:100102}.ui-dialog.pll-confirmation-modal .ui-dialog-titlebar{background:#fcfcfc;border:0;border-bottom:1px solid #dfdfdf;border-radius:0;color:#444;font-size:18px;font-weight:600;height:36px;line-height:2;padding:0 36px 0 16px;position:static}.ui-dialog.pll-confirmation-modal .ui-dialog-title{float:none;margin:0;width:auto}.pll-confirmation-modal .ui-widget-header .ui-icon{background:none;position:static}.pll-confirmation-modal .ui-button.ui-dialog-titlebar-close{background:none;border:0;height:36px;margin:0;padding:0;right:0;top:0;width:36px}.ui-dialog.pll-confirmation-modal .ui-dialog-content{border:0;box-sizing:border-box;color:#444;padding:16px;position:static}.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane{background:#fcfcfc;border:0;border-top:1px solid #dfdfdf;margin:0;padding:16px}.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane .ui-button{background:#f7f7f7;border:1px solid #ccc;border-radius:3px;line-height:2;margin:0 0 0 16px;padding:0 10px 1px;position:static;vertical-align:top}.ui-dialog.pll-confirmation-modal .ui-button:focus,.ui-dialog.pll-confirmation-modal .ui-button:hover{background:#fafafa;border-color:#999;color:#23282d}.pll-confirmation-modal+.ui-widget-overlay{background:#000;opacity:.7;z-index:100101}
1
+ .pll-confirmation-modal .ui-widget,.pll-confirmation-modal.ui-widget,.pll-confirmation-modal.ui-widget .ui-widget{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px}.pll-confirmation-modal.ui-dialog{background:#fff;border:0;border-radius:0;color:#444;padding:0;z-index:100102}.ui-dialog.pll-confirmation-modal .ui-dialog-titlebar{background:#fcfcfc;border:0;border-bottom:1px solid #dfdfdf;border-radius:0;color:#444;font-size:18px;font-weight:600;height:36px;line-height:2;padding:0 36px 0 16px;position:static}.ui-dialog.pll-confirmation-modal .ui-dialog-title{float:none;margin:0;width:auto}.pll-confirmation-modal .ui-widget-header .ui-icon{background:none;position:static}.pll-confirmation-modal .ui-button.ui-dialog-titlebar-close{background:none;border:0;height:36px;margin:0;padding:0;right:0;top:0;width:36px}.ui-dialog.pll-confirmation-modal .ui-dialog-content{border:0;box-sizing:border-box;color:#444;padding:16px;position:static}.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane{background:#fcfcfc;border:0;border-top:1px solid #dfdfdf;margin:0;padding:16px}.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane .ui-button{background:#f7f7f7;border:1px solid #ccc;border-radius:3px;line-height:2;margin:0 0 0 16px;padding:0 10px 1px;position:static;vertical-align:top}.ui-dialog.pll-confirmation-modal .ui-button:focus,.ui-dialog.pll-confirmation-modal .ui-button:hover{background:#fafafa;border-color:#999;color:#23282d}.pll-confirmation-modal+.ui-widget-overlay{background:#000;opacity:.7;z-index:100101}
css/build/selectmenu.min.css CHANGED
@@ -1 +1 @@
1
- .ui-widget-overlay{height:100%;left:0;position:fixed;top:0;width:100%}.ui-menu{display:block;list-style:none;margin:0;outline:none;padding:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");margin:0;min-height:0;padding:3px 1em 3px .4em;position:relative}.ui-menu .ui-menu-item:not([role]){padding:0}.ui-menu-item-wrapper{padding:3px 1em 3px 2em}.rtl .ui-menu .ui-menu-item{text-align:right}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item[role]{padding-left:2em}.rtl .ui-menu-icons .ui-menu-item[role],.rtl .ui-menu-item-wrapper{padding-left:1em;padding-right:2em}.ui-menu .ui-icon,.ui-selectmenu-text .ui-icon{bottom:0;left:.3em;margin:auto 0;position:absolute;top:0}.rtl .ui-menu .ui-icon,.rtl .ui-selectmenu-text .ui-icon{left:auto;right:.3em}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-selectmenu-menu{display:none;left:0;margin:0;padding:0;position:absolute;top:0}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{border:0;font-size:1em;font-weight:700;height:auto;line-height:23px;margin:.5em 0 0;padding:2px .4em}.ui-selectmenu-open{display:block}.ui-selectmenu-button,.ui-selectmenu-button.ui-button{box-sizing:border-box;display:inline-block;height:28px;line-height:normal;overflow:hidden;padding:0;position:relative;text-align:left;text-decoration:none;vertical-align:top;white-space:nowrap}.ui-selectmenu-button span.ui-icon{background:none;height:16px;left:auto;position:absolute;right:.5em;text-indent:0;top:26%;width:16px}.rtl .ui-selectmenu-button span.ui-icon{left:.5em;right:auto}.ui-selectmenu-button.ui-widget span.ui-selectmenu-text,.ui-selectmenu-button span.ui-selectmenu-text{display:block;line-height:23px;margin:0;overflow:hidden;padding:.1em 2.1em .2em 2em;text-align:left;text-overflow:ellipsis;white-space:nowrap}.rtl .ui-selectmenu-button span.ui-selectmenu-text{padding:.2em 2em .2em 2.1em;text-align:right}.ui-button.ui-selectmenu-button-closed,.ui-button.ui-selectmenu-button-open,.ui-selectmenu-button.ui-state-default,.ui-state-default,.ui-widget-content,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{background:#fff;border:1px solid #ddd;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);color:#32373c}.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-closed,.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-open,.toplevel_page_mlang .ui-selectmenu-button.ui-state-default{border:1px solid #7e8993;border-radius:4px;box-shadow:0 0 0 transparent}.pll-selectmenu-button.ui-widget,.pll-selectmenu-menu .ui-widget{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px}.toplevel_page_mlang .ui-button.ui-selectmenu-button:focus{background:#fff;border-color:#007cba;box-shadow:0 0 0 1px #007cba;color:#016087;outline:2px solid transparent}.toplevel_page_mlang .ui-menu-item,.toplevel_page_mlang .ui-widget-content .ui-state-active,.toplevel_page_mlang .ui-widget-content .ui-state-focus,.toplevel_page_mlang .ui-widget-content .ui-state-hover{color:#016087;margin:0}.pll-selectmenu-menu .ui-widget-content .ui-state-active,.pll-selectmenu-menu .ui-widget-content .ui-state-focus,.pll-selectmenu-menu .ui-widget-content .ui-state-hover,.ui-selectmenu-open .ui-widget-content .ui-state-active,.ui-selectmenu-open .ui-widget-content .ui-state-focus,.ui-selectmenu-open .ui-widget-content .ui-state-hover{background:#d5d5d5;border:0}.ui-selectmenu-button.ui-state-focus{border:1px solid #5b9dd9;box-shadow:0 0 2px rgba(30,140,190,.8)}.ui-icon-triangle-1-s:before{background:#fff url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E) no-repeat right 0 top 55%;background-size:16px 16px;box-sizing:border-box;content:"";height:16px;position:absolute;width:16px}.pll-selectmenu-button.ui-button:hover,.pll-wizard .ui-button:focus,.pll-wizard .ui-button:hover{background:#fff}.ui-widget-content{box-shadow:0 2px 6px hsla(0,0%,39%,.3);max-height:231px}
1
+ .ui-widget-overlay{height:100%;left:0;position:fixed;top:0;width:100%}.ui-menu{display:block;list-style:none;margin:0;outline:none;padding:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{cursor:pointer;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);margin:0;min-height:0;padding:3px 1em 3px .4em;position:relative}.ui-menu .ui-menu-item:not([role]){padding:0}.ui-menu-item-wrapper{padding:3px 1em 3px 2em}.rtl .ui-menu .ui-menu-item{text-align:right}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item[role]{padding-left:2em}.rtl .ui-menu-icons .ui-menu-item[role],.rtl .ui-menu-item-wrapper{padding-left:1em;padding-right:2em}.ui-menu .ui-icon,.ui-selectmenu-text .ui-icon{bottom:0;left:.3em;margin:auto 0;position:absolute;top:0}.rtl .ui-menu .ui-icon,.rtl .ui-selectmenu-text .ui-icon{left:auto;right:.3em}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-selectmenu-menu{display:none;left:0;margin:0;padding:0;position:absolute;top:0}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{border:0;font-size:1em;font-weight:700;height:auto;line-height:23px;margin:.5em 0 0;padding:2px .4em}.ui-selectmenu-open{display:block}.ui-selectmenu-button,.ui-selectmenu-button.ui-button{box-sizing:border-box;display:inline-block;height:28px;line-height:normal;overflow:hidden;padding:0;position:relative;text-align:left;text-decoration:none;vertical-align:top;white-space:nowrap}.ui-selectmenu-button span.ui-icon{background:none;height:16px;left:auto;position:absolute;right:.5em;text-indent:0;top:26%;width:16px}.rtl .ui-selectmenu-button span.ui-icon{left:.5em;right:auto}.ui-selectmenu-button span.ui-selectmenu-text,.ui-selectmenu-button.ui-widget span.ui-selectmenu-text{display:block;line-height:23px;margin:0;overflow:hidden;padding:.1em 2.1em .2em 2em;text-align:left;text-overflow:ellipsis;white-space:nowrap}.rtl .ui-selectmenu-button span.ui-selectmenu-text{padding:.2em 2em .2em 2.1em;text-align:right}.ui-button.ui-selectmenu-button-closed,.ui-button.ui-selectmenu-button-open,.ui-selectmenu-button.ui-state-default,.ui-state-default,.ui-widget-content,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{background:#fff;border:1px solid #ddd;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);color:#32373c}.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-closed,.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-open,.toplevel_page_mlang .ui-selectmenu-button.ui-state-default{border:1px solid #7e8993;border-radius:4px;box-shadow:0 0 0 transparent}.pll-selectmenu-button.ui-widget,.pll-selectmenu-menu .ui-widget{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px}.toplevel_page_mlang .ui-button.ui-selectmenu-button:focus{background:#fff;border-color:#007cba;box-shadow:0 0 0 1px #007cba;color:#016087;outline:2px solid transparent}.toplevel_page_mlang .ui-menu-item,.toplevel_page_mlang .ui-widget-content .ui-state-active,.toplevel_page_mlang .ui-widget-content .ui-state-focus,.toplevel_page_mlang .ui-widget-content .ui-state-hover{color:#016087;margin:0}.pll-selectmenu-menu .ui-widget-content .ui-state-active,.pll-selectmenu-menu .ui-widget-content .ui-state-focus,.pll-selectmenu-menu .ui-widget-content .ui-state-hover,.ui-selectmenu-open .ui-widget-content .ui-state-active,.ui-selectmenu-open .ui-widget-content .ui-state-focus,.ui-selectmenu-open .ui-widget-content .ui-state-hover{background:#d5d5d5;border:0}.ui-selectmenu-button.ui-state-focus{border:1px solid #5b9dd9;box-shadow:0 0 2px rgba(30,140,190,.8)}.ui-icon-triangle-1-s:before{background:#fff url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E") no-repeat right 0 top 55%;background-size:16px 16px;box-sizing:border-box;content:"";height:16px;position:absolute;width:16px}.pll-selectmenu-button.ui-button:hover,.pll-wizard .ui-button:focus,.pll-wizard .ui-button:hover{background:#fff}.ui-widget-content{box-shadow:0 2px 6px hsla(0,0%,39%,.3);max-height:231px}
frontend/choose-lang-url.php CHANGED
@@ -50,7 +50,7 @@ class PLL_Choose_Lang_Url extends PLL_Choose_Lang {
50
 
51
  $requested_url = pll_get_requested_url();
52
  $requested_host = str_replace( 'www.', '', wp_parse_url( $requested_url, PHP_URL_HOST ) ); // Remove www. for the comparison
53
- $requested_path = rtrim( str_replace( $this->index, '', wp_parse_url( $requested_url, PHP_URL_PATH ) ), '/' ); // Some PHP setups turn requests for / into /index.php in REQUEST_URI
54
  $requested_query = wp_parse_url( $requested_url, PHP_URL_QUERY );
55
 
56
  // Home is requested
50
 
51
  $requested_url = pll_get_requested_url();
52
  $requested_host = str_replace( 'www.', '', wp_parse_url( $requested_url, PHP_URL_HOST ) ); // Remove www. for the comparison
53
+ $requested_path = rtrim( str_replace( $this->index, '', (string) wp_parse_url( $requested_url, PHP_URL_PATH ) ), '/' ); // Some PHP setups turn requests for / into /index.php in REQUEST_URI
54
  $requested_query = wp_parse_url( $requested_url, PHP_URL_QUERY );
55
 
56
  // Home is requested
frontend/frontend-filters-links.php CHANGED
@@ -291,6 +291,7 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
291
  array( 'function' => 'wp_nav_menu' ),
292
  array( 'function' => 'login_footer' ),
293
  array( 'function' => 'get_custom_logo' ),
 
294
  )
295
  );
296
  }
@@ -392,29 +393,35 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
392
  }
393
  }
394
 
395
- elseif ( $this->links_model->using_permalinks && is_category() && ! empty( $this->wp_query()->query['cat'] ) ) {
396
- // When we receive a plain permaling with a cat query var, we need to redirect to the pretty permalink.
397
  if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $this->wp_query()->tax_query ) ) ) {
398
- $term_id = $this->get_queried_term_id( $this->wp_query()->tax_query );
399
- $language = $this->model->term->get_language( $term_id );
400
- $redirect_url = $this->maybe_add_page_to_redirect_url( get_term_link( $term_id ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  }
402
- }
403
 
404
- elseif ( is_category() || is_tag() || is_tax() ) {
405
- // We need to switch the language when there is no language provided in a pretty permalink.
406
- $obj = get_queried_object();
407
- if ( ! empty( $obj ) && $this->model->is_translated_taxonomy( $obj->taxonomy ) ) {
408
- $language = $this->model->term->get_language( (int) $obj->term_id );
409
  }
410
  }
411
 
412
  elseif ( is_404() && ! empty( $this->wp_query()->tax_query ) ) {
413
  // When a wrong language is passed through a pretty permalink, we just need to switch the language.
414
- if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $this->wp_query()->tax_query ) ) ) {
415
- $term_id = $this->get_queried_term_id( $this->wp_query()->tax_query );
416
- $language = $this->model->term->get_language( $term_id );
417
- }
418
  }
419
 
420
  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' ) ) {
@@ -554,4 +561,19 @@ class PLL_Frontend_Filters_Links extends PLL_Filters_Links {
554
  protected function wp_query() {
555
  return $GLOBALS['wp_query'];
556
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  }
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
  }
393
  }
394
  }
395
 
396
+ elseif ( is_category() || is_tag() || is_tax() ) {
 
397
  if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $this->wp_query()->tax_query ) ) ) {
398
+ if ( $this->links_model->using_permalinks && ( ! empty( $this->wp_query()->query['cat'] ) || ! empty( $this->wp_query()->query['tag'] ) ) ) {
399
+ // When we receive a plain permalink with a cat or tag query var, we need to redirect to the pretty permalink.
400
+ $term_id = $this->get_queried_term_id( $this->wp_query()->tax_query );
401
+ if ( is_feed() ) {
402
+ $redirect_url = $this->maybe_add_page_to_redirect_url( get_term_feed_link( $term_id, '' ) );
403
+ } else {
404
+ $redirect_url = $this->maybe_add_page_to_redirect_url( get_term_link( $term_id ) );
405
+ }
406
+ $language = $this->get_queried_term_language();
407
+ } else {
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
 
416
+ if ( is_feed() && empty( $obj ) ) {
417
+ // Allows to replace the language correctly in a category feed query.
418
+ $language = $this->get_queried_term_language();
 
 
419
  }
420
  }
421
 
422
  elseif ( is_404() && ! empty( $this->wp_query()->tax_query ) ) {
423
  // When a wrong language is passed through a pretty permalink, we just need to switch the language.
424
+ $language = $this->get_queried_term_language();
 
 
 
425
  }
426
 
427
  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' ) ) {
561
  protected function wp_query() {
562
  return $GLOBALS['wp_query'];
563
  }
564
+
565
+ /**
566
+ * Get the language corresponding to the queried term.
567
+ *
568
+ * @since 3.2
569
+ *
570
+ * @return PLL_Language|false The language object or false.
571
+ */
572
+ public function get_queried_term_language() {
573
+ if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $this->wp_query()->tax_query ) ) ) {
574
+ $term_id = $this->get_queried_term_id( $this->wp_query()->tax_query );
575
+ return $this->model->term->get_language( $term_id );
576
+ }
577
+ return false;
578
+ }
579
  }
frontend/frontend.php CHANGED
@@ -85,6 +85,8 @@ class PLL_Frontend extends PLL_Base {
85
  if ( ! defined( 'PLL_AUTO_TRANSLATE' ) || PLL_AUTO_TRANSLATE ) {
86
  add_action( 'template_redirect', array( $this, 'auto_translate' ), 7 );
87
  }
 
 
88
  }
89
 
90
  /**
@@ -233,4 +235,26 @@ class PLL_Frontend extends PLL_Base {
233
  $this->load_strings_translations();
234
  }
235
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  }
85
  if ( ! defined( 'PLL_AUTO_TRANSLATE' ) || PLL_AUTO_TRANSLATE ) {
86
  add_action( 'template_redirect', array( $this, 'auto_translate' ), 7 );
87
  }
88
+
89
+ add_action( 'admin_bar_menu', array( $this, 'remove_customize_admin_bar' ), 41 ); // After WP_Admin_Bar::add_menus
90
  }
91
 
92
  /**
235
  $this->load_strings_translations();
236
  }
237
  }
238
+
239
+ /**
240
+ * Remove the customize admin bar on front-end when using a block theme.
241
+ *
242
+ * WordPress removes the Customizer menu if a block theme is activated and no other plugins interact with it.
243
+ * As Polylang interacts with the Customizer, we have to delete this menu ourselves in the case of a block theme,
244
+ * unless another plugin than Polylang interacts with the Customizer.
245
+ *
246
+ * @since 3.2
247
+ *
248
+ * @return void
249
+ */
250
+ public function remove_customize_admin_bar() {
251
+ if ( ! $this->should_customize_menu_be_removed() ) {
252
+ return;
253
+ }
254
+
255
+ global $wp_admin_bar;
256
+
257
+ remove_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' ); // To avoid the script launch.
258
+ $wp_admin_bar->remove_menu( 'customize' );
259
+ }
260
  }
include/base.php CHANGED
@@ -167,4 +167,30 @@ abstract class PLL_Base {
167
  */
168
  return $new_blog_id !== $prev_blog_id && in_array( POLYLANG_BASENAME, $plugins ) && get_option( 'polylang' );
169
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  }
167
  */
168
  return $new_blog_id !== $prev_blog_id && in_array( POLYLANG_BASENAME, $plugins ) && get_option( 'polylang' );
169
  }
170
+
171
+ /**
172
+ * Check if the customize menu should be removed or not.
173
+ *
174
+ * @since 3.2
175
+ *
176
+ * @return bool True if it should be removed, false otherwise.
177
+ */
178
+ public function should_customize_menu_be_removed() {
179
+ // Exit if a block theme isn't activated.
180
+ if ( ! function_exists( 'wp_is_block_theme' ) || ! wp_is_block_theme() ) {
181
+ return false;
182
+ }
183
+
184
+ global $wp_filter;
185
+ if ( empty( $wp_filter['customize_register'] ) ) {
186
+ return false;
187
+ }
188
+
189
+ $customize_register_hooks = count( array_merge( ...array_values( $wp_filter['customize_register']->callbacks ) ) );
190
+ if ( $customize_register_hooks > 1 ) {
191
+ return false;
192
+ }
193
+
194
+ return true;
195
+ }
196
  }
include/class-polylang.php CHANGED
@@ -95,10 +95,10 @@ class Polylang {
95
  */
96
  public static function is_rest_request() {
97
  // Handle pretty permalinks.
98
- $home_path = trim( wp_parse_url( home_url(), PHP_URL_PATH ), '/' );
99
  $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
100
 
101
- $req_uri = trim( wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
102
  $req_uri = preg_replace( $home_path_regex, '', $req_uri );
103
  $req_uri = trim( $req_uri, '/' );
104
  $req_uri = str_replace( 'index.php', '', $req_uri );
@@ -106,7 +106,7 @@ class Polylang {
106
 
107
  // And also test rest_route query string parameter is not empty for plain permalinks.
108
  $query_string = array();
109
- wp_parse_str( wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY ), $query_string );
110
  $rest_route = isset( $query_string['rest_route'] ) ? trim( $query_string['rest_route'], '/' ) : false;
111
 
112
  return 0 === strpos( $req_uri, rest_get_url_prefix() . '/' ) || ! empty( $rest_route );
95
  */
96
  public static function is_rest_request() {
97
  // Handle pretty permalinks.
98
+ $home_path = trim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' );
99
  $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
100
 
101
+ $req_uri = trim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
102
  $req_uri = preg_replace( $home_path_regex, '', $req_uri );
103
  $req_uri = trim( $req_uri, '/' );
104
  $req_uri = str_replace( 'index.php', '', $req_uri );
106
 
107
  // And also test rest_route query string parameter is not empty for plain permalinks.
108
  $query_string = array();
109
+ wp_parse_str( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY ), $query_string );
110
  $rest_route = isset( $query_string['rest_route'] ) ? trim( $query_string['rest_route'], '/' ) : false;
111
 
112
  return 0 === strpos( $req_uri, rest_get_url_prefix() . '/' ) || ! empty( $rest_route );
include/crud-posts.php CHANGED
@@ -197,10 +197,12 @@ class PLL_CRUD_Posts {
197
  */
198
  public function wp_insert_post_parent( $post_parent, $post_id ) {
199
  $lang = $this->model->post->get_language( $post_id );
 
200
  // Dont break the hierarchy in case the post has no language
201
- if ( ! empty( $lang ) ) {
202
  $post_parent = $this->model->post->get_translation( $post_parent, $lang );
203
  }
 
204
  return $post_parent;
205
  }
206
 
197
  */
198
  public function wp_insert_post_parent( $post_parent, $post_id ) {
199
  $lang = $this->model->post->get_language( $post_id );
200
+ $parent_post_type = $post_parent > 0 ? get_post_type( $post_parent ) : null;
201
  // Dont break the hierarchy in case the post has no language
202
+ if ( ! empty( $lang ) && ! empty( $parent_post_type ) && $this->model->is_translated_post_type( $parent_post_type ) ) {
203
  $post_parent = $this->model->post->get_translation( $post_parent, $lang );
204
  }
205
+
206
  return $post_parent;
207
  }
208
 
include/db-tools.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package Polylang
4
+ */
5
+
6
+ defined( 'ABSPATH' ) || exit; // @phpstan-ignore-line
7
+
8
+ /**
9
+ * Small set of tools to work with the database.
10
+ *
11
+ * @since 3.2
12
+ */
13
+ class PLL_Db_Tools {
14
+
15
+ /**
16
+ * Changes an array of values into a comma separated list, ready to be used in a `IN ()` clause.
17
+ * Only string and integers and supported for now.
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 );
26
+
27
+ return implode( ',', $values );
28
+ }
29
+
30
+ /**
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 ) {
41
+ if ( ! is_numeric( $value ) ) {
42
+ return $GLOBALS['wpdb']->prepare( '%s', $value );
43
+ }
44
+
45
+ return (int) $value;
46
+ }
47
+ }
include/filters-links.php CHANGED
@@ -163,7 +163,9 @@ class PLL_Filters_Links {
163
  // In case someone calls get_term_link for the 'language' taxonomy.
164
  if ( 'language' === $tax ) {
165
  $lang = $this->model->get_language( $term->term_id );
166
- return $this->links_model->home_url( $lang );
 
 
167
  }
168
 
169
  return $link;
163
  // In case someone calls get_term_link for the 'language' taxonomy.
164
  if ( 'language' === $tax ) {
165
  $lang = $this->model->get_language( $term->term_id );
166
+ if ( $lang ) {
167
+ return $this->links_model->home_url( $lang );
168
+ }
169
  }
170
 
171
  return $link;
include/filters.php CHANGED
@@ -79,6 +79,9 @@ class PLL_Filters {
79
 
80
  // Personal data exporter
81
  add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ), 0 ); // Since WP 4.9.6
 
 
 
82
  }
83
 
84
  /**
@@ -200,36 +203,20 @@ class PLL_Filters {
200
 
201
  static $once = false;
202
 
203
- // Obliged to redo the get_pages query if we want to get the right number
204
  if ( ! empty( $args['number'] ) && ! $once ) {
205
- $once = true; // avoid infinite loop
206
-
207
- $r = array(
208
- 'lang' => 0, // So this query is not filtered
209
- 'numberposts' => -1,
210
- 'nopaging' => true,
211
- 'post_type' => $args['post_type'],
212
- 'fields' => 'ids',
213
- 'tax_query' => array(
214
- array(
215
- 'taxonomy' => 'language',
216
- 'field' => 'term_taxonomy_id', // Since WP 3.5
217
- 'terms' => $language->term_taxonomy_id,
218
- 'operator' => 'NOT IN',
219
- ),
220
- ),
221
- );
222
 
223
  // Take care that 'exclude' argument accepts integer or strings too.
224
- $args['exclude'] = array_merge( wp_parse_id_list( $args['exclude'] ), get_posts( $r ) ); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
225
  $pages = get_pages( $args );
226
  }
227
 
228
  $ids = wp_list_pluck( $pages, 'ID' );
229
 
230
- // Filters the queried list of pages by language
231
  if ( ! $once ) {
232
- $ids = array_intersect( $ids, $this->model->post->get_objects_in_language( $language ) );
 
233
 
234
  foreach ( $pages as $key => $page ) {
235
  if ( ! in_array( $page->ID, $ids ) ) {
@@ -237,16 +224,47 @@ class PLL_Filters {
237
  }
238
  }
239
 
240
- $pages = array_values( $pages ); // In case 3rd parties suppose the existence of $pages[0]
241
  }
242
 
243
- // Not done by WP but extremely useful for performance when manipulating taxonomies
244
  update_object_term_cache( $ids, $args['post_type'] );
245
 
246
- $once = false; // In case get_pages is called another time
247
  return $pages;
248
  }
249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  /**
251
  * Modifies the sql request for get_adjacent_post to filter by the current language.
252
  *
@@ -399,4 +417,32 @@ class PLL_Filters {
399
  'done' => true,
400
  );
401
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  }
79
 
80
  // Personal data exporter
81
  add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ), 0 ); // Since WP 4.9.6
82
+
83
+ // Fix for `term_exists()`.
84
+ add_filter( 'term_exists_default_query_args', array( $this, 'term_exists_default_query_args' ), 0, 3 ); // Since WP 6.0.0.
85
  }
86
 
87
  /**
203
 
204
  static $once = false;
205
 
 
206
  if ( ! empty( $args['number'] ) && ! $once ) {
207
+ // We are obliged to redo the get_pages() query if we want to get the right number.
208
+ $once = true; // Avoid infinite loop.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
  // Take care that 'exclude' argument accepts integer or strings too.
211
+ $args['exclude'] = array_merge( wp_parse_id_list( $args['exclude'] ), $this->get_related_page_ids( $language, 'NOT IN', $args ) ); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
212
  $pages = get_pages( $args );
213
  }
214
 
215
  $ids = wp_list_pluck( $pages, 'ID' );
216
 
 
217
  if ( ! $once ) {
218
+ // Filters the queried list of pages by language.
219
+ $ids = array_intersect( $ids, $this->get_related_page_ids( $language, 'IN', $args ) );
220
 
221
  foreach ( $pages as $key => $page ) {
222
  if ( ! in_array( $page->ID, $ids ) ) {
224
  }
225
  }
226
 
227
+ $pages = array_values( $pages ); // In case 3rd parties suppose the existence of $pages[0].
228
  }
229
 
230
+ // Not done by WP but extremely useful for performance when manipulating taxonomies.
231
  update_object_term_cache( $ids, $args['post_type'] );
232
 
233
+ $once = false; // In case get_pages() is called another time.
234
  return $pages;
235
  }
236
 
237
+ /**
238
+ * Get page ids related to a get_pages() in or not in a given language.
239
+ *
240
+ * @since 3.2
241
+ *
242
+ * @param PLL_Language $language The language to use in the relationship
243
+ * @param string $relation 'IN' or 'NOT IN'.
244
+ * @param array $args Array of get_pages() arguments.
245
+ * @return int[]
246
+ */
247
+ protected function get_related_page_ids( $language, $relation, $args ) {
248
+ $r = array(
249
+ 'lang' => '', // Ensure this query is not filtered.
250
+ 'numberposts' => -1,
251
+ 'nopaging' => true,
252
+ 'post_type' => $args['post_type'],
253
+ 'post_status' => $args['post_status'],
254
+ 'fields' => 'ids',
255
+ 'tax_query' => array(
256
+ array(
257
+ 'taxonomy' => 'language',
258
+ 'field' => 'term_taxonomy_id', // Since WP 3.5.
259
+ 'terms' => $language->term_taxonomy_id,
260
+ 'operator' => $relation,
261
+ ),
262
+ ),
263
+ );
264
+
265
+ return get_posts( $r );
266
+ }
267
+
268
  /**
269
  * Modifies the sql request for get_adjacent_post to filter by the current language.
270
  *
417
  'done' => true,
418
  );
419
  }
420
+
421
+ /**
422
+ * Filters default query arguments for checking if a term exists.
423
+ * In `term_exists()`, WP 6.0 uses `get_terms()`, which is filtered by language by Polylang.
424
+ * This filter prevents `term_exists()` to be filtered by language.
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 ) ) {
435
+ return $defaults;
436
+ }
437
+
438
+ if ( ! is_array( $defaults ) ) {
439
+ $defaults = array();
440
+ }
441
+
442
+ if ( ! isset( $defaults['lang'] ) ) {
443
+ $defaults['lang'] = '';
444
+ }
445
+
446
+ return $defaults;
447
+ }
448
  }
include/license.php CHANGED
@@ -138,12 +138,12 @@ class PLL_License {
138
  }
139
 
140
  /**
141
- * Activate the license key
142
  *
143
  * @since 1.9
144
  *
145
- * @param string $license_key activation key
146
- * @return object updated $this
147
  */
148
  public function activate_license( $license_key ) {
149
  $this->license_key = $license_key;
@@ -156,11 +156,11 @@ class PLL_License {
156
 
157
 
158
  /**
159
- * Deactivate the license key
160
  *
161
  * @since 1.9
162
  *
163
- * @return object updated $this
164
  */
165
  public function deactivate_license() {
166
  $this->api_request( 'deactivate_license' );
@@ -168,11 +168,11 @@ class PLL_License {
168
  }
169
 
170
  /**
171
- * Check if license key is valid
172
  *
173
  * @since 1.9
174
  *
175
- * @return object updated $this
176
  */
177
  public function check_license() {
178
  $this->api_request( 'check_license' );
@@ -331,7 +331,7 @@ class PLL_License {
331
  $class = 'notice-warning notice-alt';
332
  $message = sprintf(
333
  /* translators: %1$s is a date, %2$s is link start tag, %3$s is link end tag. */
334
- esc_html__( 'Your license key will expire soon! Precisely, it will expire on %1$s. %2$sRenew your license key today!%3$s.', 'polylang' ),
335
  esc_html( date_i18n( get_option( 'date_format' ), $expiration ) ),
336
  sprintf( '<a href="%s" target="_blank">', 'https://polylang.pro/account/' ),
337
  '</a>'
138
  }
139
 
140
  /**
141
+ * Activates the license key.
142
  *
143
  * @since 1.9
144
  *
145
+ * @param string $license_key Activation key.
146
+ * @return PLL_License Updated PLL_License object.
147
  */
148
  public function activate_license( $license_key ) {
149
  $this->license_key = $license_key;
156
 
157
 
158
  /**
159
+ * Deactivates the license key.
160
  *
161
  * @since 1.9
162
  *
163
+ * @return PLL_License Updated PLL_License object.
164
  */
165
  public function deactivate_license() {
166
  $this->api_request( 'deactivate_license' );
168
  }
169
 
170
  /**
171
+ * Checks if the license key is valid.
172
  *
173
  * @since 1.9
174
  *
175
+ * @return PLL_License Updated PLL_License object.
176
  */
177
  public function check_license() {
178
  $this->api_request( 'check_license' );
331
  $class = 'notice-warning notice-alt';
332
  $message = sprintf(
333
  /* translators: %1$s is a date, %2$s is link start tag, %3$s is link end tag. */
334
+ esc_html__( 'Your license key will expire soon! Precisely, it will expire on %1$s. %2$sRenew your license key today!%3$s', 'polylang' ),
335
  esc_html( date_i18n( get_option( 'date_format' ), $expiration ) ),
336
  sprintf( '<a href="%s" target="_blank">', 'https://polylang.pro/account/' ),
337
  '</a>'
include/links-directory.php CHANGED
@@ -123,10 +123,10 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
123
  $url = pll_get_requested_url();
124
  }
125
 
126
- $path = wp_parse_url( $url, PHP_URL_PATH );
127
  $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
128
 
129
- $pattern = 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
123
  $url = pll_get_requested_url();
124
  }
125
 
126
+ $path = (string) wp_parse_url( $url, PHP_URL_PATH );
127
  $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
128
 
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
include/model.php CHANGED
@@ -80,19 +80,24 @@ class PLL_Model {
80
  */
81
  public function get_languages_list( $args = array() ) {
82
  if ( false === $languages = $this->cache->get( 'languages' ) ) {
 
83
 
84
  // Create the languages from taxonomies.
85
  if ( ( defined( 'PLL_CACHE_LANGUAGES' ) && ! PLL_CACHE_LANGUAGES ) || false === ( $languages = get_transient( 'pll_languages_list' ) ) ) {
86
- $languages = get_terms( 'language', array( 'hide_empty' => false, 'orderby' => 'term_group' ) );
87
- $languages = empty( $languages ) || is_wp_error( $languages ) ? array() : $languages;
 
 
88
 
89
  $term_languages = get_terms( 'term_language', array( 'hide_empty' => false ) );
90
  $term_languages = empty( $term_languages ) || is_wp_error( $term_languages ) ?
91
  array() : array_combine( wp_list_pluck( $term_languages, 'slug' ), $term_languages );
92
 
93
- if ( ! empty( $languages ) && ! empty( $term_languages ) ) {
94
- foreach ( $languages as $k => $v ) {
95
- $languages[ $k ] = new PLL_Language( $v, $term_languages[ 'pll_' . $v->slug ] );
 
 
96
  }
97
 
98
  // We will need the languages list to allow its access in the filter below.
@@ -116,9 +121,6 @@ class PLL_Model {
116
  */
117
  set_transient( 'pll_languages_list', array_map( 'get_object_vars', $languages ) );
118
  }
119
- else {
120
- $languages = array(); // In case something went wrong.
121
- }
122
  }
123
 
124
  // Create the languages directly from arrays stored in transients.
@@ -508,6 +510,8 @@ class PLL_Model {
508
  $counts = wp_cache_get( $cache_key, 'counts' );
509
 
510
  if ( false === $counts ) {
 
 
511
  $select = "SELECT pll_tr.term_taxonomy_id, COUNT( * ) AS num_posts FROM {$wpdb->posts}";
512
  $join = $this->post->join_clause();
513
  $where = sprintf( " WHERE post_status = '%s'", esc_sql( $q['post_status'] ) );
80
  */
81
  public function get_languages_list( $args = array() ) {
82
  if ( false === $languages = $this->cache->get( 'languages' ) ) {
83
+ $languages = array();
84
 
85
  // Create the languages from taxonomies.
86
  if ( ( defined( 'PLL_CACHE_LANGUAGES' ) && ! PLL_CACHE_LANGUAGES ) || false === ( $languages = get_transient( 'pll_languages_list' ) ) ) {
87
+ $languages = array();
88
+
89
+ $post_languages = get_terms( 'language', array( 'hide_empty' => false, 'orderby' => 'term_group' ) );
90
+ $post_languages = empty( $post_languages ) || is_wp_error( $post_languages ) ? array() : $post_languages;
91
 
92
  $term_languages = get_terms( 'term_language', array( 'hide_empty' => false ) );
93
  $term_languages = empty( $term_languages ) || is_wp_error( $term_languages ) ?
94
  array() : array_combine( wp_list_pluck( $term_languages, 'slug' ), $term_languages );
95
 
96
+ if ( ! empty( $post_languages ) && ! empty( $term_languages ) ) {
97
+ foreach ( $post_languages as $k => $v ) {
98
+ if ( isset( $term_languages[ 'pll_' . $v->slug ] ) ) {
99
+ $languages[ $k ] = new PLL_Language( $v, $term_languages[ 'pll_' . $v->slug ] );
100
+ }
101
  }
102
 
103
  // We will need the languages list to allow its access in the filter below.
121
  */
122
  set_transient( 'pll_languages_list', array_map( 'get_object_vars', $languages ) );
123
  }
 
 
 
124
  }
125
 
126
  // Create the languages directly from arrays stored in transients.
510
  $counts = wp_cache_get( $cache_key, 'counts' );
511
 
512
  if ( false === $counts ) {
513
+ $counts = array();
514
+
515
  $select = "SELECT pll_tr.term_taxonomy_id, COUNT( * ) AS num_posts FROM {$wpdb->posts}";
516
  $join = $this->post->join_clause();
517
  $where = sprintf( " WHERE post_status = '%s'", esc_sql( $q['post_status'] ) );
include/olt-manager.php CHANGED
@@ -74,7 +74,7 @@ class PLL_OLT_Manager {
74
  *
75
  * @since 1.7
76
  *
77
- * @return object
78
  */
79
  public static function instance() {
80
  if ( empty( self::$instance ) ) {
74
  *
75
  * @since 1.7
76
  *
77
+ * @return PLL_OLT_Manager
78
  */
79
  public static function instance() {
80
  if ( empty( self::$instance ) ) {
include/rest-request.php CHANGED
@@ -9,34 +9,39 @@
9
  * @since 2.6
10
  */
11
  class PLL_REST_Request extends PLL_Base {
 
 
 
 
 
12
 
13
  /**
14
- * @var PLL_Filters
15
  */
16
  public $filters;
17
 
18
  /**
19
- * @var PLL_Filters_Links
20
  */
21
  public $filters_links;
22
 
23
  /**
24
- * @var PLL_Admin_Links
25
  */
26
  public $links;
27
 
28
  /**
29
- * @var PLL_Nav_Menu
30
  */
31
  public $nav_menu;
32
 
33
  /**
34
- * @var PLL_Static_Pages
35
  */
36
  public $static_pages;
37
 
38
  /**
39
- * @var PLL_Filters_Widgets_Options
40
  */
41
  public $filters_widgets_options;
42
 
@@ -50,23 +55,39 @@ class PLL_REST_Request extends PLL_Base {
50
  public function init() {
51
  parent::init();
52
 
53
- if ( $this->model->get_languages_list() ) {
54
-
55
- /** This action is documented in include/class-polylang.php */
56
- do_action( 'pll_no_language_defined' ); // To load overridden textdomains.
57
 
58
- $this->filters_links = new PLL_Filters_Links( $this );
59
- $this->filters = new PLL_Filters( $this );
60
- $this->filters_widgets_options = new PLL_Filters_Widgets_Options( $this );
 
61
 
62
- // Static front page and page for posts.
63
- if ( 'page' === get_option( 'show_on_front' ) ) {
64
- $this->static_pages = new PLL_Static_Pages( $this );
65
  }
 
 
 
 
 
 
 
 
 
66
 
67
- $this->links = new PLL_Admin_Links( $this );
 
 
68
 
69
- $this->nav_menu = new PLL_Nav_Menu( $this ); // For auto added pages to menu.
 
 
70
  }
 
 
 
71
  }
72
  }
9
  * @since 2.6
10
  */
11
  class PLL_REST_Request extends PLL_Base {
12
+ /**
13
+ * @var PLL_Language|false|null A `PLL_Language` when defined, `false` otherwise. `null` until the language
14
+ * definition process runs.
15
+ */
16
+ public $curlang;
17
 
18
  /**
19
+ * @var PLL_Filters|null
20
  */
21
  public $filters;
22
 
23
  /**
24
+ * @var PLL_Filters_Links|null
25
  */
26
  public $filters_links;
27
 
28
  /**
29
+ * @var PLL_Admin_Links|null
30
  */
31
  public $links;
32
 
33
  /**
34
+ * @var PLL_Nav_Menu|null
35
  */
36
  public $nav_menu;
37
 
38
  /**
39
+ * @var PLL_Static_Pages|null
40
  */
41
  public $static_pages;
42
 
43
  /**
44
+ * @var PLL_Filters_Widgets_Options|null
45
  */
46
  public $filters_widgets_options;
47
 
55
  public function init() {
56
  parent::init();
57
 
58
+ if ( ! $this->model->get_languages_list() ) {
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.
69
+ $this->curlang = $this->model->get_language( sanitize_key( $this->options['default_lang'] ) );
70
  }
71
+ }
72
+
73
+ if ( ! empty( $this->curlang ) ) {
74
+ /** This action is documented in frontend/choose-lang.php */
75
+ do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang );
76
+ } else {
77
+ /** This action is documented in include/class-polylang.php */
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
  }
include/switcher.php CHANGED
@@ -212,6 +212,7 @@ class PLL_Switcher {
212
  * @return string|array either the html markup of the switcher or the raw elements to build a custom language switcher
213
  */
214
  public function the_languages( $links, $args = array() ) {
 
215
  $this->links = $links;
216
  $args = wp_parse_args( $args, self::DEFAULTS );
217
 
@@ -242,8 +243,10 @@ class PLL_Switcher {
242
 
243
  if ( $args['dropdown'] ) {
244
  $args['name'] = 'lang_choice_' . $args['dropdown'];
 
 
 
245
  $walker = new PLL_Walker_Dropdown();
246
- $args['selected'] = $this->get_current_language( $args );
247
  } else {
248
  $walker = new PLL_Walker_List();
249
  }
@@ -264,14 +267,9 @@ class PLL_Switcher {
264
  $out .= sprintf(
265
  '<script type="text/javascript">
266
  //<![CDATA[
267
- var %1$s = %2$s;
268
- document.getElementById( "%3$s" ).onchange = function() {
269
- location.href = %1$s[this.value];
270
- }
271
  //]]>
272
  </script>',
273
- 'urls_' . preg_replace( '#[^a-zA-Z0-9]#', '', $args['dropdown'] ),
274
- wp_json_encode( wp_list_pluck( $elements, 'url' ) ),
275
  esc_js( $args['name'] )
276
  );
277
  }
212
  * @return string|array either the html markup of the switcher or the raw elements to build a custom language switcher
213
  */
214
  public function the_languages( $links, $args = array() ) {
215
+
216
  $this->links = $links;
217
  $args = wp_parse_args( $args, self::DEFAULTS );
218
 
243
 
244
  if ( $args['dropdown'] ) {
245
  $args['name'] = 'lang_choice_' . $args['dropdown'];
246
+ $args['class'] = 'pll-switcher-select';
247
+ $args['value'] = 'url';
248
+ $args['selected'] = $this->get_link( $this->links->model->get_language( $this->get_current_language( $args ) ), $args );
249
  $walker = new PLL_Walker_Dropdown();
 
250
  } else {
251
  $walker = new PLL_Walker_List();
252
  }
267
  $out .= sprintf(
268
  '<script type="text/javascript">
269
  //<![CDATA[
270
+ document.getElementById( "%1$s" ).addEventListener( "change", function ( event ) { location.href = event.currentTarget.value; } )
 
 
 
271
  //]]>
272
  </script>',
 
 
273
  esc_js( $args['name'] )
274
  );
275
  }
include/translated-object.php CHANGED
@@ -101,7 +101,9 @@ abstract class PLL_Translated_Object {
101
  * @return void
102
  */
103
  public function update_language( $id, $lang ) {
104
- if ( $this->get_language( $id ) === $lang ) {
 
 
105
  return;
106
  }
107
 
@@ -117,8 +119,8 @@ abstract class PLL_Translated_Object {
117
  }
118
 
119
  /**
120
- * Wrap wp_get_object_terms() to cache it and return only one object.
121
- * inspired by the WordPress function get_the_terms().
122
  *
123
  * @since 1.2
124
  *
@@ -127,44 +129,68 @@ abstract class PLL_Translated_Object {
127
  * @return WP_Term|false The term associated to the object in the requested taxonomy if it exists, false otherwise.
128
  */
129
  public function get_object_term( $object_id, $taxonomy ) {
130
- if ( empty( $object_id ) || is_wp_error( $object_id ) ) {
 
 
131
  return false;
132
  }
133
 
134
- $object_id = (int) $object_id;
135
 
136
- if ( $object_id < 0 ) {
137
- return false;
138
  }
139
 
140
- $term = get_object_term_cache( $object_id, $taxonomy );
 
141
 
142
- if ( false === $term ) {
143
- // Query language and translations at the same time.
144
- $taxonomies = array( $this->tax_language, $this->tax_translations );
145
-
146
- // Query terms.
147
- $terms = array();
148
- $object_terms = wp_get_object_terms( $object_id, $taxonomies, array( 'update_term_meta_cache' => false ) );
149
- if ( is_array( $object_terms ) ) {
150
- foreach ( $object_terms as $t ) {
151
- $terms[ $t->taxonomy ] = $t;
152
- if ( $t->taxonomy == $taxonomy ) {
153
- $term = $t;
154
- }
155
  }
156
  }
 
157
 
158
- // Stores it the way WP expects it. Set an empty cache if no term was found in the taxonomy.
159
- foreach ( $taxonomies as $tax ) {
160
- wp_cache_add( $object_id, empty( $terms[ $tax ] ) ? array() : array( $terms[ $tax ] ), $tax . '_relationships' );
161
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  }
163
- else {
164
- $term = reset( $term );
 
 
 
165
  }
166
 
167
- return empty( $term ) ? false : $term;
 
 
 
168
  }
169
 
170
  /**
@@ -173,13 +199,14 @@ abstract class PLL_Translated_Object {
173
  * @since 2.3
174
  *
175
  * @param int $id Object id ( typically a post_id or term_id ).
176
- * @param int[] $translations An associative array of translations with language code as key and translation id as value.
 
177
  * @return bool
178
  */
179
  protected function should_update_translation_group( $id, $translations ) {
180
  // Don't do anything if no translations have been added to the group.
181
  $old_translations = $this->get_translations( $id ); // Includes at least $id itself.
182
- return count( array_diff_assoc( $translations, $old_translations ) ) > 0;
183
  }
184
 
185
  /**
@@ -189,79 +216,72 @@ abstract class PLL_Translated_Object {
189
  *
190
  * @param int $id Object id ( typically a post_id or term_id ).
191
  * @param int[] $translations An associative array of translations with language code as key and translation id as value.
192
- *
193
- * @return int[] An associative array with language codes as key and post ids as values.
194
  */
195
  public function save_translations( $id, $translations ) {
196
- $translations = $this->validate_translations( $translations );
197
 
198
- $id = (int) $id;
 
 
199
 
200
- if ( $lang = $this->get_language( $id ) ) {
201
- // Sanitize the translations array.
202
- $translations = array_map( 'intval', $translations );
203
- $translations = array_merge( array( $lang->slug => $id ), $translations ); // Make sure this object is in translations.
204
- $translations = array_diff( $translations, array( 0 ) ); // Don't keep non translated languages.
205
- $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) ); // Keep only valid languages slugs as keys.
206
 
207
- // Unlink removed translations.
208
- $old_translations = $this->get_translations( $id );
209
- foreach ( array_diff_assoc( $old_translations, $translations ) as $object_id ) {
210
- $this->delete_translation( $object_id );
211
- }
212
 
213
- // Check id we need to create or update the translation group.
214
- if ( $this->should_update_translation_group( $id, $translations ) ) {
215
- $terms = wp_get_object_terms( $translations, $this->tax_translations );
216
- $term = reset( $terms );
217
-
218
- // Create a new term if necessary.
219
- if ( empty( $term ) ) {
220
- wp_insert_term( $group = uniqid( 'pll_' ), $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
221
- } else {
222
- // Take care not to overwrite extra data stored in the description field, if any.
223
- $d = maybe_unserialize( $term->description );
224
- $d = is_array( $d ) ? array_diff_key( $d, $old_translations ) : array(); // Remove old translations.
225
- $d = array_merge( $d, $translations ); // Add new one.
226
- wp_update_term( $group = (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
227
- }
228
 
229
- // Link all translations to the new term.
230
- foreach ( $translations as $p ) {
231
- wp_set_object_terms( $p, $group, $this->tax_translations );
232
- }
233
 
234
- // Clean now unused translation groups.
235
- foreach ( wp_list_pluck( $terms, 'term_id' ) as $term_id ) {
236
- $term = get_term( $term_id, $this->tax_translations );
237
- if ( empty( $term->count ) ) {
238
- wp_delete_term( $term_id, $this->tax_translations );
239
- }
240
- }
241
- }
242
  return $translations;
 
 
 
 
 
 
 
 
 
243
  } else {
244
- return array();
 
 
 
 
 
245
  }
246
- }
247
 
248
- /**
249
- * Returns translations after checking the translated post is in the right language
250
- *
251
- * @since 3.1
252
- *
253
- * @param int[] $translations An associative array of translations with language code as key and translation id as value.
254
- *
255
- * @return int[]
256
- */
257
- public function validate_translations( $translations ) {
258
- $valid_translations = array();
259
 
260
- foreach ( $translations as $lang => $tr_id ) {
261
- $valid_translations[ $lang ] = ( $tr_id && $this->get_language( (int) $tr_id )->slug == $lang ) ? (int) $tr_id : 0;
 
 
 
 
 
 
 
 
 
 
262
  }
263
 
264
- return $valid_translations;
265
  }
266
 
267
  /**
@@ -273,22 +293,33 @@ abstract class PLL_Translated_Object {
273
  * @return void
274
  */
275
  public function delete_translation( $id ) {
276
- $id = (int) $id;
 
 
 
 
 
277
  $term = $this->get_object_term( $id, $this->tax_translations );
278
 
279
- if ( ! empty( $term ) ) {
280
- $d = maybe_unserialize( $term->description );
281
- if ( is_array( $d ) ) {
282
- $slug = array_search( $id, $this->get_translations( $id ) ); // In case some plugin stores the same value with different key.
283
- unset( $d[ $slug ] );
284
- }
285
 
286
- if ( empty( $d ) ) {
287
- wp_delete_term( (int) $term->term_id, $this->tax_translations );
288
- } else {
289
- wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
 
290
  }
291
  }
 
 
 
 
 
 
292
  }
293
 
294
  /**
@@ -300,20 +331,16 @@ abstract class PLL_Translated_Object {
300
  * @return int[] An associative array of translations with language code as key and translation id as value.
301
  */
302
  public function get_translations( $id ) {
303
- $term = $this->get_object_term( $id, $this->tax_translations );
304
- $translations = empty( $term ) ? array() : maybe_unserialize( $term->description );
305
 
306
- // Make sure we return only translations ( thus we allow plugins to store other information in the array ).
307
- if ( is_array( $translations ) ) {
308
- $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) );
309
  }
310
 
311
- // Make sure to return at least the passed object in its translation array.
312
- if ( empty( $translations ) && $lang = $this->get_language( $id ) ) {
313
- $translations = array( $lang->slug => $id );
314
- }
315
 
316
- return $translations;
317
  }
318
 
319
  /**
@@ -326,7 +353,9 @@ abstract class PLL_Translated_Object {
326
  * @return int|false Object id of the translation, false if there is none.
327
  */
328
  public function get_translation( $id, $lang ) {
329
- if ( ! $lang = $this->model->get_language( $lang ) ) {
 
 
330
  return false;
331
  }
332
 
@@ -345,14 +374,25 @@ abstract class PLL_Translated_Object {
345
  * @return int|false The translation object id if exists, otherwise the passed id, false if the passed object has no language.
346
  */
347
  public function get( $id, $lang ) {
348
- $id = (int) $id;
 
 
 
 
 
349
  $lang = $this->model->get_language( $lang );
 
 
 
 
 
350
  $obj_lang = $this->get_language( $id );
351
- if ( empty( $lang ) || empty( $obj_lang ) ) {
 
352
  return false;
353
  }
354
 
355
- return $obj_lang->term_id == $lang->term_id ? $id : $this->get_translation( $id, $lang );
356
  }
357
 
358
  /**
@@ -398,38 +438,7 @@ abstract class PLL_Translated_Object {
398
  }
399
 
400
  /**
401
- * Returns ids of objects in a language similarly to get_objects_in_term() for a taxonomy.
402
- * It is faster than get_objects_in_term() as it avoids a JOIN.
403
- *
404
- * @since 1.4
405
- *
406
- * @param PLL_Language $lang PLL_Language object.
407
- * @return int[] Object ids.
408
- */
409
- public function get_objects_in_language( $lang ) {
410
- global $wpdb;
411
- $tt_id = $this->tax_tt;
412
-
413
- $last_changed = wp_cache_get_last_changed( 'terms' );
414
- $cache_key = "polylang:get_objects_in_language:{$lang->$tt_id}:{$last_changed}";
415
- $cache = wp_cache_get( $cache_key, 'terms' );
416
-
417
- if ( false === $cache ) {
418
- $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $lang->$tt_id ) );
419
- wp_cache_set( $cache_key, $object_ids, 'terms' );
420
- } else {
421
- $object_ids = (array) $cache;
422
- }
423
-
424
- if ( ! $object_ids ) {
425
- return array();
426
- }
427
-
428
- return $object_ids;
429
- }
430
-
431
- /**
432
- * Check if a user can synchronize translations.
433
  *
434
  * @since 2.6
435
  *
@@ -437,6 +446,12 @@ abstract class PLL_Translated_Object {
437
  * @return bool
438
  */
439
  public function current_user_can_synchronize( $id ) {
 
 
 
 
 
 
440
  /**
441
  * Filters whether a synchronization capability check should take place.
442
  *
@@ -449,8 +464,9 @@ abstract class PLL_Translated_Object {
449
  * @param int $id The synchronization source object id.
450
  */
451
  $check = apply_filters( "pll_pre_current_user_can_synchronize_{$this->type}", true, $id );
 
452
  if ( null !== $check ) {
453
- return $check;
454
  }
455
 
456
  if ( ! current_user_can( "edit_{$this->type}", $id ) ) {
@@ -465,4 +481,109 @@ abstract class PLL_Translated_Object {
465
 
466
  return true;
467
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
101
  * @return void
102
  */
103
  public function update_language( $id, $lang ) {
104
+ $id = $this->sanitize_int_id( $id );
105
+
106
+ if ( empty( $id ) || $this->get_language( $id ) === $lang ) {
107
  return;
108
  }
109
 
119
  }
120
 
121
  /**
122
+ * Wraps wp_get_object_terms() to cache it and return only one object.
123
+ * Inspired by the WordPress function get_the_terms().
124
  *
125
  * @since 1.2
126
  *
129
  * @return WP_Term|false The term associated to the object in the requested taxonomy if it exists, false otherwise.
130
  */
131
  public function get_object_term( $object_id, $taxonomy ) {
132
+ $object_id = $this->sanitize_int_id( $object_id );
133
+
134
+ if ( empty( $object_id ) ) {
135
  return false;
136
  }
137
 
138
+ $term = get_object_term_cache( $object_id, $taxonomy );
139
 
140
+ if ( is_array( $term ) ) {
141
+ return ! empty( $term ) ? reset( $term ) : false;
142
  }
143
 
144
+ // Query language and translations at the same time.
145
+ $taxonomies = array( $this->tax_language, $this->tax_translations );
146
 
147
+ // Query terms.
148
+ $terms = array();
149
+ $term = false;
150
+ $object_terms = wp_get_object_terms( $object_id, $taxonomies, array( 'update_term_meta_cache' => false ) );
151
+
152
+ if ( is_array( $object_terms ) ) {
153
+ foreach ( $object_terms as $t ) {
154
+ $terms[ $t->taxonomy ] = $t;
155
+ if ( $t->taxonomy === $taxonomy ) {
156
+ $term = $t;
 
 
 
157
  }
158
  }
159
+ }
160
 
161
+ // Stores it the way WP expects it. Set an empty cache if no term was found in the taxonomy.
162
+ foreach ( $taxonomies as $tax ) {
163
+ wp_cache_add( $object_id, empty( $terms[ $tax ] ) ? array() : array( $terms[ $tax ] ), $tax . '_relationships' );
164
+ }
165
+
166
+ return $term;
167
+ }
168
+
169
+ /**
170
+ * Returns a list of post translations, given a `tax_translations` term ID.
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 );
179
+
180
+ if ( empty( $term_id ) ) {
181
+ return array();
182
  }
183
+
184
+ $translations_term = get_term( $term_id, $this->tax_translations );
185
+
186
+ if ( ! $translations_term instanceof WP_Term || empty( $translations_term->description ) ) {
187
+ return array();
188
  }
189
 
190
+ // Lang slugs as array keys, template IDs as array values.
191
+ $translations = maybe_unserialize( $translations_term->description );
192
+
193
+ return $this->validate_translations( $translations, 0, 'display' );
194
  }
195
 
196
  /**
199
  * @since 2.3
200
  *
201
  * @param int $id Object id ( typically a post_id or term_id ).
202
+ * @param int[] $translations An associative array of translations with language code as key and translation id as
203
+ * value. Make sure to sanitize this.
204
  * @return bool
205
  */
206
  protected function should_update_translation_group( $id, $translations ) {
207
  // Don't do anything if no translations have been added to the group.
208
  $old_translations = $this->get_translations( $id ); // Includes at least $id itself.
209
+ return ! empty( array_diff_assoc( $translations, $old_translations ) );
210
  }
211
 
212
  /**
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 );
223
 
224
+ if ( empty( $id ) ) {
225
+ return array();
226
+ }
227
 
228
+ $lang = $this->get_language( $id );
 
 
 
 
 
229
 
230
+ if ( empty( $lang ) ) {
231
+ return array();
232
+ }
 
 
233
 
234
+ // Sanitize and validate the translations array.
235
+ $translations = $this->validate_translations( $translations, $id );
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
+ // Unlink removed translations.
238
+ $old_translations = $this->get_translations( $id );
 
 
239
 
240
+ foreach ( array_diff_assoc( $old_translations, $translations ) as $object_id ) {
241
+ $this->delete_translation( $object_id );
242
+ }
243
+
244
+ // Check id we need to create or update the translation group.
245
+ if ( ! $this->should_update_translation_group( $id, $translations ) ) {
 
 
246
  return $translations;
247
+ }
248
+
249
+ $terms = wp_get_object_terms( $translations, $this->tax_translations );
250
+ $term = is_array( $terms ) && ! empty( $terms ) ? reset( $terms ) : false;
251
+
252
+ if ( empty( $term ) ) {
253
+ // Create a new term if necessary.
254
+ $group = uniqid( 'pll_' );
255
+ wp_insert_term( $group, $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
256
  } else {
257
+ // Take care not to overwrite extra data stored in the description field, if any.
258
+ $group = (int) $term->term_id;
259
+ $descr = maybe_unserialize( $term->description );
260
+ $descr = is_array( $descr ) ? array_diff_key( $descr, $old_translations ) : array(); // Remove old translations.
261
+ $descr = array_merge( $descr, $translations ); // Add new one.
262
+ wp_update_term( $group, $this->tax_translations, array( 'description' => maybe_serialize( $descr ) ) );
263
  }
 
264
 
265
+ // Link all translations to the new term.
266
+ foreach ( $translations as $p ) {
267
+ wp_set_object_terms( $p, $group, $this->tax_translations );
268
+ }
 
 
 
 
 
 
 
269
 
270
+ if ( ! is_array( $terms ) ) {
271
+ return $translations;
272
+ }
273
+
274
+ // Clean now unused translation groups.
275
+ foreach ( $terms as $term ) {
276
+ // Get fresh count value.
277
+ $term = get_term( $term->term_id, $this->tax_translations );
278
+
279
+ if ( $term instanceof WP_Term && empty( $term->count ) ) {
280
+ wp_delete_term( $term->term_id, $this->tax_translations );
281
+ }
282
  }
283
 
284
+ return $translations;
285
  }
286
 
287
  /**
293
  * @return void
294
  */
295
  public function delete_translation( $id ) {
296
+ $id = $this->sanitize_int_id( $id );
297
+
298
+ if ( empty( $id ) ) {
299
+ return;
300
+ }
301
+
302
  $term = $this->get_object_term( $id, $this->tax_translations );
303
 
304
+ if ( empty( $term ) ) {
305
+ return;
306
+ }
307
+
308
+ $descr = maybe_unserialize( $term->description );
 
309
 
310
+ if ( ! empty( $descr ) && is_array( $descr ) ) {
311
+ $slug = array_search( $id, $this->get_translations( $id ) ); // In case some plugin stores the same value with different key.
312
+
313
+ if ( false !== $slug ) {
314
+ unset( $descr[ $slug ] );
315
  }
316
  }
317
+
318
+ if ( empty( $descr ) || ! is_array( $descr ) ) {
319
+ wp_delete_term( (int) $term->term_id, $this->tax_translations );
320
+ } else {
321
+ wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $descr ) ) );
322
+ }
323
  }
324
 
325
  /**
331
  * @return int[] An associative array of translations with language code as key and translation id as value.
332
  */
333
  public function get_translations( $id ) {
334
+ $id = $this->sanitize_int_id( $id );
 
335
 
336
+ if ( empty( $id ) ) {
337
+ return array();
 
338
  }
339
 
340
+ $term = $this->get_object_term( $id, $this->tax_translations );
341
+ $translations = empty( $term->description ) ? array() : maybe_unserialize( $term->description );
 
 
342
 
343
+ return $this->validate_translations( $translations, $id, 'display' );
344
  }
345
 
346
  /**
353
  * @return int|false Object id of the translation, false if there is none.
354
  */
355
  public function get_translation( $id, $lang ) {
356
+ $lang = $this->model->get_language( $lang );
357
+
358
+ if ( empty( $lang ) ) {
359
  return false;
360
  }
361
 
374
  * @return int|false The translation object id if exists, otherwise the passed id, false if the passed object has no language.
375
  */
376
  public function get( $id, $lang ) {
377
+ $id = $this->sanitize_int_id( $id );
378
+
379
+ if ( empty( $id ) ) {
380
+ return false;
381
+ }
382
+
383
  $lang = $this->model->get_language( $lang );
384
+
385
+ if ( empty( $lang ) ) {
386
+ return false;
387
+ }
388
+
389
  $obj_lang = $this->get_language( $id );
390
+
391
+ if ( empty( $obj_lang ) ) {
392
  return false;
393
  }
394
 
395
+ return $obj_lang->term_id === $lang->term_id ? $id : $this->get_translation( $id, $lang );
396
  }
397
 
398
  /**
438
  }
439
 
440
  /**
441
+ * Checks if a user can synchronize translations.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  *
443
  * @since 2.6
444
  *
446
  * @return bool
447
  */
448
  public function current_user_can_synchronize( $id ) {
449
+ $id = $this->sanitize_int_id( $id );
450
+
451
+ if ( empty( $id ) ) {
452
+ return false;
453
+ }
454
+
455
  /**
456
  * Filters whether a synchronization capability check should take place.
457
  *
464
  * @param int $id The synchronization source object id.
465
  */
466
  $check = apply_filters( "pll_pre_current_user_can_synchronize_{$this->type}", true, $id );
467
+
468
  if ( null !== $check ) {
469
+ return (bool) $check;
470
  }
471
 
472
  if ( ! current_user_can( "edit_{$this->type}", $id ) ) {
481
 
482
  return true;
483
  }
484
+
485
+ /**
486
+ * Sanitizes an ID as positive integer.
487
+ * Kind of similar to `absint()`, but rejects negetive integers instead of making them positive.
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
+ /**
499
+ * Sanitizes an array of IDs as positive integers.
500
+ * `0` values are removed.
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 ) ) {
509
+ return array();
510
+ }
511
+
512
+ $ids = array_map( array( $this, 'sanitize_int_id' ), $ids );
513
+
514
+ return array_filter( $ids );
515
+ }
516
+
517
+ /**
518
+ * Validates and sanitizes translations.
519
+ * This will:
520
+ * - Make sure to return only translations in existing languages (and only translations).
521
+ * - Sanitize the values.
522
+ * - Make sure the provided translation (`$id`) is in the list.
523
+ * - Check that the translated objects are in the right language, if `$context` is set to 'save'.
524
+ *
525
+ * @since 3.1
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' ) {
539
+ if ( ! is_array( $translations ) ) {
540
+ $translations = array();
541
+ }
542
+
543
+ /**
544
+ * Remove translations in non-existing languages, and non-translation data (we allow plugins to store other
545
+ * information in the array).
546
+ */
547
+ $translations = array_intersect_key(
548
+ $translations,
549
+ array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) )
550
+ );
551
+
552
+ // Make sure values are clean before working with them.
553
+ $translations = $this->sanitize_int_ids_list( $translations );
554
+
555
+ if ( 'save' === $context ) {
556
+ /**
557
+ * Check that the translated objects are in the right language.
558
+ * For better performance, this should be done only when saving the data into the database, not when
559
+ * retrieving data from it.
560
+ */
561
+ $valid_translations = array();
562
+
563
+ foreach ( $translations as $lang_slug => $tr_id ) {
564
+ $tr_lang = $this->get_language( $tr_id );
565
+
566
+ if ( ! empty( $tr_lang ) && $tr_lang->slug === $lang_slug ) {
567
+ $valid_translations[ $lang_slug ] = $tr_id;
568
+ }
569
+ }
570
+
571
+ $translations = $valid_translations;
572
+ }
573
+
574
+ $id = $this->sanitize_int_id( $id );
575
+
576
+ if ( empty( $id ) ) {
577
+ return $translations;
578
+ }
579
+
580
+ // Make sure to return at least the passed object in its translation array.
581
+ $lang = $this->get_language( $id );
582
+
583
+ if ( empty( $lang ) ) {
584
+ return $translations;
585
+ }
586
+
587
+ return array_merge( array( $lang->slug => $id ), $translations );
588
+ }
589
  }
include/translated-post.php CHANGED
@@ -47,6 +47,12 @@ class PLL_Translated_Post extends PLL_Translated_Object {
47
  * @return void
48
  */
49
  public function set_language( $post_id, $lang ) {
 
 
 
 
 
 
50
  $old_lang = $this->get_language( $post_id );
51
  $old_lang = $old_lang ? $old_lang->slug : '';
52
 
@@ -54,7 +60,7 @@ class PLL_Translated_Post extends PLL_Translated_Object {
54
  $lang = $lang ? $lang->slug : '';
55
 
56
  if ( $old_lang !== $lang ) {
57
- wp_set_post_terms( (int) $post_id, $lang, 'language' );
58
  }
59
  }
60
 
@@ -67,8 +73,14 @@ class PLL_Translated_Post extends PLL_Translated_Object {
67
  * @return PLL_Language|false PLL_Language object, false if no language is associated to that post
68
  */
69
  public function get_language( $post_id ) {
70
- $lang = $this->get_object_term( $post_id, 'language' );
71
- return ( $lang ) ? $this->model->get_language( $lang ) : false;
 
 
 
 
 
 
72
  }
73
 
74
  /**
@@ -80,6 +92,12 @@ class PLL_Translated_Post extends PLL_Translated_Object {
80
  * @return void
81
  */
82
  public function delete_translation( $id ) {
 
 
 
 
 
 
83
  parent::delete_translation( $id );
84
  wp_set_object_terms( $id, array(), $this->tax_translations );
85
  }
@@ -109,7 +127,7 @@ class PLL_Translated_Post extends PLL_Translated_Object {
109
  */
110
  public function register_taxonomy() {
111
  register_taxonomy(
112
- 'language',
113
  $this->model->get_translated_post_types(),
114
  array(
115
  'labels' => array(
@@ -138,8 +156,8 @@ class PLL_Translated_Post extends PLL_Translated_Object {
138
  */
139
  public function registered_post_type( $post_type ) {
140
  if ( $this->model->is_translated_post_type( $post_type ) ) {
141
- register_taxonomy_for_object_type( 'language', $post_type );
142
- register_taxonomy_for_object_type( 'post_translations', $post_type );
143
  }
144
  }
145
 
@@ -169,6 +187,12 @@ class PLL_Translated_Post extends PLL_Translated_Object {
169
  * @return bool
170
  */
171
  public function current_user_can_read( $post_id, $context = 'view' ) {
 
 
 
 
 
 
172
  $post = get_post( $post_id );
173
 
174
  if ( empty( $post ) ) {
@@ -189,9 +213,19 @@ class PLL_Translated_Post extends PLL_Translated_Object {
189
 
190
  // Follow WP practices, which shows links to private posts ( when readable ), but not for draft posts ( ex: get_adjacent_post_link() )
191
  if ( in_array( $post->post_status, get_post_stati( array( 'private' => true ) ) ) ) {
192
- $post_type_object = get_post_type_object( $post->post_type );
 
 
 
193
  $user = wp_get_current_user();
194
- return is_user_logged_in() && ( current_user_can( $post_type_object->cap->read_private_posts ) || $user->ID == $post->post_author ); // Comparison must not be strict!
 
 
 
 
 
 
 
195
  }
196
 
197
  // In edit context, show draft and future posts.
@@ -237,7 +271,7 @@ class PLL_Translated_Post extends PLL_Translated_Object {
237
  'post_type' => $type,
238
  'tax_query' => array(
239
  array(
240
- 'taxonomy' => 'language',
241
  'field' => 'term_taxonomy_id', // WP 3.5+
242
  'terms' => $lang->term_taxonomy_id,
243
  ),
47
  * @return void
48
  */
49
  public function set_language( $post_id, $lang ) {
50
+ $post_id = $this->sanitize_int_id( $post_id );
51
+
52
+ if ( empty( $post_id ) ) {
53
+ return;
54
+ }
55
+
56
  $old_lang = $this->get_language( $post_id );
57
  $old_lang = $old_lang ? $old_lang->slug : '';
58
 
60
  $lang = $lang ? $lang->slug : '';
61
 
62
  if ( $old_lang !== $lang ) {
63
+ wp_set_post_terms( $post_id, $lang, $this->tax_language );
64
  }
65
  }
66
 
73
  * @return PLL_Language|false PLL_Language object, false if no language is associated to that post
74
  */
75
  public function get_language( $post_id ) {
76
+ $post_id = $this->sanitize_int_id( $post_id );
77
+
78
+ if ( empty( $post_id ) ) {
79
+ return false;
80
+ }
81
+
82
+ $lang = $this->get_object_term( $post_id, $this->tax_language );
83
+ return ! empty( $lang ) ? $this->model->get_language( $lang ) : false;
84
  }
85
 
86
  /**
92
  * @return void
93
  */
94
  public function delete_translation( $id ) {
95
+ $id = $this->sanitize_int_id( $id );
96
+
97
+ if ( empty( $id ) ) {
98
+ return;
99
+ }
100
+
101
  parent::delete_translation( $id );
102
  wp_set_object_terms( $id, array(), $this->tax_translations );
103
  }
127
  */
128
  public function register_taxonomy() {
129
  register_taxonomy(
130
+ $this->tax_language,
131
  $this->model->get_translated_post_types(),
132
  array(
133
  'labels' => array(
156
  */
157
  public function registered_post_type( $post_type ) {
158
  if ( $this->model->is_translated_post_type( $post_type ) ) {
159
+ register_taxonomy_for_object_type( $this->tax_language, $post_type );
160
+ register_taxonomy_for_object_type( $this->tax_translations, $post_type );
161
  }
162
  }
163
 
187
  * @return bool
188
  */
189
  public function current_user_can_read( $post_id, $context = 'view' ) {
190
+ $post_id = $this->sanitize_int_id( $post_id );
191
+
192
+ if ( empty( $post_id ) ) {
193
+ return false;
194
+ }
195
+
196
  $post = get_post( $post_id );
197
 
198
  if ( empty( $post ) ) {
213
 
214
  // Follow WP practices, which shows links to private posts ( when readable ), but not for draft posts ( ex: get_adjacent_post_link() )
215
  if ( in_array( $post->post_status, get_post_stati( array( 'private' => true ) ) ) ) {
216
+ if ( ! is_user_logged_in() ) {
217
+ return false;
218
+ }
219
+
220
  $user = wp_get_current_user();
221
+
222
+ if ( (int) $user->ID === (int) $post->post_author ) {
223
+ return true;
224
+ }
225
+
226
+ $post_type_object = get_post_type_object( $post->post_type );
227
+
228
+ return ! empty( $post_type_object ) && current_user_can( $post_type_object->cap->read_private_posts );
229
  }
230
 
231
  // In edit context, show draft and future posts.
271
  'post_type' => $type,
272
  'tax_query' => array(
273
  array(
274
+ 'taxonomy' => $this->tax_language,
275
  'field' => 'term_taxonomy_id', // WP 3.5+
276
  'terms' => $lang->term_taxonomy_id,
277
  ),
include/translated-term.php CHANGED
@@ -43,7 +43,11 @@ class PLL_Translated_Term extends PLL_Translated_Object {
43
  * @return void
44
  */
45
  public function set_language( $term_id, $lang ) {
46
- $term_id = (int) $term_id;
 
 
 
 
47
 
48
  $old_lang = $this->get_language( $term_id );
49
  $old_lang = $old_lang ? $old_lang->tl_term_id : '';
@@ -51,17 +55,21 @@ class PLL_Translated_Term extends PLL_Translated_Object {
51
  $lang = $this->model->get_language( $lang );
52
  $lang = $lang ? $lang->tl_term_id : '';
53
 
54
- if ( $old_lang !== $lang ) {
55
- wp_set_object_terms( $term_id, $lang, 'term_language' );
 
 
 
56
 
57
- // Add translation group for correct WXR export
58
- $translations = $this->get_translations( $term_id );
59
- if ( $slug = array_search( $term_id, $translations ) ) {
60
- unset( $translations[ $slug ] );
61
- }
62
 
63
- $this->save_translations( $term_id, $translations );
 
64
  }
 
 
65
  }
66
 
67
  /**
@@ -73,7 +81,7 @@ class PLL_Translated_Term extends PLL_Translated_Object {
73
  * @return void
74
  */
75
  public function delete_language( $term_id ) {
76
- wp_delete_object_term_relationships( $term_id, 'term_language' );
77
  }
78
 
79
  /**
@@ -87,7 +95,7 @@ class PLL_Translated_Term extends PLL_Translated_Object {
87
  */
88
  public function get_language( $value, $taxonomy = '' ) {
89
  if ( is_numeric( $value ) ) {
90
- $term_id = $value;
91
  }
92
 
93
  // get_term_by still not cached in WP 3.5.1 but internally, the function is always called by term_id
@@ -98,8 +106,18 @@ class PLL_Translated_Term extends PLL_Translated_Object {
98
  }
99
  }
100
 
101
- // Get the language and make sure it is a PLL_Language object
102
- return isset( $term_id ) && ( $lang = $this->get_object_term( $term_id, 'term_language' ) ) ? $this->model->get_language( $lang->term_id ) : false;
 
 
 
 
 
 
 
 
 
 
103
  }
104
 
105
  /**
@@ -108,19 +126,20 @@ class PLL_Translated_Term extends PLL_Translated_Object {
108
  * @since 2.3
109
  *
110
  * @param int $id Post id or term id.
111
- * @param int[] $translations An associative array of translations with language code as key and translation id as value.
 
112
  * @return bool
113
  */
114
  protected function should_update_translation_group( $id, $translations ) {
115
  // Don't do anything if no translations have been added to the group
116
  $old_translations = $this->get_translations( $id );
117
- if ( count( $translations ) > 1 && count( array_diff_assoc( $translations, $old_translations ) ) > 0 ) {
118
  return true;
119
  }
120
 
121
  // But we need a translation group for terms to allow relationships remap when importing from a WXR file
122
  $term = $this->get_object_term( $id, $this->tax_translations );
123
- return empty( $term ) || count( array_diff_assoc( $translations, $old_translations ) );
124
  }
125
 
126
  /**
@@ -133,16 +152,18 @@ class PLL_Translated_Term extends PLL_Translated_Object {
133
  */
134
  public function delete_translation( $id ) {
135
  global $wpdb;
 
136
  $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
137
 
138
  parent::delete_translation( $id );
139
- wp_delete_object_term_relationships( $id, 'term_translations' );
140
 
141
  if ( ! doing_action( 'pre_delete_term' ) && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->terms WHERE term_id = %d;", $id ) ) ) {
142
  // Always keep a group for terms to allow relationships remap when importing from a WXR file
 
143
  $translations = array( $slug => $id );
144
- wp_insert_term( $group = uniqid( 'pll_' ), 'term_translations', array( 'description' => maybe_serialize( $translations ) ) );
145
- wp_set_object_terms( $id, $group, 'term_translations' );
146
  }
147
  }
148
 
@@ -195,7 +216,7 @@ class PLL_Translated_Term extends PLL_Translated_Object {
195
  * @return WP_Term[] Unmodified $terms.
196
  */
197
  public function wp_get_object_terms( $terms, $object_ids, $taxonomies ) {
198
- if ( ! in_array( 'term_translations', $taxonomies ) ) {
199
  $this->_prime_terms_cache( $terms, $taxonomies );
200
  }
201
  return $terms;
@@ -210,6 +231,6 @@ class PLL_Translated_Term extends PLL_Translated_Object {
210
  * @return void
211
  */
212
  public function clean_term_cache( $ids ) {
213
- clean_object_term_cache( $ids, 'term' );
214
  }
215
  }
43
  * @return void
44
  */
45
  public function set_language( $term_id, $lang ) {
46
+ $term_id = $this->sanitize_int_id( $term_id );
47
+
48
+ if ( empty( $term_id ) ) {
49
+ return;
50
+ }
51
 
52
  $old_lang = $this->get_language( $term_id );
53
  $old_lang = $old_lang ? $old_lang->tl_term_id : '';
55
  $lang = $this->model->get_language( $lang );
56
  $lang = $lang ? $lang->tl_term_id : '';
57
 
58
+ if ( $old_lang === $lang ) {
59
+ return;
60
+ }
61
+
62
+ wp_set_object_terms( $term_id, $lang, $this->tax_language );
63
 
64
+ // Add translation group for correct WXR export.
65
+ $translations = $this->get_translations( $term_id );
66
+ $slug = array_search( $term_id, $translations );
 
 
67
 
68
+ if ( ! empty( $slug ) ) {
69
+ unset( $translations[ $slug ] );
70
  }
71
+
72
+ $this->save_translations( $term_id, $translations );
73
  }
74
 
75
  /**
81
  * @return void
82
  */
83
  public function delete_language( $term_id ) {
84
+ wp_delete_object_term_relationships( $this->sanitize_int_id( $term_id ), $this->tax_language );
85
  }
86
 
87
  /**
95
  */
96
  public function get_language( $value, $taxonomy = '' ) {
97
  if ( is_numeric( $value ) ) {
98
+ $term_id = $this->sanitize_int_id( $value );
99
  }
100
 
101
  // get_term_by still not cached in WP 3.5.1 but internally, the function is always called by term_id
106
  }
107
  }
108
 
109
+ if ( empty( $term_id ) ) {
110
+ return false;
111
+ }
112
+
113
+ // Get the language and make sure it is a PLL_Language object.
114
+ $lang = $this->get_object_term( $term_id, $this->tax_language );
115
+
116
+ if ( empty( $lang ) ) {
117
+ return false;
118
+ }
119
+
120
+ return $this->model->get_language( $lang->term_id );
121
  }
122
 
123
  /**
126
  * @since 2.3
127
  *
128
  * @param int $id Post id or term id.
129
+ * @param int[] $translations An associative array of translations with language code as key and translation id as
130
+ * value. Make sure to sanitize this.
131
  * @return bool
132
  */
133
  protected function should_update_translation_group( $id, $translations ) {
134
  // Don't do anything if no translations have been added to the group
135
  $old_translations = $this->get_translations( $id );
136
+ if ( count( $translations ) > 1 && ! empty( array_diff_assoc( $translations, $old_translations ) ) ) {
137
  return true;
138
  }
139
 
140
  // But we need a translation group for terms to allow relationships remap when importing from a WXR file
141
  $term = $this->get_object_term( $id, $this->tax_translations );
142
+ return empty( $term ) || ! empty( array_diff_assoc( $translations, $old_translations ) );
143
  }
144
 
145
  /**
152
  */
153
  public function delete_translation( $id ) {
154
  global $wpdb;
155
+ $id = $this->sanitize_int_id( $id );
156
  $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
157
 
158
  parent::delete_translation( $id );
159
+ wp_delete_object_term_relationships( $id, $this->tax_translations );
160
 
161
  if ( ! doing_action( 'pre_delete_term' ) && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->terms WHERE term_id = %d;", $id ) ) ) {
162
  // Always keep a group for terms to allow relationships remap when importing from a WXR file
163
+ $group = uniqid( 'pll_' );
164
  $translations = array( $slug => $id );
165
+ wp_insert_term( $group, $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
166
+ wp_set_object_terms( $id, $group, $this->tax_translations );
167
  }
168
  }
169
 
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;
231
  * @return void
232
  */
233
  public function clean_term_cache( $ids ) {
234
+ clean_object_term_cache( $this->sanitize_int_ids_list( $ids ), 'term' );
235
  }
236
  }
include/walker-dropdown.php CHANGED
@@ -31,12 +31,12 @@ class PLL_Walker_Dropdown extends Walker {
31
  * @return void
32
  */
33
  public function start_el( &$output, $element, $depth = 0, $args = array(), $current_object_id = 0 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
34
- $value = $args['value'];
35
  $output .= sprintf(
36
  "\t" . '<option value="%1$s"%2$s%3$s>%4$s</option>' . "\n",
37
- esc_attr( $element->$value ),
38
  method_exists( $element, 'get_locale' ) ? sprintf( ' lang="%s"', esc_attr( $element->get_locale( 'display' ) ) ) : '',
39
- selected( isset( $args['selected'] ) && $args['selected'] === $element->$value, true, false ),
40
  esc_html( $element->name )
41
  );
42
  }
31
  * @return void
32
  */
33
  public function start_el( &$output, $element, $depth = 0, $args = array(), $current_object_id = 0 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
34
+ $value_type = $args['value'];
35
  $output .= sprintf(
36
  "\t" . '<option value="%1$s"%2$s%3$s>%4$s</option>' . "\n",
37
+ 'url' === $value_type ? esc_url( $element->$value_type ) : esc_attr( $element->$value_type ),
38
  method_exists( $element, 'get_locale' ) ? sprintf( ' lang="%s"', esc_attr( $element->get_locale( 'display' ) ) ) : '',
39
+ selected( isset( $args['selected'] ) && $args['selected'] === $element->$value_type, true, false ),
40
  esc_html( $element->name )
41
  );
42
  }
include/widget-languages.php CHANGED
@@ -67,7 +67,7 @@ class PLL_Widget_Languages extends WP_Widget {
67
  $format = apply_filters( 'navigation_widgets_format', $format );
68
 
69
  if ( 'html5' === $format ) {
70
- echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
71
  }
72
 
73
  echo "<ul>\n" . $list . "</ul>\n"; // phpcs:ignore WordPress.Security.EscapeOutput
67
  $format = apply_filters( 'navigation_widgets_format', $format );
68
 
69
  if ( 'html5' === $format ) {
70
+ echo '<nav aria-label="' . esc_attr( $aria_label ) . '">';
71
  }
72
 
73
  echo "<ul>\n" . $list . "</ul>\n"; // phpcs:ignore WordPress.Security.EscapeOutput
install/plugin-updater.php CHANGED
@@ -7,22 +7,22 @@ if ( ! defined( 'ABSPATH' ) ) {
7
 
8
  /**
9
  * Allows plugins to use their own update API.
10
- * Modified version with 'polylang' text domain and comments for translators.
11
  *
12
  * @author Easy Digital Downloads
13
- * @version 1.8.0
14
  */
15
  class PLL_Plugin_Updater {
16
 
17
- private $api_url = '';
18
- private $api_data = array();
19
- private $name = '';
20
- private $slug = '';
21
- private $version = '';
22
- private $wp_override = false;
23
- private $cache_key = '';
24
-
25
- private $health_check_timeout = 5;
26
 
27
  /**
28
  * Class constructor.
@@ -38,14 +38,15 @@ class PLL_Plugin_Updater {
38
 
39
  global $edd_plugin_data;
40
 
41
- $this->api_url = trailingslashit( $_api_url );
42
- $this->api_data = $_api_data;
43
- $this->name = plugin_basename( $_plugin_file );
44
- $this->slug = basename( $_plugin_file, '.php' );
45
- $this->version = $_api_data['version'];
46
- $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
47
- $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
48
- $this->cache_key = 'edd_sl_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
 
49
 
50
  $edd_plugin_data[ $this->slug ] = $this->api_data;
51
 
@@ -74,8 +75,7 @@ class PLL_Plugin_Updater {
74
 
75
  add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
76
  add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
77
- remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
78
- add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
79
  add_action( 'admin_init', array( $this, 'show_changelog' ) );
80
 
81
  }
@@ -98,11 +98,7 @@ class PLL_Plugin_Updater {
98
  global $pagenow;
99
 
100
  if ( ! is_object( $_transient_data ) ) {
101
- $_transient_data = new stdClass;
102
- }
103
-
104
- if ( 'plugins.php' == $pagenow && is_multisite() ) {
105
- return $_transient_data;
106
  }
107
 
108
  if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
@@ -156,124 +152,127 @@ class PLL_Plugin_Updater {
156
  }
157
 
158
  /**
159
- * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
160
  *
161
  * @param string $file
162
  * @param array $plugin
163
  */
164
  public function show_update_notification( $file, $plugin ) {
165
 
166
- if ( is_network_admin() ) {
167
- return;
168
- }
169
-
170
- if( ! current_user_can( 'update_plugins' ) ) {
171
  return;
172
  }
173
 
174
- if( ! is_multisite() ) {
 
175
  return;
176
  }
177
 
178
- if ( $this->name != $file ) {
179
  return;
180
  }
181
 
182
- // Remove our filter on the site transient
183
- remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
184
-
185
  $update_cache = get_site_transient( 'update_plugins' );
186
 
187
- $update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
188
-
189
- if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
190
-
191
- $version_info = $this->get_repo_api_data();
192
-
193
- if ( false === $version_info ) {
194
- $version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
195
-
196
- // Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
197
- if ( isset( $version_info->banners ) && ! is_array( $version_info->banners ) ) {
198
- $version_info->banners = $this->convert_object_to_array( $version_info->banners );
199
- }
200
-
201
- if ( isset( $version_info->sections ) && ! is_array( $version_info->sections ) ) {
202
- $version_info->sections = $this->convert_object_to_array( $version_info->sections );
203
- }
204
-
205
- if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
206
- $version_info->icons = $this->convert_object_to_array( $version_info->icons );
207
- }
208
-
209
- if ( isset( $version_info->contributors ) && ! is_array( $version_info->contributors ) ) {
210
- $version_info->contributors = $this->convert_object_to_array( $version_info->contributors );
211
- }
212
-
213
- $this->set_version_info_cache( $version_info );
214
- }
215
-
216
- if ( ! is_object( $version_info ) ) {
217
- return;
218
  }
 
 
219
 
220
- if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
221
- $update_cache->response[ $this->name ] = $version_info;
222
- } else {
223
- $update_cache->no_update[ $this->name ] = $version_info;
224
- }
225
 
226
- $update_cache->last_checked = time();
227
- $update_cache->checked[ $this->name ] = $this->version;
 
 
 
 
228
 
229
- set_site_transient( 'update_plugins', $update_cache );
 
230
 
231
- } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
- $version_info = $update_cache->response[ $this->name ];
 
 
 
 
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
- // Restore our filter
238
- add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
239
 
240
- if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
241
-
242
- // build a plugin list row, with update notification
243
- $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
244
- # <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
245
- echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
246
- echo '<td colspan="3" class="plugin-update colspanchange">';
247
- echo '<div class="update-message notice inline notice-warning notice-alt">';
248
-
249
- $changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
250
-
251
- if ( empty( $version_info->download_link ) ) {
252
- printf(
253
- /* translators: %1$s plugin name, %3$s plugin version, %2$s is link start tag, %4$s is link end tag. */
254
- __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'polylang' ),
255
- esc_html( $version_info->name ),
256
- '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
257
- esc_html( $version_info->new_version ),
258
- '</a>'
259
- );
260
- } else {
261
- printf(
262
- /* translators: %1$s plugin name, %3$s plugin version, %2$s and %5$s are link start tags, %4$s and %6$s are link end tags. */
263
- __( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'polylang' ),
264
- esc_html( $version_info->name ),
265
- '<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
266
- esc_html( $version_info->new_version ),
267
- '</a>',
268
- '<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
269
- '</a>'
270
- );
271
- }
272
 
273
- do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
 
 
 
 
 
 
 
274
 
275
- echo '</div></td></tr>';
276
- }
277
  }
278
 
279
  /**
@@ -288,13 +287,13 @@ class PLL_Plugin_Updater {
288
  */
289
  public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
290
 
291
- if ( $_action != 'plugin_information' ) {
292
 
293
  return $_data;
294
 
295
  }
296
 
297
- if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
298
 
299
  return $_data;
300
 
@@ -307,7 +306,7 @@ class PLL_Plugin_Updater {
307
  'banners' => array(),
308
  'reviews' => false,
309
  'icons' => array(),
310
- )
311
  );
312
 
313
  // Get the transient where we store the api request for this plugin for 24 hours
@@ -324,7 +323,6 @@ class PLL_Plugin_Updater {
324
  if ( false !== $api_response ) {
325
  $_data = $api_response;
326
  }
327
-
328
  } else {
329
  $_data = $edd_api_request_transient;
330
  }
@@ -349,7 +347,7 @@ class PLL_Plugin_Updater {
349
  $_data->contributors = $this->convert_object_to_array( $_data->contributors );
350
  }
351
 
352
- if( ! isset( $_data->plugin ) ) {
353
  $_data->plugin = $this->name;
354
  }
355
 
@@ -369,6 +367,9 @@ class PLL_Plugin_Updater {
369
  * @return array
370
  */
371
  private function convert_object_to_array( $data ) {
 
 
 
372
  $new_data = array();
373
  foreach ( $data as $key => $value ) {
374
  $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value;
@@ -386,9 +387,8 @@ class PLL_Plugin_Updater {
386
  */
387
  public function http_request_args( $args, $url ) {
388
 
389
- $verify_ssl = $this->verify_ssl();
390
  if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
391
- $args['sslverify'] = $verify_ssl;
392
  }
393
  return $args;
394
 
@@ -403,85 +403,68 @@ class PLL_Plugin_Updater {
403
  *
404
  * @param string $_action The requested action.
405
  * @param array $_data Parameters for the API action.
406
- * @return false|object
407
  */
408
  private function api_request( $_action, $_data ) {
 
409
 
410
- global $wp_version, $edd_plugin_url_available;
411
-
412
- $verify_ssl = $this->verify_ssl();
413
-
414
- // Do a quick status check on this domain if we haven't already checked it.
415
- $store_hash = md5( $this->api_url );
416
- if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
417
- $test_url_parts = parse_url( $this->api_url );
418
-
419
- $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http';
420
- $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : '';
421
- $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : '';
422
-
423
- if ( empty( $host ) ) {
424
- $edd_plugin_url_available[ $store_hash ] = false;
425
- } else {
426
- $test_url = $scheme . '://' . $host . $port;
427
- $response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl ) );
428
- $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
429
- }
430
  }
431
 
432
- if ( false === $edd_plugin_url_available[ $store_hash ] ) {
 
433
  return false;
434
  }
435
 
436
- $data = array_merge( $this->api_data, $_data );
437
-
438
- if ( $data['slug'] != $this->slug ) {
439
  return false;
440
  }
441
 
442
- if ( $this->api_url == trailingslashit ( home_url() ) ) {
443
- return false; // Don't allow a plugin to ping itself
444
- }
445
-
446
- $api_params = array(
447
- 'edd_action' => 'get_version',
448
- 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
449
- 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
450
- 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
451
- 'version' => isset( $data['version'] ) ? $data['version'] : false,
452
- 'slug' => $data['slug'],
453
- 'author' => $data['author'],
454
- 'url' => home_url(),
455
- 'beta' => ! empty( $data['beta'] ),
456
- );
457
-
458
- $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
459
 
460
- if ( ! is_wp_error( $request ) ) {
461
- $request = json_decode( wp_remote_retrieve_body( $request ) );
462
- }
 
 
 
 
 
 
463
 
464
- if ( $request && isset( $request->sections ) ) {
465
- $request->sections = maybe_unserialize( $request->sections );
466
- } else {
467
- $request = false;
468
  }
469
 
470
- if ( $request && isset( $request->banners ) ) {
471
- $request->banners = maybe_unserialize( $request->banners );
472
- }
 
 
 
473
 
474
- if ( $request && isset( $request->icons ) ) {
475
- $request->icons = maybe_unserialize( $request->icons );
476
  }
477
 
478
- if ( ! empty( $request->sections ) ) {
479
- foreach( $request->sections as $key => $section ) {
480
- $request->$key = (array) $section;
481
- }
482
- }
483
 
484
- return $request;
 
 
 
 
 
 
 
 
 
 
 
 
485
  }
486
 
487
  /**
@@ -489,90 +472,119 @@ class PLL_Plugin_Updater {
489
  */
490
  public function show_changelog() {
491
 
492
- global $edd_plugin_data;
493
-
494
- if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
495
  return;
496
  }
497
 
498
- if( empty( $_REQUEST['plugin'] ) ) {
499
  return;
500
  }
501
 
502
- if( empty( $_REQUEST['slug'] ) ) {
503
  return;
504
  }
505
 
506
- if( ! current_user_can( 'update_plugins' ) ) {
507
- wp_die( __( 'You do not have permission to install plugin updates', 'polylang' ), __( 'Error', 'polylang' ), array( 'response' => 403 ) );
508
  }
509
 
510
- $data = $edd_plugin_data[ $_REQUEST['slug'] ];
511
- $version_info = $this->get_cached_version_info();
 
 
 
 
 
 
 
 
512
 
513
- if( false === $version_info ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
 
515
- $api_params = array(
516
- 'edd_action' => 'get_version',
517
- 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
518
- 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
519
- 'slug' => $_REQUEST['slug'],
520
- 'author' => $data['author'],
521
- 'url' => home_url(),
522
- 'beta' => ! empty( $data['beta'] )
523
- );
 
 
 
 
 
 
 
 
524
 
525
- $verify_ssl = $this->verify_ssl();
526
- $request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
527
 
528
- if ( ! is_wp_error( $request ) ) {
529
- $version_info = json_decode( wp_remote_retrieve_body( $request ) );
530
- }
531
 
532
- if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
533
- $version_info->sections = maybe_unserialize( $version_info->sections );
534
- } else {
535
- $version_info = false;
536
- }
537
 
538
- if( ! empty( $version_info ) ) {
539
- foreach( $version_info->sections as $key => $section ) {
540
- $version_info->$key = (array) $section;
541
- }
542
- }
543
 
544
- $this->set_version_info_cache( $version_info );
 
 
545
 
546
- // Delete the unneeded option
547
- delete_option( md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $this->beta . '_version_info' ) );
548
  }
549
 
550
- if ( isset( $version_info->sections ) ) {
551
- $sections = $this->convert_object_to_array( $version_info->sections );
552
- if ( ! empty( $sections['changelog'] ) ) {
553
- echo '<div style="background:#fff;padding:10px;">' . wp_kses_post( $sections['changelog'] ) . '</div>';
554
  }
555
  }
556
 
557
- exit;
558
  }
559
 
560
  /**
561
- * Gets the plugin's cached version information from the database.
562
  *
563
  * @param string $cache_key
564
- * @return boolean|string
565
  */
566
  public function get_cached_version_info( $cache_key = '' ) {
567
 
568
- if( empty( $cache_key ) ) {
569
- $cache_key = $this->cache_key;
570
  }
571
 
572
  $cache = get_option( $cache_key );
573
 
574
- if( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
575
- return false; // Cache is expired
 
576
  }
577
 
578
  // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
@@ -593,13 +605,13 @@ class PLL_Plugin_Updater {
593
  */
594
  public function set_version_info_cache( $value = '', $cache_key = '' ) {
595
 
596
- if( empty( $cache_key ) ) {
597
- $cache_key = $this->cache_key;
598
  }
599
 
600
  $data = array(
601
  'timeout' => strtotime( '+3 hours', time() ),
602
- 'value' => json_encode( $value )
603
  );
604
 
605
  update_option( $cache_key, $data, 'no' );
@@ -618,4 +630,16 @@ class PLL_Plugin_Updater {
618
  return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
619
  }
620
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  }
7
 
8
  /**
9
  * Allows plugins to use their own update API.
10
+ * Modified version with 'polylang' text domain and missing comments for translators.
11
  *
12
  * @author Easy Digital Downloads
13
+ * @version 1.9.1
14
  */
15
  class PLL_Plugin_Updater {
16
 
17
+ private $api_url = '';
18
+ private $api_data = array();
19
+ private $plugin_file = '';
20
+ private $name = '';
21
+ private $slug = '';
22
+ private $version = '';
23
+ private $wp_override = false;
24
+ private $beta = false;
25
+ private $failed_request_cache_key;
26
 
27
  /**
28
  * Class constructor.
38
 
39
  global $edd_plugin_data;
40
 
41
+ $this->api_url = trailingslashit( $_api_url );
42
+ $this->api_data = $_api_data;
43
+ $this->plugin_file = $_plugin_file;
44
+ $this->name = plugin_basename( $_plugin_file );
45
+ $this->slug = basename( $_plugin_file, '.php' );
46
+ $this->version = $_api_data['version'];
47
+ $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
48
+ $this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
49
+ $this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url );
50
 
51
  $edd_plugin_data[ $this->slug ] = $this->api_data;
52
 
75
 
76
  add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
77
  add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
78
+ add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 );
 
79
  add_action( 'admin_init', array( $this, 'show_changelog' ) );
80
 
81
  }
98
  global $pagenow;
99
 
100
  if ( ! is_object( $_transient_data ) ) {
101
+ $_transient_data = new stdClass();
 
 
 
 
102
  }
103
 
104
  if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
152
  }
153
 
154
  /**
155
+ * Show the update notification on multisite subsites.
156
  *
157
  * @param string $file
158
  * @param array $plugin
159
  */
160
  public function show_update_notification( $file, $plugin ) {
161
 
162
+ // Return early if in the network admin, or if this is not a multisite install.
163
+ if ( is_network_admin() || ! is_multisite() ) {
 
 
 
164
  return;
165
  }
166
 
167
+ // Allow single site admins to see that an update is available.
168
+ if ( ! current_user_can( 'activate_plugins' ) ) {
169
  return;
170
  }
171
 
172
+ if ( $this->name !== $file ) {
173
  return;
174
  }
175
 
176
+ // Do not print any message if update does not exist.
 
 
177
  $update_cache = get_site_transient( 'update_plugins' );
178
 
179
+ if ( ! isset( $update_cache->response[ $this->name ] ) ) {
180
+ if ( ! is_object( $update_cache ) ) {
181
+ $update_cache = new stdClass();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  }
183
+ $update_cache->response[ $this->name ] = $this->get_repo_api_data();
184
+ }
185
 
186
+ // Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin.
187
+ if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) {
188
+ return;
189
+ }
 
190
 
191
+ printf(
192
+ '<tr class="plugin-update-tr %3$s" id="%1$s-update" data-slug="%1$s" data-plugin="%2$s">',
193
+ $this->slug,
194
+ $file,
195
+ in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive'
196
+ );
197
 
198
+ echo '<td colspan="3" class="plugin-update colspanchange">';
199
+ echo '<div class="update-message notice inline notice-warning notice-alt"><p>';
200
 
201
+ $changelog_link = '';
202
+ if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) {
203
+ $changelog_link = add_query_arg(
204
+ array(
205
+ 'edd_sl_action' => 'view_plugin_changelog',
206
+ 'plugin' => urlencode( $this->name ),
207
+ 'slug' => urlencode( $this->slug ),
208
+ 'TB_iframe' => 'true',
209
+ 'width' => 77,
210
+ 'height' => 911,
211
+ ),
212
+ self_admin_url( 'index.php' )
213
+ );
214
+ }
215
+ $update_link = add_query_arg(
216
+ array(
217
+ 'action' => 'upgrade-plugin',
218
+ 'plugin' => urlencode( $this->name ),
219
+ ),
220
+ self_admin_url( 'update.php' )
221
+ );
222
 
223
+ printf(
224
+ /* translators: the plugin name. */
225
+ esc_html__( 'There is a new version of %1$s available.', 'polylang' ),
226
+ esc_html( $plugin['Name'] )
227
+ );
228
 
229
+ if ( ! current_user_can( 'update_plugins' ) ) {
230
+ echo ' ';
231
+ esc_html_e( 'Contact your network administrator to install the update.', 'polylang' );
232
+ } elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) {
233
+ echo ' ';
234
+ printf(
235
+ /* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */
236
+ __( '%1$sView version %2$s details%3$s.', 'polylang' ),
237
+ '<a target="_blank" class="thickbox open-plugin-details-modal" href="' . esc_url( $changelog_link ) . '">',
238
+ esc_html( $update_cache->response[ $this->name ]->new_version ),
239
+ '</a>'
240
+ );
241
+ } elseif ( ! empty( $changelog_link ) ) {
242
+ echo ' ';
243
+ printf(
244
+ /* translators: 1. and 4. are opening anchor tags 2. the new plugin version 3. and 5. are closing anchor tags. */
245
+ __( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'polylang' ),
246
+ '<a target="_blank" class="thickbox open-plugin-details-modal" href="' . esc_url( $changelog_link ) . '">',
247
+ esc_html( $update_cache->response[ $this->name ]->new_version ),
248
+ '</a>',
249
+ '<a target="_blank" class="update-link" href="' . esc_url( wp_nonce_url( $update_link, 'upgrade-plugin_' . $file ) ) . '">',
250
+ '</a>'
251
+ );
252
+ } else {
253
+ printf(
254
+ ' %1$s%2$s%3$s',
255
+ '<a target="_blank" class="update-link" href="' . esc_url( wp_nonce_url( $update_link, 'upgrade-plugin_' . $file ) ) . '">',
256
+ esc_html__( 'Update now.', 'polylang' ),
257
+ '</a>'
258
+ );
259
  }
260
 
261
+ do_action( "in_plugin_update_message-{$file}", $plugin, $plugin );
 
262
 
263
+ echo '</p></div></td></tr>';
264
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
+ /**
267
+ * Gets the plugins active in a multisite network.
268
+ *
269
+ * @return array
270
+ */
271
+ private function get_active_plugins() {
272
+ $active_plugins = (array) get_option( 'active_plugins' );
273
+ $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' );
274
 
275
+ return array_merge( $active_plugins, array_keys( $active_network_plugins ) );
 
276
  }
277
 
278
  /**
287
  */
288
  public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
289
 
290
+ if ( 'plugin_information' !== $_action ) {
291
 
292
  return $_data;
293
 
294
  }
295
 
296
+ if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) {
297
 
298
  return $_data;
299
 
306
  'banners' => array(),
307
  'reviews' => false,
308
  'icons' => array(),
309
+ ),
310
  );
311
 
312
  // Get the transient where we store the api request for this plugin for 24 hours
323
  if ( false !== $api_response ) {
324
  $_data = $api_response;
325
  }
 
326
  } else {
327
  $_data = $edd_api_request_transient;
328
  }
347
  $_data->contributors = $this->convert_object_to_array( $_data->contributors );
348
  }
349
 
350
+ if ( ! isset( $_data->plugin ) ) {
351
  $_data->plugin = $this->name;
352
  }
353
 
367
  * @return array
368
  */
369
  private function convert_object_to_array( $data ) {
370
+ if ( ! is_array( $data ) && ! is_object( $data ) ) {
371
+ return array();
372
+ }
373
  $new_data = array();
374
  foreach ( $data as $key => $value ) {
375
  $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value;
387
  */
388
  public function http_request_args( $args, $url ) {
389
 
 
390
  if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
391
+ $args['sslverify'] = $this->verify_ssl();
392
  }
393
  return $args;
394
 
403
  *
404
  * @param string $_action The requested action.
405
  * @param array $_data Parameters for the API action.
406
+ * @return false|object|void
407
  */
408
  private function api_request( $_action, $_data ) {
409
+ $data = array_merge( $this->api_data, $_data );
410
 
411
+ if ( $data['slug'] !== $this->slug ) {
412
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  }
414
 
415
+ // Don't allow a plugin to ping itself
416
+ if ( trailingslashit( home_url() ) === $this->api_url ) {
417
  return false;
418
  }
419
 
420
+ if ( $this->request_recently_failed() ) {
 
 
421
  return false;
422
  }
423
 
424
+ return $this->get_version_from_remote();
425
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
+ /**
428
+ * Determines if a request has recently failed.
429
+ *
430
+ * @since 1.9.1
431
+ *
432
+ * @return bool
433
+ */
434
+ private function request_recently_failed() {
435
+ $failed_request_details = get_option( $this->failed_request_cache_key );
436
 
437
+ // Request has never failed.
438
+ if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) {
439
+ return false;
 
440
  }
441
 
442
+ /*
443
+ * Request previously failed, but the timeout has expired.
444
+ * This means we're allowed to try again.
445
+ */
446
+ if ( time() > $failed_request_details ) {
447
+ delete_option( $this->failed_request_cache_key );
448
 
449
+ return false;
 
450
  }
451
 
452
+ return true;
453
+ }
 
 
 
454
 
455
+ /**
456
+ * Logs a failed HTTP request for this API URL.
457
+ * We set a timestamp for 1 hour from now. This prevents future API requests from being
458
+ * made to this domain for 1 hour. Once the timestamp is in the past, API requests
459
+ * will be allowed again. This way if the site is down for some reason we don't bombard
460
+ * it with failed API requests.
461
+ *
462
+ * @see EDD_SL_Plugin_Updater::request_recently_failed
463
+ *
464
+ * @since 1.9.1
465
+ */
466
+ private function log_failed_request() {
467
+ update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ) );
468
  }
469
 
470
  /**
472
  */
473
  public function show_changelog() {
474
 
475
+ if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) {
 
 
476
  return;
477
  }
478
 
479
+ if ( empty( $_REQUEST['plugin'] ) ) {
480
  return;
481
  }
482
 
483
+ if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) {
484
  return;
485
  }
486
 
487
+ if ( ! current_user_can( 'update_plugins' ) ) {
488
+ wp_die( esc_html__( 'You do not have permission to install plugin updates', 'polylang' ), esc_html__( 'Error', 'polylang' ), array( 'response' => 403 ) );
489
  }
490
 
491
+ $version_info = $this->get_repo_api_data();
492
+ if ( isset( $version_info->sections ) ) {
493
+ $sections = $this->convert_object_to_array( $version_info->sections );
494
+ if ( ! empty( $sections['changelog'] ) ) {
495
+ echo '<div style="background:#fff;padding:10px;">' . wp_kses_post( $sections['changelog'] ) . '</div>';
496
+ }
497
+ }
498
+
499
+ exit;
500
+ }
501
 
502
+ /**
503
+ * Gets the current version information from the remote site.
504
+ *
505
+ * @return array|false
506
+ */
507
+ private function get_version_from_remote() {
508
+ $api_params = array(
509
+ 'edd_action' => 'get_version',
510
+ 'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '',
511
+ 'item_name' => isset( $this->api_data['item_name'] ) ? $this->api_data['item_name'] : false,
512
+ 'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false,
513
+ 'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false,
514
+ 'slug' => $this->slug,
515
+ 'author' => $this->api_data['author'],
516
+ 'url' => home_url(),
517
+ 'beta' => $this->beta,
518
+ 'php_version' => phpversion(),
519
+ 'wp_version' => get_bloginfo( 'version' ),
520
+ );
521
 
522
+ /**
523
+ * Filters the parameters sent in the API request.
524
+ *
525
+ * @param array $api_params The array of data sent in the request.
526
+ * @param array $this->api_data The array of data set up in the class constructor.
527
+ * @param string $this->plugin_file The full path and filename of the file.
528
+ */
529
+ $api_params = apply_filters( 'edd_sl_plugin_updater_api_params', $api_params, $this->api_data, $this->plugin_file );
530
+
531
+ $request = wp_remote_post(
532
+ $this->api_url,
533
+ array(
534
+ 'timeout' => 15,
535
+ 'sslverify' => $this->verify_ssl(),
536
+ 'body' => $api_params,
537
+ )
538
+ );
539
 
540
+ if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) {
541
+ $this->log_failed_request();
542
 
543
+ return false;
544
+ }
 
545
 
546
+ $request = json_decode( wp_remote_retrieve_body( $request ) );
 
 
 
 
547
 
548
+ if ( $request && isset( $request->sections ) ) {
549
+ $request->sections = maybe_unserialize( $request->sections );
550
+ } else {
551
+ $request = false;
552
+ }
553
 
554
+ if ( $request && isset( $request->banners ) ) {
555
+ $request->banners = maybe_unserialize( $request->banners );
556
+ }
557
 
558
+ if ( $request && isset( $request->icons ) ) {
559
+ $request->icons = maybe_unserialize( $request->icons );
560
  }
561
 
562
+ if ( ! empty( $request->sections ) ) {
563
+ foreach ( $request->sections as $key => $section ) {
564
+ $request->$key = (array) $section;
 
565
  }
566
  }
567
 
568
+ return $request;
569
  }
570
 
571
  /**
572
+ * Get the version info from the cache, if it exists.
573
  *
574
  * @param string $cache_key
575
+ * @return object
576
  */
577
  public function get_cached_version_info( $cache_key = '' ) {
578
 
579
+ if ( empty( $cache_key ) ) {
580
+ $cache_key = $this->get_cache_key();
581
  }
582
 
583
  $cache = get_option( $cache_key );
584
 
585
+ // Cache is expired
586
+ if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
587
+ return false;
588
  }
589
 
590
  // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
605
  */
606
  public function set_version_info_cache( $value = '', $cache_key = '' ) {
607
 
608
+ if ( empty( $cache_key ) ) {
609
+ $cache_key = $this->get_cache_key();
610
  }
611
 
612
  $data = array(
613
  'timeout' => strtotime( '+3 hours', time() ),
614
+ 'value' => wp_json_encode( $value ),
615
  );
616
 
617
  update_option( $cache_key, $data, 'no' );
630
  return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
631
  }
632
 
633
+ /**
634
+ * Gets the unique key (option name) for a plugin.
635
+ *
636
+ * @since 1.9.0
637
+ * @return string
638
+ */
639
+ private function get_cache_key() {
640
+ $string = $this->slug . $this->api_data['license'] . $this->beta;
641
+
642
+ return 'edd_sl_' . md5( serialize( $string ) );
643
+ }
644
+
645
  }
integrations/integrations.php CHANGED
@@ -35,7 +35,7 @@ class PLL_Integrations {
35
  *
36
  * @since 1.7
37
  *
38
- * @return object
39
  */
40
  public static function instance() {
41
  if ( empty( self::$instance ) ) {
35
  *
36
  * @since 1.7
37
  *
38
+ * @return PLL_Integrations
39
  */
40
  public static function instance() {
41
  if ( empty( self::$instance ) ) {
integrations/wp-importer/wp-import.php CHANGED
@@ -17,14 +17,14 @@ class PLL_WP_Import extends WP_Import {
17
  public $post_translations = array();
18
 
19
  /**
20
- * Overrides WP_Import::process_terms to remap terms translations
21
  *
22
  * @since 1.2
23
  */
24
  public function process_terms() {
25
  $term_translations = array();
26
 
27
- // Store this for future usage as parent function unsets $this->terms
28
  foreach ( $this->terms as $term ) {
29
  if ( 'post_translations' == $term['term_taxonomy'] ) {
30
  $this->post_translations[] = $term;
@@ -36,16 +36,15 @@ class PLL_WP_Import extends WP_Import {
36
 
37
  parent::process_terms();
38
 
39
- // Update the languages list if needed
40
  // First reset the core terms cache as WordPress Importer calls wp_suspend_cache_invalidation( true );
41
  wp_cache_set( 'last_changed', microtime(), 'terms' );
42
- PLL()->model->clean_languages_cache();
43
 
44
- if ( ( $options = get_option( 'polylang' ) ) && empty( $options['default_lang'] ) && ( $languages = PLL()->model->get_languages_list() ) ) {
45
- // Assign the default language if importer created the first language
 
46
  $default_lang = reset( $languages );
47
- $options['default_lang'] = $default_lang->slug;
48
- update_option( 'polylang', $options );
49
  }
50
 
51
  $this->remap_terms_relations( $term_translations );
17
  public $post_translations = array();
18
 
19
  /**
20
+ * Overrides WP_Import::process_terms to remap terms translations.
21
  *
22
  * @since 1.2
23
  */
24
  public function process_terms() {
25
  $term_translations = array();
26
 
27
+ // Store this for future usage as parent function unsets $this->terms.
28
  foreach ( $this->terms as $term ) {
29
  if ( 'post_translations' == $term['term_taxonomy'] ) {
30
  $this->post_translations[] = $term;
36
 
37
  parent::process_terms();
38
 
 
39
  // First reset the core terms cache as WordPress Importer calls wp_suspend_cache_invalidation( true );
40
  wp_cache_set( 'last_changed', microtime(), 'terms' );
 
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 );
48
  }
49
 
50
  $this->remap_terms_relations( $term_translations );
integrations/wpseo/wpseo.php CHANGED
@@ -39,16 +39,12 @@ class PLL_WPSEO {
39
  }
40
 
41
  add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) );
42
- if ( version_compare( WPSEO_VERSION, '14.0', '<' ) ) {
43
- add_action( 'wpseo_opengraph', array( $this, 'wpseo_ogp' ), 2 );
44
- } else {
45
- add_filter( 'wpseo_frontend_presenters', array( $this, 'wpseo_frontend_presenters' ) );
46
- }
47
  add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) );
48
  add_filter( 'wpseo_frontend_presentation', array( $this, 'frontend_presentation' ) );
49
  add_filter( 'wpseo_breadcrumb_indexables', array( $this, 'breadcrumb_indexables' ) );
50
  } else {
51
- add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 10, 2 );
52
  add_filter( 'pll_translate_post_meta', array( $this, 'translate_post_meta' ), 10, 3 );
53
 
54
  // Yoast SEO adds the columns hooks only for the 'inline-save' action. We need them for 'pll_update_post_rows' too.
@@ -105,7 +101,7 @@ class PLL_WPSEO {
105
  */
106
  public function wpseo_home_url( $url, $path ) {
107
  if ( empty( $path ) ) {
108
- $path = ltrim( wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
109
  }
110
 
111
  if ( preg_match( '#sitemap(_index)?\.xml|([^\/]+?)-?sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $path ) ) {
@@ -292,17 +288,17 @@ class PLL_WPSEO {
292
  }
293
 
294
  /**
295
- * Get alternate language codes for Opengraph
296
  *
297
  * @since 2.7.3
298
  *
299
- * @return array
300
  */
301
  protected function get_ogp_alternate_languages() {
302
  $alternates = array();
303
 
304
  foreach ( PLL()->model->get_languages_list() as $language ) {
305
- if ( PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) {
306
  $alternates[] = $language->facebook;
307
  }
308
  }
@@ -311,22 +307,6 @@ class PLL_WPSEO {
311
  return array_unique( $alternates );
312
  }
313
 
314
- /**
315
- * Adds opengraph support for translations
316
- *
317
- * @since 1.6
318
- */
319
- public function wpseo_ogp() {
320
- global $wpseo_og;
321
-
322
- // WPSEO already deals with the locale
323
- if ( did_action( 'pll_init' ) && method_exists( $wpseo_og, 'og_tag' ) ) {
324
- foreach ( $this->get_ogp_alternate_languages() as $lang ) {
325
- $wpseo_og->og_tag( 'og:locale:alternate', $lang );
326
- }
327
- }
328
- }
329
-
330
  /**
331
  * Adds opengraph support for translations
332
  *
@@ -438,9 +418,11 @@ class PLL_WPSEO {
438
  *
439
  * @param string[] $keys List of custom fields names.
440
  * @param bool $sync True if it is synchronization, false if it is a copy.
 
 
441
  * @return array
442
  */
443
- public function copy_post_metas( $keys, $sync ) {
444
  if ( ! $sync ) {
445
  // Text requiring translation.
446
  $keys[] = '_yoast_wpseo_title';
@@ -468,6 +450,10 @@ class PLL_WPSEO {
468
  )
469
  );
470
 
 
 
 
 
471
  foreach ( $taxonomies as $taxonomy ) {
472
  $keys[] = '_yoast_wpseo_primary_' . $taxonomy;
473
  }
@@ -486,9 +472,15 @@ class PLL_WPSEO {
486
  * @return int
487
  */
488
  public function translate_post_meta( $value, $key, $lang ) {
489
- if ( false !== strpos( $key, '_yoast_wpseo_primary_' ) ) {
490
- $value = pll_get_term( $value, $lang );
 
 
 
 
 
491
  }
492
- return $value;
 
493
  }
494
  }
39
  }
40
 
41
  add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) );
42
+ add_filter( 'wpseo_frontend_presenters', array( $this, 'wpseo_frontend_presenters' ) );
 
 
 
 
43
  add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) );
44
  add_filter( 'wpseo_frontend_presentation', array( $this, 'frontend_presentation' ) );
45
  add_filter( 'wpseo_breadcrumb_indexables', array( $this, 'breadcrumb_indexables' ) );
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.
101
  */
102
  public function wpseo_home_url( $url, $path ) {
103
  if ( empty( $path ) ) {
104
+ $path = ltrim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
105
  }
106
 
107
  if ( preg_match( '#sitemap(_index)?\.xml|([^\/]+?)-?sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $path ) ) {
288
  }
289
 
290
  /**
291
+ * Get alternate language codes for Opengraph.
292
  *
293
  * @since 2.7.3
294
  *
295
+ * @return string[]
296
  */
297
  protected function get_ogp_alternate_languages() {
298
  $alternates = array();
299
 
300
  foreach ( PLL()->model->get_languages_list() as $language ) {
301
+ if ( isset( PLL()->curlang ) && PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) {
302
  $alternates[] = $language->facebook;
303
  }
304
  }
307
  return array_unique( $alternates );
308
  }
309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  /**
311
  * Adds opengraph support for translations
312
  *
418
  *
419
  * @param string[] $keys List of custom fields names.
420
  * @param bool $sync True if it is synchronization, false if it is a copy.
421
+ * @param int $from Id of the post from which we copy informations.
422
+ * @param int $to Id of the post to which we paste informations.
423
  * @return array
424
  */
425
+ public function copy_post_metas( $keys, $sync, $from, $to ) {
426
  if ( ! $sync ) {
427
  // Text requiring translation.
428
  $keys[] = '_yoast_wpseo_title';
450
  )
451
  );
452
 
453
+ $sync_taxonomies = PLL()->sync->taxonomies->get_taxonomies_to_copy( $sync, $from, $to );
454
+
455
+ $taxonomies = array_intersect( $taxonomies, $sync_taxonomies );
456
+
457
  foreach ( $taxonomies as $taxonomy ) {
458
  $keys[] = '_yoast_wpseo_primary_' . $taxonomy;
459
  }
472
  * @return int
473
  */
474
  public function translate_post_meta( $value, $key, $lang ) {
475
+ if ( 0 !== strpos( $key, '_yoast_wpseo_primary_' ) ) {
476
+ return $value;
477
+ }
478
+
479
+ $taxonomy = str_replace( '_yoast_wpseo_primary_', '', $key );
480
+ if ( ! PLL()->model->is_translated_taxonomy( $taxonomy ) ) {
481
+ return $value;
482
  }
483
+
484
+ return pll_get_term( $value, $lang );
485
  }
486
  }
modules/site-health/admin-site-health.php CHANGED
@@ -43,10 +43,12 @@ class PLL_Admin_Site_Health {
43
  // Information tab.
44
  add_filter( 'debug_information', array( $this, 'info_options' ), 15 );
45
  add_filter( 'debug_information', array( $this, 'info_languages' ), 15 );
46
- add_filter( 'debug_information', array( $this, 'info_warning' ), 15 );
47
 
48
  // Tests Tab.
49
  add_filter( 'site_status_tests', array( $this, 'status_tests' ) );
 
 
50
  }
51
 
52
  /**
@@ -168,9 +170,10 @@ class PLL_Admin_Site_Health {
168
  }
169
  }
170
  }
 
171
  $debug_info['pll_options'] = array(
172
  /* translators: placeholder is the plugin name */
173
- 'label' => sprintf( esc_html__( '%s Options', 'polylang' ), POLYLANG ),
174
  'fields' => $fields,
175
  );
176
 
@@ -208,7 +211,7 @@ class PLL_Admin_Site_Health {
208
 
209
  $debug_info[ 'pll_language_' . $language->slug ] = array(
210
  /* translators: placeholder is the language name */
211
- 'label' => sprintf( esc_html__( 'Language: %s', 'polylang' ), esc_html( $language->name ) ),
212
  /* translators: placeholder is the flag image */
213
  'description' => sprintf( esc_html__( 'Flag used in the language switcher: %s', 'polylang' ), $this->get_flag( $language ) ),
214
  'fields' => $fields,
@@ -291,9 +294,10 @@ class PLL_Admin_Site_Health {
291
  * @param array $debug_info The debug information to be added to the core information page.
292
  * @return array
293
  */
294
- public function info_warning( $debug_info ) {
295
  $fields = array();
296
 
 
297
  $posts_no_lang = $this->get_post_ids_without_lang();
298
 
299
  if ( ! empty( $posts_no_lang ) ) {
@@ -308,9 +312,23 @@ class PLL_Admin_Site_Health {
308
  $fields['term-no-lang']['value'] = $this->format_array( $terms_no_lang );
309
  }
310
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  if ( ! empty( $fields ) ) {
312
  $debug_info['pll_warnings'] = array(
313
- 'label' => sprintf( esc_html__( 'Polylang warnings', 'polylang' ), POLYLANG ),
 
314
  'fields' => $fields,
315
  );
316
  }
@@ -365,4 +383,24 @@ class PLL_Admin_Site_Health {
365
 
366
  return $terms;
367
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  }
43
  // Information tab.
44
  add_filter( 'debug_information', array( $this, 'info_options' ), 15 );
45
  add_filter( 'debug_information', array( $this, 'info_languages' ), 15 );
46
+ add_filter( 'debug_information', array( $this, 'info' ), 15 );
47
 
48
  // Tests Tab.
49
  add_filter( 'site_status_tests', array( $this, 'status_tests' ) );
50
+ add_filter( 'site_status_test_php_modules', array( $this, 'site_status_test_php_modules' ) ); // Require simplexml in Site health.
51
+
52
  }
53
 
54
  /**
170
  }
171
  }
172
  }
173
+
174
  $debug_info['pll_options'] = array(
175
  /* translators: placeholder is the plugin name */
176
+ 'label' => sprintf( __( '%s options', 'polylang' ), POLYLANG ),
177
  'fields' => $fields,
178
  );
179
 
211
 
212
  $debug_info[ 'pll_language_' . $language->slug ] = array(
213
  /* translators: placeholder is the language name */
214
+ 'label' => sprintf( __( 'Language: %s', 'polylang' ), $language->name ),
215
  /* translators: placeholder is the flag image */
216
  'description' => sprintf( esc_html__( 'Flag used in the language switcher: %s', 'polylang' ), $this->get_flag( $language ) ),
217
  'fields' => $fields,
294
  * @param array $debug_info The debug information to be added to the core information page.
295
  * @return array
296
  */
297
+ public function info( $debug_info ) {
298
  $fields = array();
299
 
300
+ // Add Post Types without languages.
301
  $posts_no_lang = $this->get_post_ids_without_lang();
302
 
303
  if ( ! empty( $posts_no_lang ) ) {
312
  $fields['term-no-lang']['value'] = $this->format_array( $terms_no_lang );
313
  }
314
 
315
+ // Add WPML files.
316
+ $wpml_files = PLL_WPML_Config::instance()->get_files();
317
+ if ( ! empty( $wpml_files ) ) {
318
+ $fields['wpml']['label'] = 'wpml-config.xml files';
319
+ $fields['wpml']['value'] = $wpml_files;
320
+
321
+ if ( ! extension_loaded( 'simplexml' ) ) {
322
+ $fields['simplexml']['label'] = __( 'PHP SimpleXML extension', 'polylang' );
323
+ $fields['simplexml']['value'] = __( 'Not loaded. Contact your host provider.', 'polylang' );
324
+ }
325
+ }
326
+
327
+ // Create the section.
328
  if ( ! empty( $fields ) ) {
329
  $debug_info['pll_warnings'] = array(
330
+ /* translators: placeholder is the plugin name */
331
+ 'label' => sprintf( __( '%s information', 'polylang' ), POLYLANG ),
332
  'fields' => $fields,
333
  );
334
  }
383
 
384
  return $terms;
385
  }
386
+
387
+ /**
388
+ * Requires the simplexml PHP module when a wpml-config.xml has been found.
389
+ *
390
+ * @since 3.1
391
+ * @since 3.2 Moved from PLL_WPML_Config
392
+ *
393
+ * @param array $modules An associative array of modules to test for.
394
+ * @return array
395
+ */
396
+ public function site_status_test_php_modules( $modules ) {
397
+ $files = PLL_WPML_Config::instance()->get_files();
398
+ if ( ! empty( $files ) ) {
399
+ $modules['simplexml'] = array(
400
+ 'extension' => 'simplexml',
401
+ 'required' => true,
402
+ );
403
+ }
404
+ return $modules;
405
+ }
406
  }
modules/sync/sync-metas.php CHANGED
@@ -348,9 +348,6 @@ abstract class PLL_Sync_Metas {
348
  public function copy( $from, $to, $lang, $sync = false ) {
349
  $this->remove_all_meta_actions();
350
 
351
- remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
352
- remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
353
-
354
  $to_copy = $this->get_metas_to_copy( $from, $to, $lang, $sync );
355
  $metas = get_metadata( $this->meta_type, $from );
356
  $tr_metas = get_metadata( $this->meta_type, $to );
348
  public function copy( $from, $to, $lang, $sync = false ) {
349
  $this->remove_all_meta_actions();
350
 
 
 
 
351
  $to_copy = $this->get_metas_to_copy( $from, $to, $lang, $sync );
352
  $metas = get_metadata( $this->meta_type, $from );
353
  $tr_metas = get_metadata( $this->meta_type, $to );
modules/sync/sync-post-metas.php CHANGED
@@ -45,20 +45,22 @@ class PLL_Sync_Post_Metas extends PLL_Sync_Metas {
45
  * @return string[] List of meta keys.
46
  */
47
  protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) {
48
- // Copy or synchronize post metas and allow plugins to do the same
49
- $metas = get_post_custom( $from );
50
  $keys = array();
51
 
52
- // Get public meta keys ( including from translated post in case we just deleted a custom field )
53
  if ( ! $sync || in_array( 'post_meta', $this->options['sync'] ) ) {
54
- foreach ( $keys = array_unique( array_merge( array_keys( $metas ), array_keys( get_post_custom( $to ) ) ) ) as $k => $meta_key ) {
 
 
 
 
55
  if ( is_protected_meta( $meta_key ) ) {
56
  unset( $keys[ $k ] );
57
  }
58
  }
59
  }
60
 
61
- // Add page template and featured image
62
  foreach ( array( '_wp_page_template', '_thumbnail_id' ) as $meta ) {
63
  if ( ! $sync || in_array( $meta, $this->options['sync'] ) ) {
64
  $keys[] = $meta;
45
  * @return string[] List of meta keys.
46
  */
47
  protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) {
 
 
48
  $keys = array();
49
 
50
+ // Get public meta keys ( including from translated post in case we just deleted a custom field ).
51
  if ( ! $sync || in_array( 'post_meta', $this->options['sync'] ) ) {
52
+ $from_keys = (array) get_post_custom_keys( $from );
53
+ $to_keys = (array) get_post_custom_keys( $to );
54
+
55
+ $keys = array_unique( array_merge( $from_keys, $to_keys ) );
56
+ foreach ( $keys as $k => $meta_key ) {
57
  if ( is_protected_meta( $meta_key ) ) {
58
  unset( $keys[ $k ] );
59
  }
60
  }
61
  }
62
 
63
+ // Add page template and featured image.
64
  foreach ( array( '_wp_page_template', '_thumbnail_id' ) as $meta ) {
65
  if ( ! $sync || in_array( $meta, $this->options['sync'] ) ) {
66
  $keys[] = $meta;
modules/sync/sync-tax.php CHANGED
@@ -44,6 +44,7 @@ class PLL_Sync_Tax {
44
  *
45
  * @since 1.7
46
  * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
 
47
  *
48
  * @param bool $sync True if it is synchronization, false if it is a copy.
49
  * @param int $from Id of the post from which we copy informations, optional, defaults to null.
@@ -51,7 +52,7 @@ class PLL_Sync_Tax {
51
  * @param string $lang Language slug, optional, defaults to null.
52
  * @return string[] List of taxonomy names.
53
  */
54
- protected function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) {
55
  $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
56
  if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
57
  $taxonomies[] = 'post_format';
44
  *
45
  * @since 1.7
46
  * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
47
+ * @since 3.2 Changed visibility from protected to public.
48
  *
49
  * @param bool $sync True if it is synchronization, false if it is a copy.
50
  * @param int $from Id of the post from which we copy informations, optional, defaults to null.
52
  * @param string $lang Language slug, optional, defaults to null.
53
  * @return string[] List of taxonomy names.
54
  */
55
+ public function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) {
56
  $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
57
  if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
58
  $taxonomies[] = 'post_format';
modules/wpml/wpml-api.php CHANGED
@@ -224,15 +224,21 @@ class PLL_WPML_API {
224
  */
225
  public function wpml_element_language_code( $language_code, $args ) {
226
  $type = $args['element_type'];
227
- $id = $args['element_id'];
228
- $pll_type = ( 'post' == $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' == $type || pll_is_translated_taxonomy( $type ) ? 'term' : false );
229
- if ( 'term' === $pll_type ) {
 
 
 
 
230
  $term = get_term_by( 'term_taxonomy_id', $id );
231
  if ( $term instanceof WP_Term ) {
232
  $id = $term->term_id;
233
  }
 
234
  }
235
- return $pll_type ? call_user_func( "pll_get_{$pll_type}_language", $id ) : $language_code;
 
236
  }
237
 
238
  /**
@@ -302,7 +308,12 @@ class PLL_WPML_API {
302
  * @return bool
303
  */
304
  public function wpml_element_has_translations( $null, $id, $type ) {
305
- $pll_type = ( 'post' == $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' == $type || pll_is_translated_taxonomy( $type ) ? 'term' : false );
306
- return ( $pll_type && $translations = call_user_func( "pll_get_{$pll_type}_translations", $id ) ) ? count( $translations ) > 1 : false;
 
 
 
 
 
307
  }
308
  }
224
  */
225
  public function wpml_element_language_code( $language_code, $args ) {
226
  $type = $args['element_type'];
227
+ $id = $args['element_id'];
228
+
229
+ if ( 'post' === $type || pll_is_translated_post_type( $type ) ) {
230
+ return pll_get_post_language( $id );
231
+ }
232
+
233
+ if ( 'term' === $type || pll_is_translated_taxonomy( $type ) ) {
234
  $term = get_term_by( 'term_taxonomy_id', $id );
235
  if ( $term instanceof WP_Term ) {
236
  $id = $term->term_id;
237
  }
238
+ return pll_get_term_language( $id );
239
  }
240
+
241
+ return $language_code;
242
  }
243
 
244
  /**
308
  * @return bool
309
  */
310
  public function wpml_element_has_translations( $null, $id, $type ) {
311
+ if ( 'post' === $type || pll_is_translated_post_type( $type ) ) {
312
+ return count( pll_get_post_translations( $id ) ) > 1;
313
+ } elseif ( 'term' === $type || pll_is_translated_taxonomy( $type ) ) {
314
+ return count( pll_get_term_translations( $id ) ) > 1;
315
+ }
316
+
317
+ return false;
318
  }
319
  }
modules/wpml/wpml-config.php CHANGED
@@ -25,6 +25,13 @@ class PLL_WPML_Config {
25
  */
26
  protected $xmls;
27
 
 
 
 
 
 
 
 
28
  /**
29
  * Constructor
30
  *
@@ -41,7 +48,7 @@ class PLL_WPML_Config {
41
  *
42
  * @since 1.7
43
  *
44
- * @return object
45
  */
46
  public static function instance() {
47
  if ( empty( self::$instance ) ) {
@@ -62,7 +69,6 @@ class PLL_WPML_Config {
62
  $files = $this->get_files();
63
 
64
  if ( ! empty( $files ) ) {
65
- add_filter( 'site_status_test_php_modules', array( $this, 'site_status_test_php_modules' ) ); // Require simplexml in Site health.
66
 
67
  // Read all files.
68
  if ( extension_loaded( 'simplexml' ) ) {
@@ -113,7 +119,12 @@ class PLL_WPML_Config {
113
  *
114
  * @return array
115
  */
116
- protected function get_files() {
 
 
 
 
 
117
  $files = array();
118
 
119
  // Plugins
@@ -142,23 +153,9 @@ class PLL_WPML_Config {
142
  $files['Polylang'] = $file;
143
  }
144
 
145
- return $files;
146
- }
147
 
148
- /**
149
- * Requires the simplexml PHP module when a wpml-config.xml has been found.
150
- *
151
- * @since 3.1
152
- *
153
- * @param array $modules An associative array of modules to test for.
154
- * @return array
155
- */
156
- public function site_status_test_php_modules( $modules ) {
157
- $modules['simplexml'] = array(
158
- 'extension' => 'simplexml',
159
- 'required' => true,
160
- );
161
- return $modules;
162
  }
163
 
164
  /**
@@ -270,9 +267,9 @@ class PLL_WPML_Config {
270
  *
271
  * @since 2.8
272
  *
273
- * @param string $context The group in which the strings will be registered.
274
- * @param string $name Option name.
275
- * @param object $key XML node.
276
  * @return void
277
  */
278
  protected function register_or_translate_option( $context, $name, $key ) {
@@ -285,8 +282,8 @@ class PLL_WPML_Config {
285
  *
286
  * @since 2.9
287
  *
288
- * @param object $key XML node.
289
- * @param array $arr Array of option keys to translate.
290
  * @return array
291
  */
292
  protected function xml_to_array( $key, &$arr = array() ) {
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
  *
48
  *
49
  * @since 1.7
50
  *
51
+ * @return PLL_WPML_Config
52
  */
53
  public static function instance() {
54
  if ( empty( self::$instance ) ) {
69
  $files = $this->get_files();
70
 
71
  if ( ! empty( $files ) ) {
 
72
 
73
  // Read all files.
74
  if ( extension_loaded( 'simplexml' ) ) {
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
153
  $files['Polylang'] = $file;
154
  }
155
 
156
+ $this->files = $files;
 
157
 
158
+ return $files;
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  }
160
 
161
  /**
267
  *
268
  * @since 2.8
269
  *
270
+ * @param string $context The group in which the strings will be registered.
271
+ * @param string $name Option name.
272
+ * @param SimpleXMLElement $key XML node.
273
  * @return void
274
  */
275
  protected function register_or_translate_option( $context, $name, $key ) {
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() ) {
modules/wpml/wpml-legacy-api.php CHANGED
@@ -157,34 +157,61 @@ if ( ! function_exists( 'icl_link_to_element' ) ) {
157
 
158
  if ( ! function_exists( 'icl_object_id' ) ) {
159
  /**
160
- * Used for calculating the IDs of objects (usually categories) in the current language
161
  *
162
  * @since 0.9.5
163
  *
164
- * @param int $id Object id
165
- * @param string $type Optional, post type or taxonomy name of the object, defaults to 'post'
166
- * @param bool $return_original_if_missing Optional, true if Polylang should return the original id if the translation is missing, defaults to false
167
- * @param string $lang Optional, language code, defaults to current language
168
- * @return int|null The object id of the translation, null if the translation is missing and $return_original_if_missing set to false
169
  */
170
- function icl_object_id( $id, $type = 'post', $return_original_if_missing = false, $lang = '' ) {
171
- $lang = $lang ? $lang : pll_current_language();
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- if ( 'nav_menu' === $type ) {
 
 
 
 
 
174
  $theme = get_option( 'stylesheet' );
175
  if ( isset( PLL()->options['nav_menus'][ $theme ] ) ) {
176
  foreach ( PLL()->options['nav_menus'][ $theme ] as $menu ) {
177
- if ( array_search( $id, $menu ) && ! empty( $menu[ $lang ] ) ) {
178
- $tr_id = $menu[ $lang ];
179
  break;
180
  }
181
  }
182
  }
183
- } elseif ( $pll_type = ( 'post' === $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' === $type || pll_is_translated_taxonomy( $type ) ? 'term' : false ) ) {
184
- $tr_id = PLL()->model->$pll_type->get_translation( $id, $lang );
 
 
 
 
 
 
 
 
 
 
185
  }
186
 
187
- return ! empty( $tr_id ) ? $tr_id : ( $return_original_if_missing ? $id : null );
188
  }
189
  }
190
 
157
 
158
  if ( ! function_exists( 'icl_object_id' ) ) {
159
  /**
160
+ * Returns an element’s ID in the current language or in another specified language.
161
  *
162
  * @since 0.9.5
163
  *
164
+ * @param int $element_id Object id.
165
+ * @param string $element_type Optional, post type or taxonomy name of the object, defaults to 'post'.
166
+ * @param bool $return_original_if_missing Optional, true if Polylang should return the original id if the translation is missing, defaults to false.
167
+ * @param string|null $ulanguage_code Optional, language code, defaults to the current language.
168
+ * @return int|null The object id of the translation, null if the translation is missing and $return_original_if_missing set to false.
169
  */
170
+ function icl_object_id( $element_id, $element_type = 'post', $return_original_if_missing = false, $ulanguage_code = null ) {
171
+ if ( empty( $element_id ) ) {
172
+ return null;
173
+ }
174
+
175
+ $element_id = (int) $element_id;
176
+
177
+ if ( 'any' === $element_type ) {
178
+ $element_type = get_post_type( $element_id );
179
+ }
180
+
181
+ if ( empty( $element_type ) ) {
182
+ return null;
183
+ }
184
 
185
+ if ( empty( $ulanguage_code ) ) {
186
+ $ulanguage_code = pll_current_language();
187
+ }
188
+
189
+ if ( 'nav_menu' === $element_type ) {
190
+ $tr_id = false;
191
  $theme = get_option( 'stylesheet' );
192
  if ( isset( PLL()->options['nav_menus'][ $theme ] ) ) {
193
  foreach ( PLL()->options['nav_menus'][ $theme ] as $menu ) {
194
+ if ( array_search( $element_id, $menu ) && ! empty( $menu[ $ulanguage_code ] ) ) {
195
+ $tr_id = $menu[ $ulanguage_code ];
196
  break;
197
  }
198
  }
199
  }
200
+ } elseif ( pll_is_translated_post_type( $element_type ) ) {
201
+ $tr_id = PLL()->model->post->get_translation( $element_id, $ulanguage_code );
202
+ } elseif ( pll_is_translated_taxonomy( $element_type ) ) {
203
+ $tr_id = PLL()->model->term->get_translation( $element_id, $ulanguage_code );
204
+ }
205
+
206
+ if ( ! isset( $tr_id ) ) {
207
+ return $element_id; // WPML doesn't honor $return_original_if_missing if the post type or taxonomy is not translated.
208
+ }
209
+
210
+ if ( empty( $tr_id ) ) {
211
+ return $return_original_if_missing ? $element_id : null;
212
  }
213
 
214
+ return (int) $tr_id;
215
  }
216
  }
217
 
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.1.4
14
- * Requires at least: 5.4
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.1.4' );
57
- define( 'PLL_MIN_WP_VERSION', '5.4' );
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.2
14
+ * Requires at least: 5.6
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.2' );
57
+ define( 'PLL_MIN_WP_VERSION', '5.6' );
58
  define( 'PLL_MIN_PHP_VERSION', '5.6' );
59
 
60
  define( 'POLYLANG_FILE', __FILE__ );
readme.txt CHANGED
@@ -1,34 +1,59 @@
1
  === Polylang ===
2
- Contributors: Chouby, manooweb, raaaahman, marianne38, sebastienserre
3
  Donate link: https://polylang.pro
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
- Requires at least: 5.4
6
- Tested up to: 5.8
7
  Requires PHP: 5.6
8
- Stable tag: 3.1.4
9
  License: GPLv3 or later
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
 
12
- Making WordPress multilingual
13
 
14
  == Description ==
15
 
16
- = Features =
17
 
18
- Polylang allows you to create a bilingual or multilingual WordPress site. You write posts, pages and create categories and post tags as usual, and then define the language for each of them. The translation of a post, whether it is in the default language or not, is optional.
19
 
20
- * You can use as many languages as you want. RTL language scripts are supported. WordPress languages packs are automatically downloaded and updated.
21
- * You can translate posts, pages, media, categories, post tags, menus, widgets...
22
- * Custom post types, custom taxonomies, sticky posts and post formats, RSS feeds and all default WordPress widgets are supported.
23
- * The language is either set by the content or by the language code in url, or you can use one different subdomain or domain per language
24
- * Categories, post tags as well as some other metas are automatically copied when adding a new post or page translation
25
- * A customizable language switcher is provided as a widget or in the nav menu
26
 
27
- > The author does not provide support on the wordpress.org forum. Support and extra features are available to [Polylang Pro](https://polylang.pro) users.
28
 
29
- If you wish to migrate from WPML, you can use the plugin [WPML to Polylang](https://wordpress.org/plugins/wpml-to-polylang/)
 
 
 
 
 
30
 
31
- If you wish to use a professional or automatic translation service, you can install [Lingotek Translation](https://wordpress.org/plugins/lingotek-translation/), as an addon of Polylang. Lingotek offers a complete translation management system which provides services such as translation memory or semi-automated translation processes (e.g. machine translation > human translation > legal review).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  = Credits =
34
 
@@ -37,18 +62,12 @@ Thanks a lot to [Alex Lopez](http://www.alexlopez.rocks/) for the design of the
37
  Most of the flags included with Polylang are coming from [famfamfam](http://famfamfam.com/) and are public domain.
38
  Wherever third party code has been used, credit has been given in the code’s comments.
39
 
40
- = Do you like Polylang? =
41
-
42
- Don't hesitate to [give your feedback](http://wordpress.org/support/view/plugin-reviews/polylang#postform).
43
-
44
  == Installation ==
45
 
46
- 1. Make sure you are using WordPress 5.1 or later and that your server is running PHP 5.6 or later (same requirement as WordPress itself)
47
  1. If you tried other multilingual plugins, deactivate them before activating Polylang, otherwise, you may get unexpected results!
48
  1. Install and activate the plugin as usual from the 'Plugins' menu in WordPress.
49
- 1. Go to the languages settings page and create the languages you need
50
- 1. Add the 'language switcher' widget to let your visitors switch the language.
51
- 1. Take care that your theme must come with the corresponding .mo files (Polylang automatically downloads them when they are available for themes and plugins in this repository). If your theme is not internationalized yet, please refer to the [Theme Handbook](https://developer.wordpress.org/themes/functionality/internationalization/) or ask the theme author to internationalize it.
52
 
53
  == Frequently Asked Questions ==
54
 
@@ -59,11 +78,11 @@ Don't hesitate to [give your feedback](http://wordpress.org/support/view/plugin-
59
  * Search the [community support forum](https://wordpress.org/search/). You will probably find your answer here.
60
  * Read the sticky posts in the [community support forum](http://wordpress.org/support/plugin/polylang).
61
  * If you still have a problem, open a new thread in the [community support forum](http://wordpress.org/support/plugin/polylang).
62
- * [Polylang Pro](https://polylang.pro) users have access to our helpdesk.
63
 
64
  = Is Polylang compatible with WooCommerce? =
65
 
66
- * You need a separate addon to make Polylang and WooCommerce work together. [A Premium addon](https://polylang.pro/downloads/polylang-for-woocommerce/) is available.
67
 
68
  = Do you need translation services? =
69
 
@@ -78,6 +97,38 @@ Don't hesitate to [give your feedback](http://wordpress.org/support/view/plugin-
78
 
79
  == Changelog ==
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  = 3.1.4 (2022-01-31) =
82
 
83
  * Pro: Adapt duplication and synchronization of the gallery block refactored in WP 5.9
1
  === Polylang ===
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: 5.9
7
  Requires PHP: 5.6
8
+ Stable tag: 3.2
9
  License: GPLv3 or later
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
 
12
+ Go multilingual in a simple and efficient way. Keep writing posts, creating categories and post tags as usual while defining the language all at once.
13
 
14
  == Description ==
15
 
16
+ With Polylang fully integrated to WordPress and using only its built-in core features (taxonomies), keep steady performances on your site and create a multilingual site featuring from just one extra language to 10 or more depending on your needs. There is no limit in the number of languages added and WordPress’ language packs are automatically downloaded when ready.
17
 
18
+ = Features =
19
 
20
+ Depending on the type of site you have built or are planning to build, a combination of plugins from the list below might be of interest:
 
 
 
 
 
21
 
22
+ ### Polylang
23
 
24
+ Polylang and [Polylang Pro](https://polylang.pro) share the same core providing features such as:
25
+ * Translating posts, pages, media, categories, post tags, custom post types and taxonomies, RSS feeds; RTL scripts are supported.
26
+ * The language is either set by the language code in URL, or you can use a different sub-domain or domain per language.
27
+ * Automatic copy of categories, post tags and other metas when creating a new post or page translation.
28
+ * Translating menus and widgets.
29
+ * Customizable language switcher available as a widget or a navigation menu item.
30
 
31
+ ### Polylang Pro
32
+
33
+ Helps optimizing the time spent translating your site with some very useful extra features such as:
34
+ * Better integration in the new Block Editor.
35
+ * Language switcher available as a block.
36
+ * Widget block editor and full Site Editing (FSE) compatibility.
37
+ * Duplicate and/or synchronize content across post translations.
38
+ * Improved compatibilities with other plugins such as [ACF Pro](https://polylang.pro/doc/working-with-acf-pro/).
39
+ * Share the same URL slug for posts or terms across languages.
40
+ * [Translate the slugs](https://polylang.pro/doc/translating-urls-slugs/) in the URL for category and author bases, custom post types and more...
41
+ * **Access to a Premium Support for personalized assistance**
42
+
43
+ ### Polylang for WooCommerce
44
+
45
+ [Add-on](https://polylang.pro/downloads/polylang-for-woocommerce/) for the compatibility with WooCommerce will provide features such as:
46
+ * Translating WooCommerce pages (shop, check-out, cart, my account), product categories and global attribute terms directly in the WooCommerce interface.
47
+ * Translating WooCommerce e-mails and sending them to customers in their language.
48
+ * Products metadata synchronization.
49
+ * Compatibility with the native WooCommerce CSV import & export tool.
50
+ * Compatibility with popular plugins such as WooCommerce Subscriptions, Product Bundles, WooCommerce Bookings, Shipment tracking and more.
51
+ * Ability to use the WooCommerce REST API (available with Polylang Pro).
52
+ * **Access to a Premium Support for personalized assistance**
53
+
54
+ Neither of them will allow you to do automated translation. Nevertheless, you can install, alongside Polylang Pro or Polylang, a third party plugin such as [Lingotek Translation](https://wordpress.org/plugins/lingotek-translation/) which offers a complete translation management system and provides services such as a translation memory or semi-automated translation processes (e.g., machine translation => human translation => legal review).
55
+
56
+ If you wish to migrate from WPML, you can use the plugin [WPML to Polylang](https://wordpress.org/plugins/wpml-to-polylang/).
57
 
58
  = Credits =
59
 
62
  Most of the flags included with Polylang are coming from [famfamfam](http://famfamfam.com/) and are public domain.
63
  Wherever third party code has been used, credit has been given in the code’s comments.
64
 
 
 
 
 
65
  == Installation ==
66
 
67
+ 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).
68
  1. If you tried other multilingual plugins, deactivate them before activating Polylang, otherwise, you may get unexpected results!
69
  1. Install and activate the plugin as usual from the 'Plugins' menu in WordPress.
70
+ 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.
 
 
71
 
72
  == Frequently Asked Questions ==
73
 
78
  * Search the [community support forum](https://wordpress.org/search/). You will probably find your answer here.
79
  * Read the sticky posts in the [community support forum](http://wordpress.org/support/plugin/polylang).
80
  * If you still have a problem, open a new thread in the [community support forum](http://wordpress.org/support/plugin/polylang).
81
+ * [Polylang Pro and Polylang for WooCommerce](https://polylang.pro) users have access to our helpdesk.
82
 
83
  = Is Polylang compatible with WooCommerce? =
84
 
85
+ * You need [Polylang for WooCommerce](https://polylang.pro/downloads/polylang-for-woocommerce/), a premium addon, to make both plugins work together.
86
 
87
  = Do you need translation services? =
88
 
97
 
98
  == Changelog ==
99
 
100
+ = 3.2 (2022-04-12) =
101
+
102
+ * Requires WP 5.6 as minimum version
103
+ * Pro: Add compatibility with the full site editing introduced in WP 5.9
104
+ * Pro: Add a language switcher block for the navigation block introduced in WP 5.9
105
+ * Pro: Add compatibility with the new gallery block introduced in WP 5.9
106
+ * Pro: Make the language switcher block available in the widget section of the customizer
107
+ * Pro: Fix wrong category when translating the latest block
108
+ * Pro: Fix the language switcher block when using the dropdown option
109
+ * Pro: Fix some edge cases with locale fallback
110
+ * Pro: Fix post template replacing the post content when duplicating a post
111
+ * Pro: Fix synchronization groups not correctly cleaned up when a language is deleted
112
+ * Pro: Fix incorrect sticky property when duplicating / synchronizing posts
113
+ * Pro: Fix "Page for posts" label after the page has been bulk translated
114
+ * Pro: Fix translated slug when the url includes a query string
115
+ * Pro: Synchronize ACF layout fields if a child field is synchronized or translatable
116
+ * Pro: Fix wrong field group translation displayed when using object cache with ACF
117
+ * Update plugin updater to 1.9.1
118
+ * Add compatibility with the block site title introduced in WP 5.9
119
+ * Add the list of wpml-config.xml files in the site health information
120
+ * Improve the performance of the get_pages() filter #980
121
+ * Improve the compatibility of 'wpml_object_id' with the original filter #972
122
+ * Prevent term_exists to be filtered by language in WP 6.0
123
+ * Fix some PHP 8.1 deprecations #949 #985
124
+ * Fix a fatal error in PHP 8.1 #987
125
+ * Fix category feed not redirected when the langage code is wrong #887
126
+ * Fix default category not created for secondary languages (introduced in 3.1) #997
127
+ * Fix parent page when the parent post type is not translatable #1001
128
+ * Fix the Yoast SEO breadcrumb when it includes a non-synchronized taxonomy #1005
129
+ * Fix a PHP Notice when adding a new language and Yoast SEO is active #979
130
+ * Fix a PHP warning in Yoast SEO compatibility #954
131
+
132
  = 3.1.4 (2022-01-31) =
133
 
134
  * Pro: Adapt duplication and synchronization of the gallery block refactored in WP 5.9
settings/settings-module.php CHANGED
@@ -81,7 +81,7 @@ class PLL_Settings_Module {
81
  /**
82
  * Stores html form when provided by a child class.
83
  *
84
- * @var bool|string
85
  */
86
  protected $form = false;
87
 
@@ -251,9 +251,10 @@ class PLL_Settings_Module {
251
  // Don't use flush_rewrite_rules as we don't have the right links model and permastruct
252
  delete_option( 'rewrite_rules' );
253
 
 
254
  ob_start();
255
 
256
- if ( ! get_settings_errors() ) {
257
  // Send update message
258
  add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'polylang' ), 'updated' );
259
  settings_errors();
81
  /**
82
  * Stores html form when provided by a child class.
83
  *
84
+ * @var string|false
85
  */
86
  protected $form = false;
87
 
251
  // Don't use flush_rewrite_rules as we don't have the right links model and permastruct
252
  delete_option( 'rewrite_rules' );
253
 
254
+
255
  ob_start();
256
 
257
+ if ( empty( get_settings_errors() ) ) {
258
  // Send update message
259
  add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'polylang' ), 'updated' );
260
  settings_errors();
settings/settings.php CHANGED
@@ -364,7 +364,8 @@ class PLL_Settings extends PLL_Admin_Base {
364
  * @return void
365
  */
366
  public static function redirect( $args = array() ) {
367
- if ( $errors = get_settings_errors() ) {
 
368
  set_transient( 'settings_errors', $errors, 30 );
369
  $args['settings-updated'] = 1;
370
  }
364
  * @return void
365
  */
366
  public static function redirect( $args = array() ) {
367
+ $errors = get_settings_errors();
368
+ if ( ! empty( $errors ) ) {
369
  set_transient( 'settings_errors', $errors, 30 );
370
  $args['settings-updated'] = 1;
371
  }
settings/table-languages.php CHANGED
@@ -229,12 +229,12 @@ class PLL_Table_Languages extends WP_List_Table {
229
  }
230
 
231
  /**
232
- * Sort items
233
  *
234
  * @since 0.1
235
  *
236
- * @param object $a The first object to compare
237
- * @param object $b The second object to compare
238
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
239
  */
240
  protected function usort_reorder( $a, $b ) {
@@ -245,16 +245,16 @@ class PLL_Table_Languages extends WP_List_Table {
245
  } else {
246
  $result = strcmp( $a->$orderby, $b->$orderby );
247
  }
248
- // Send final sort direction to usort
249
  return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification
250
  }
251
 
252
  /**
253
- * Prepares the list of items for displaying
254
  *
255
  * @since 0.1
256
  *
257
- * @param array $data
258
  * @return void
259
  */
260
  public function prepare_items( $data = array() ) {
229
  }
230
 
231
  /**
232
+ * Sorts language items.
233
  *
234
  * @since 0.1
235
  *
236
+ * @param PLL_Language $a The first language to compare.
237
+ * @param PLL_Language $b The second language to compare.
238
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
239
  */
240
  protected function usort_reorder( $a, $b ) {
245
  } else {
246
  $result = strcmp( $a->$orderby, $b->$orderby );
247
  }
248
+ // Send final sort direction to usort.
249
  return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification
250
  }
251
 
252
  /**
253
+ * Prepares the list of languages for display.
254
  *
255
  * @since 0.1
256
  *
257
+ * @param PLL_Language[] $data The list of languages.
258
  * @return void
259
  */
260
  public function prepare_items( $data = array() ) {
settings/table-string.php CHANGED
@@ -211,12 +211,12 @@ class PLL_Table_String extends WP_List_Table {
211
  }
212
 
213
  /**
214
- * Sort items
215
  *
216
  * @since 0.6
217
  *
218
- * @param object $a The first object to compare
219
- * @param object $b The second object to compare
220
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
221
  */
222
  protected function usort_reorder( $a, $b ) {
@@ -232,7 +232,7 @@ class PLL_Table_String extends WP_List_Table {
232
  }
233
 
234
  /**
235
- * Prepares the list of items for displaying
236
  *
237
  * @since 0.6
238
  *
211
  }
212
 
213
  /**
214
+ * Sorts registered string items.
215
  *
216
  * @since 0.6
217
  *
218
+ * @param array $a The first item to compare.
219
+ * @param array $b The second item to compare.
220
  * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
221
  */
222
  protected function usort_reorder( $a, $b ) {
232
  }
233
 
234
  /**
235
+ * Prepares the list of registered strings for display.
236
  *
237
  * @since 0.6
238
  *
settings/view-tab-strings.php CHANGED
@@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
26
  <div class="metabox-holder">
27
  <?php
28
  wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
29
- do_meta_boxes( 'languages_page_mlang_strings', 'bottom', array() );
30
  ?>
31
  </div>
32
 
26
  <div class="metabox-holder">
27
  <?php
28
  wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
29
+ do_meta_boxes( 'languages_page_mlang_strings', 'normal', array() );
30
  ?>
31
  </div>
32
 
vendor/composer/autoload_classmap.php CHANGED
@@ -43,6 +43,7 @@ return array(
43
  'PLL_Choose_Lang_Domain' => $baseDir . '/frontend/choose-lang-domain.php',
44
  'PLL_Choose_Lang_Url' => $baseDir . '/frontend/choose-lang-url.php',
45
  'PLL_Cookie' => $baseDir . '/include/cookie.php',
 
46
  'PLL_Domain_Mapping' => $baseDir . '/integrations/domain-mapping/domain-mapping.php',
47
  'PLL_Duplicate_Post' => $baseDir . '/integrations/duplicate-post/duplicate-post.php',
48
  'PLL_Featured_Content' => $baseDir . '/integrations/jetpack/featured-content.php',
43
  'PLL_Choose_Lang_Domain' => $baseDir . '/frontend/choose-lang-domain.php',
44
  'PLL_Choose_Lang_Url' => $baseDir . '/frontend/choose-lang-url.php',
45
  'PLL_Cookie' => $baseDir . '/include/cookie.php',
46
+ 'PLL_Db_Tools' => $baseDir . '/include/db-tools.php',
47
  'PLL_Domain_Mapping' => $baseDir . '/integrations/domain-mapping/domain-mapping.php',
48
  'PLL_Duplicate_Post' => $baseDir . '/integrations/duplicate-post/duplicate-post.php',
49
  'PLL_Featured_Content' => $baseDir . '/integrations/jetpack/featured-content.php',
vendor/composer/autoload_static.php CHANGED
@@ -44,6 +44,7 @@ class ComposerStaticInit57e007bdf76a1fe336cb43b59389545b
44
  'PLL_Choose_Lang_Domain' => __DIR__ . '/../..' . '/frontend/choose-lang-domain.php',
45
  'PLL_Choose_Lang_Url' => __DIR__ . '/../..' . '/frontend/choose-lang-url.php',
46
  'PLL_Cookie' => __DIR__ . '/../..' . '/include/cookie.php',
 
47
  'PLL_Domain_Mapping' => __DIR__ . '/../..' . '/integrations/domain-mapping/domain-mapping.php',
48
  'PLL_Duplicate_Post' => __DIR__ . '/../..' . '/integrations/duplicate-post/duplicate-post.php',
49
  'PLL_Featured_Content' => __DIR__ . '/../..' . '/integrations/jetpack/featured-content.php',
44
  'PLL_Choose_Lang_Domain' => __DIR__ . '/../..' . '/frontend/choose-lang-domain.php',
45
  'PLL_Choose_Lang_Url' => __DIR__ . '/../..' . '/frontend/choose-lang-url.php',
46
  'PLL_Cookie' => __DIR__ . '/../..' . '/include/cookie.php',
47
+ 'PLL_Db_Tools' => __DIR__ . '/../..' . '/include/db-tools.php',
48
  'PLL_Domain_Mapping' => __DIR__ . '/../..' . '/integrations/domain-mapping/domain-mapping.php',
49
  'PLL_Duplicate_Post' => __DIR__ . '/../..' . '/integrations/duplicate-post/duplicate-post.php',
50
  'PLL_Featured_Content' => __DIR__ . '/../..' . '/integrations/jetpack/featured-content.php',