Polylang - Version 1.9.1

Version Description

(2016-05-23) =

  • Pro: add compatibility with Beaver Builder
  • Pro: fix media wrongly created when adding a new media translation
  • Add azb, ceb, de_CH_informal, es_GT, mr, nl_NL_formal to the predefined list of languages
  • Fix the language switcher not linking to media translations for anonymous visitors
Download this release

Release Info

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

Code changes from version 1.9 to 1.9.1

admin/admin-filters-media.php CHANGED
@@ -1,14 +1,14 @@
1
  <?php
2
 
3
  /**
4
- * manages filters and actions related to media on admin side
5
- * capability to edit / create media is checked before loading this class
6
  *
7
  * @since 1.2
8
  */
9
  class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
10
  /**
11
- * constructor: setups filters and actions
12
  *
13
  * @since 1.2
14
  *
@@ -17,23 +17,23 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
17
  public function __construct( &$polylang ) {
18
  parent::__construct( $polylang );
19
 
20
- // adds the language field and translations tables in the 'Edit Media' panel
21
  add_filter( 'attachment_fields_to_edit', array( &$this, 'attachment_fields_to_edit' ), 10, 2 );
22
 
23
- // adds actions related to languages when creating, saving or deleting media
24
  add_action( 'add_attachment', array( &$this, 'set_default_language' ) );
25
  add_filter( 'attachment_fields_to_save', array( &$this, 'save_media' ), 10, 2 );
26
  add_filter( 'wp_delete_file', array( &$this, 'wp_delete_file' ) );
27
 
28
- // creates a media translation
29
  if ( isset( $_GET['action'], $_GET['new_lang'], $_GET['from_media'] ) && 'translate_media' === $_GET['action'] ) {
30
  add_action( 'admin_init', array( &$this, 'translate_media' ) );
31
  }
32
  }
33
 
34
  /**
35
- * adds the language field and translations tables in the 'Edit Media' panel
36
- * needs WP 3.5+
37
  *
38
  * @since 0.9
39
  *
@@ -43,7 +43,7 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
43
  */
44
  public function attachment_fields_to_edit( $fields, $post ) {
45
  if ( 'post.php' == $GLOBALS['pagenow'] ) {
46
- return $fields; // don't add anything on edit media panel for WP 3.5+ since we have the metabox
47
  }
48
 
49
  $post_id = $post->ID;
@@ -64,7 +64,7 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
64
  }
65
 
66
  /**
67
- * creates a media translation
68
  *
69
  * @since 1.8
70
  *
@@ -74,15 +74,15 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
74
  */
75
  public function create_media_translation( $post_id, $lang ) {
76
  $post = get_post( $post_id );
77
- $lang = $this->model->get_language( $lang ); // make sure we get a valid language slug
78
 
79
- // create a new attachment ( translate attachment parent if exists )
80
- $post->ID = null; // will force the creation
81
  $post->post_parent = ( $post->post_parent && $tr_parent = $this->model->post->get_translation( $post->post_parent, $lang->slug ) ) ? $tr_parent : 0;
82
- $post->tax_input = array( 'language' => array( $lang->slug ) ); // assigns the language
83
  $tr_id = wp_insert_attachment( $post );
84
 
85
- // copy metadata, attached file and alternative text
86
  foreach ( array( '_wp_attachment_metadata', '_wp_attached_file', '_wp_attachment_image_alt' ) as $key ) {
87
  if ( $meta = get_post_meta( $post_id, $key , true ) ) {
88
  add_post_meta( $tr_id, $key, $meta );
@@ -113,18 +113,19 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
113
  }
114
 
115
  /**
116
- * creates a media translation
117
  *
118
  * @since 0.9
119
  */
120
  public function translate_media() {
121
- //security check
122
  check_admin_referer( 'translate_media' );
123
  $post_id = (int) $_GET['from_media'];
124
 
125
- // bails if the translations already exists
126
- // see https://wordpress.org/support/topic/edit-translation-in-media-attachments?#post-7322303
127
- if ( $this->model->post->get_translation( $post_id, $_GET['new_lang'] ) ) {
 
128
  wp_safe_redirect( wp_get_referer() );
129
  exit;
130
  }
@@ -135,8 +136,8 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
135
  }
136
 
137
  /**
138
- * called when a media is saved
139
- * saves language and translations
140
  *
141
  * @since 0.9
142
  *
@@ -145,8 +146,8 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
145
  * @return array unmodified $post
146
  */
147
  public function save_media( $post, $attachment ) {
148
- // language is filled in attachment by the function applying the filter 'attachment_fields_to_save'
149
- // all security checks have been done by functions applying this filter
150
  if ( ! empty( $attachment['language'] ) ) {
151
  $this->model->post->set_language( $post['ID'], $attachment['language'] );
152
  }
@@ -159,8 +160,8 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
159
  }
160
 
161
  /**
162
- * prevents WP deleting files when there are still media using them
163
- * thanks to Bruno "Aesqe" Babic and its plugin file gallery in which I took all the ideas for this function
164
  *
165
  * @since 0.9
166
  *
@@ -179,9 +180,9 @@ class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
179
  ) );
180
 
181
  if ( ! empty( $ids ) ) {
182
- // regenerate intermediate sizes if it's an image ( since we could not prevent WP deleting them before )
183
  wp_update_attachment_metadata( $ids[0], wp_generate_attachment_metadata( $ids[0], $file ) );
184
- return ''; // prevent deleting the main file
185
  }
186
 
187
  return $file;
1
  <?php
2
 
3
  /**
4
+ * Manages filters and actions related to media on admin side
5
+ * Capability to edit / create media is checked before loading this class
6
  *
7
  * @since 1.2
8
  */
9
  class PLL_Admin_Filters_Media extends PLL_Admin_Filters_Post_Base {
10
  /**
11
+ * Constructor: setups filters and actions
12
  *
13
  * @since 1.2
14
  *
17
  public function __construct( &$polylang ) {
18
  parent::__construct( $polylang );
19
 
20
+ // Adds the language field and translations tables in the 'Edit Media' panel
21
  add_filter( 'attachment_fields_to_edit', array( &$this, 'attachment_fields_to_edit' ), 10, 2 );
22
 
23
+ // Adds actions related to languages when creating, saving or deleting media
24
  add_action( 'add_attachment', array( &$this, 'set_default_language' ) );
25
  add_filter( 'attachment_fields_to_save', array( &$this, 'save_media' ), 10, 2 );
26
  add_filter( 'wp_delete_file', array( &$this, 'wp_delete_file' ) );
27
 
28
+ // Creates a media translation
29
  if ( isset( $_GET['action'], $_GET['new_lang'], $_GET['from_media'] ) && 'translate_media' === $_GET['action'] ) {
30
  add_action( 'admin_init', array( &$this, 'translate_media' ) );
31
  }
32
  }
33
 
34
  /**
35
+ * Adds the language field and translations tables in the 'Edit Media' panel
36
+ * Needs WP 3.5+
37
  *
38
  * @since 0.9
39
  *
43
  */
44
  public function attachment_fields_to_edit( $fields, $post ) {
45
  if ( 'post.php' == $GLOBALS['pagenow'] ) {
46
+ return $fields; // Don't add anything on edit media panel for WP 3.5+ since we have the metabox
47
  }
48
 
49
  $post_id = $post->ID;
64
  }
65
 
66
  /**
67
+ * Creates a media translation
68
  *
69
  * @since 1.8
70
  *
74
  */
75
  public function create_media_translation( $post_id, $lang ) {
76
  $post = get_post( $post_id );
77
+ $lang = $this->model->get_language( $lang ); // Make sure we get a valid language slug
78
 
79
+ // Create a new attachment ( translate attachment parent if exists )
80
+ $post->ID = null; // Will force the creation
81
  $post->post_parent = ( $post->post_parent && $tr_parent = $this->model->post->get_translation( $post->post_parent, $lang->slug ) ) ? $tr_parent : 0;
82
+ $post->tax_input = array( 'language' => array( $lang->slug ) ); // Assigns the language
83
  $tr_id = wp_insert_attachment( $post );
84
 
85
+ // Copy metadata, attached file and alternative text
86
  foreach ( array( '_wp_attachment_metadata', '_wp_attached_file', '_wp_attachment_image_alt' ) as $key ) {
87
  if ( $meta = get_post_meta( $post_id, $key , true ) ) {
88
  add_post_meta( $tr_id, $key, $meta );
113
  }
114
 
115
  /**
116
+ * Creates a media translation
117
  *
118
  * @since 0.9
119
  */
120
  public function translate_media() {
121
+ // Security check
122
  check_admin_referer( 'translate_media' );
123
  $post_id = (int) $_GET['from_media'];
124
 
125
+ // Bails if the translations already exists
126
+ // See https://wordpress.org/support/topic/edit-translation-in-media-attachments?#post-7322303
127
+ // Or if the source media does not exist
128
+ if ( $this->model->post->get_translation( $post_id, $_GET['new_lang'] ) || ! get_post( $post_id ) ) {
129
  wp_safe_redirect( wp_get_referer() );
130
  exit;
131
  }
136
  }
137
 
138
  /**
139
+ * Called when a media is saved
140
+ * Saves language and translations
141
  *
142
  * @since 0.9
143
  *
146
  * @return array unmodified $post
147
  */
148
  public function save_media( $post, $attachment ) {
149
+ // Language is filled in attachment by the function applying the filter 'attachment_fields_to_save'
150
+ // All security checks have been done by functions applying this filter
151
  if ( ! empty( $attachment['language'] ) ) {
152
  $this->model->post->set_language( $post['ID'], $attachment['language'] );
153
  }
160
  }
161
 
162
  /**
163
+ * Prevents WP deleting files when there are still media using them
164
+ * Thanks to Bruno "Aesqe" Babic and its plugin file gallery in which I took all the ideas for this function
165
  *
166
  * @since 0.9
167
  *
180
  ) );
181
 
182
  if ( ! empty( $ids ) ) {
183
+ // Regenerate intermediate sizes if it's an image ( since we could not prevent WP deleting them before )
184
  wp_update_attachment_metadata( $ids[0], wp_generate_attachment_metadata( $ids[0], $file ) );
185
+ return ''; // Prevent deleting the main file
186
  }
187
 
188
  return $file;
admin/admin-filters-term.php CHANGED
@@ -1,17 +1,17 @@
1
  <?php
2
 
3
  /**
4
- * manages filters and actions related to terms on admin side
5
  *
6
  * @since 1.2
7
  */
8
  class PLL_Admin_Filters_Term {
9
  public $links, $model, $options, $curlang, $pref_lang;
10
- protected $pre_term_name; // used to store the term name before creating a slug if needed
11
- protected $post_id; // used to store the current post_id when bulk editing posts
12
 
13
  /**
14
- * constructor: setups filters and actions
15
  *
16
  * @param object $polylang
17
  */
@@ -23,17 +23,17 @@ class PLL_Admin_Filters_Term {
23
  $this->pref_lang = &$polylang->pref_lang;
24
 
25
  foreach ( $this->model->get_translated_taxonomies() as $tax ) {
26
- // adds the language field in the 'Categories' and 'Post Tags' panels
27
  add_action( $tax.'_add_form_fields', array( &$this, 'add_term_form' ) );
28
 
29
- // adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
30
  add_action( $tax.'_edit_form_fields', array( &$this, 'edit_term_form' ) );
31
 
32
- // adds action related to languages when deleting categories and post tags
33
  add_action( 'delete_'.$tax, array( &$this, 'delete_term' ) );
34
  }
35
 
36
- // adds actions related to languages when creating or saving categories and post tags
37
  add_filter( 'wp_dropdown_cats', array( &$this, 'wp_dropdown_cats' ) );
38
  add_action( 'create_term', array( &$this, 'save_term' ), 999, 3 );
39
  add_action( 'edit_term', array( &$this, 'save_term' ), 999, 3 ); // late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast
@@ -41,26 +41,26 @@ class PLL_Admin_Filters_Term {
41
  add_filter( 'pre_term_name', array( &$this, 'pre_term_name' ) );
42
  add_filter( 'pre_term_slug', array( &$this, 'pre_term_slug' ), 10, 2 );
43
 
44
- // ajax response for edit term form
45
  add_action( 'wp_ajax_term_lang_choice', array( &$this, 'term_lang_choice' ) );
46
  add_action( 'wp_ajax_pll_terms_not_translated', array( &$this, 'ajax_terms_not_translated' ) );
47
 
48
- // adds cache domain when querying terms
49
  add_filter( 'get_terms_args', array( &$this, 'get_terms_args' ), 10, 2 );
50
 
51
- // filters categories and post tags by language
52
  add_filter( 'terms_clauses', array( &$this, 'terms_clauses' ), 10, 3 );
53
 
54
- // allows to get the default categories in all languages
55
  add_filter( 'option_default_category', array( &$this, 'option_default_category' ) );
56
  add_action( 'update_option_default_category', array( &$this, 'update_option_default_category' ), 10, 2 );
57
 
58
- // updates the translations term ids when splitting a shared term
59
  add_action( 'split_shared_term', array( &$this, 'split_shared_term' ), 10, 4 ); // WP 4.2
60
  }
61
 
62
  /**
63
- * adds the language field in the 'Categories' and 'Post Tags' panels
64
  *
65
  * @since 0.1
66
  */
@@ -97,7 +97,7 @@ class PLL_Admin_Filters_Term {
97
  printf( '<input type="hidden" name="from_tag" value="%d" />', $_GET['from_tag'] );
98
  }
99
 
100
- // adds translation fields
101
  echo '<div id="term-translations" class="form-field">';
102
  if ( $lang ) {
103
  include( PLL_ADMIN_INC.'/view-translations-term.php' );
@@ -106,7 +106,7 @@ class PLL_Admin_Filters_Term {
106
  }
107
 
108
  /**
109
- * adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
110
  *
111
  * @since 0.1
112
  */
@@ -122,7 +122,7 @@ class PLL_Admin_Filters_Term {
122
 
123
  $dropdown = new PLL_Walker_Dropdown();
124
 
125
- // disable the language dropdown and the translations input fields for default categories to prevent removal
126
  $disabled = in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) );
127
 
128
  wp_nonce_field( 'pll_language', '_pll_nonce' );
@@ -156,7 +156,7 @@ class PLL_Admin_Filters_Term {
156
  }
157
 
158
  /**
159
- * translates term parent if exists when using "Add new" ( translation )
160
  *
161
  * @since 0.7
162
  *
@@ -187,7 +187,7 @@ class PLL_Admin_Filters_Term {
187
  }
188
 
189
  /**
190
- * allows to set a language by default for terms if it has no language yet
191
  *
192
  * @since 1.5.4
193
  *
@@ -196,7 +196,7 @@ class PLL_Admin_Filters_Term {
196
  */
197
  protected function set_default_language( $term_id, $taxonomy ) {
198
  if ( ! $this->model->term->get_language( $term_id ) ) {
199
- // sets language from term parent if exists thanks to Scott Kingsley Clark
200
  if ( ( $term = get_term( $term_id, $taxonomy ) ) && ! empty( $term->parent ) && $parent_lang = $this->model->term->get_language( $term->parent ) ) {
201
  $this->model->term->set_language( $term_id, $parent_lang );
202
  }
@@ -207,7 +207,7 @@ class PLL_Admin_Filters_Term {
207
  }
208
 
209
  /**
210
- * saves language
211
  *
212
  * @since 1.5
213
  *
@@ -216,10 +216,10 @@ class PLL_Admin_Filters_Term {
216
  */
217
  protected function save_language( $term_id, $taxonomy ) {
218
  global $wpdb;
219
- // security checks are necessary to accept language modifications
220
  // as 'wp_update_term' can be called from outside WP admin
221
 
222
- // edit tags
223
  if ( isset( $_POST['term_lang_choice'] ) ) {
224
  if ( 'add-' . $taxonomy == $_POST['action'] ) {
225
  check_ajax_referer( $_POST['action'], '_ajax_nonce-add-' . $taxonomy ); // category metabox
@@ -231,20 +231,20 @@ class PLL_Admin_Filters_Term {
231
  $this->model->term->set_language( $term_id, $this->model->get_language( $_POST['term_lang_choice'] ) );
232
  }
233
 
234
- // *post* bulk edit, in case a new term is created
235
  elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
236
  check_admin_referer( 'bulk-posts' );
237
 
238
- // bulk edit does not modify the language
239
- // so we possibly create a tag in several languages
240
  if ( -1 == $_GET['inline_lang_choice'] ) {
241
- // the language of the current term is set a according to the language of the current post
242
  $this->model->term->set_language( $term_id, $this->model->post->get_language( $this->post_id ) );
243
  $term = get_term( $term_id, $taxonomy );
244
 
245
- // get all terms with the same name
246
  // FIXME backward compatibility WP < 4.2
247
- // no WP function to get all terms with the exact same name so let's use a custom query
248
  // $terms = get_terms( $taxonomy, array( 'name' => $term->name, 'hide_empty' => false, 'fields' => 'ids' ) ); should be OK in 4.2
249
  // I may need to rework the loop below
250
  $terms = $wpdb->get_results( $wpdb->prepare( "
@@ -254,7 +254,7 @@ class PLL_Admin_Filters_Term {
254
  $taxonomy, $term->name
255
  ) );
256
 
257
- // if we have several terms with the same name, they are translations of each other
258
  if ( count( $terms ) > 1 ) {
259
  foreach ( $terms as $term ) {
260
  $translations[ $this->model->term->get_language( $term->term_id )->slug ] = $term->term_id;
@@ -269,18 +269,18 @@ class PLL_Admin_Filters_Term {
269
  }
270
  }
271
 
272
- // quick edit
273
  elseif ( isset( $_POST['inline_lang_choice'] ) ) {
274
  check_ajax_referer(
275
- isset( $_POST['action'] ) && 'inline-save' == $_POST['action'] ? 'inlineeditnonce' : 'taxinlineeditnonce', // post quick edit or tag quick edit ?
276
  '_inline_edit'
277
  );
278
 
279
- $old_lang = $this->model->term->get_language( $term_id ); // stores the old language
280
- $lang = $this->model->get_language( $_POST['inline_lang_choice'] ); // new language
281
  $translations = $this->model->term->get_translations( $term_id );
282
 
283
- // checks if the new language already exists in the translation group
284
  if ( $old_lang && $old_lang->slug != $lang->slug ) {
285
  if ( array_key_exists( $lang->slug, $translations ) ) {
286
  $this->model->term->delete_translation( $term_id );
@@ -292,11 +292,11 @@ class PLL_Admin_Filters_Term {
292
  }
293
  }
294
 
295
- $this->model->term->set_language( $term_id, $lang ); // set new language
296
  }
297
 
298
- // edit post
299
- elseif ( isset( $_POST['post_lang_choice'] ) ) {// FIXME should be useless now
300
  check_admin_referer( 'pll_language', '_pll_nonce' );
301
  $this->model->term->set_language( $term_id, $this->model->get_language( $_POST['post_lang_choice'] ) );
302
  }
@@ -307,7 +307,7 @@ class PLL_Admin_Filters_Term {
307
  }
308
 
309
  /**
310
- * save translations from our form
311
  *
312
  * @since 1.5
313
  *
@@ -315,11 +315,10 @@ class PLL_Admin_Filters_Term {
315
  * @return array
316
  */
317
  protected function save_translations( $term_id ) {
318
- // security check
319
- // as 'wp_update_term' can be called from outside WP admin
320
  check_admin_referer( 'pll_language', '_pll_nonce' );
321
 
322
- // save translations after checking the translated term is in the right language ( as well as cast id to int )
323
  foreach ( $_POST['term_tr_lang'] as $lang => $tr_id ) {
324
  $tr_lang = $this->model->term->get_language( (int) $tr_id );
325
  $translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? (int) $tr_id : 0;
@@ -331,8 +330,8 @@ class PLL_Admin_Filters_Term {
331
  }
332
 
333
  /**
334
- * called when a category or post tag is created or edited
335
- * saves language and translations
336
  *
337
  * @since 0.1
338
  *
@@ -341,13 +340,13 @@ class PLL_Admin_Filters_Term {
341
  * @param string $taxonomy
342
  */
343
  public function save_term( $term_id, $tt_id, $taxonomy ) {
344
- // does nothing except on taxonomies which are filterable
345
  if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
346
  return;
347
  }
348
 
349
- // capability check
350
- // as 'wp_update_term' can be called from outside WP admin
351
  // 2nd test for creating tags when creating / editing a post
352
  $tax = get_taxonomy( $taxonomy );
353
  if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) {
@@ -369,14 +368,14 @@ class PLL_Admin_Filters_Term {
369
  do_action( 'pll_save_term', $term_id, $taxonomy, empty( $translations ) ? $this->model->term->get_translations( $term_id ) : $translations );
370
  }
371
 
372
- // attempts to set a default language even if no capability
373
  else {
374
  $this->set_default_language( $term_id, $taxonomy );
375
  }
376
  }
377
 
378
  /**
379
- * stores the term name for use in pre_term_slug
380
  *
381
  * @since 0.9.5
382
  *
@@ -388,7 +387,7 @@ class PLL_Admin_Filters_Term {
388
  }
389
 
390
  /**
391
- * creates the term slug in case the term already exists in another language
392
  *
393
  * @since 0.9.5
394
  *
@@ -399,14 +398,14 @@ class PLL_Admin_Filters_Term {
399
  public function pre_term_slug( $slug, $taxonomy ) {
400
  $name = sanitize_title( $this->pre_term_name );
401
 
402
- // if the new term has the same name as a language, we *need* to differentiate the term
403
- // see http://core.trac.wordpress.org/ticket/23199
404
- // backward compatibility with WP < 4.1
405
  if ( version_compare( $GLOBALS['wp_version'], '4.1', '<' ) && term_exists( $name, 'language' ) && ! term_exists( $name, $taxonomy ) && ( ! $slug || $slug == $name ) ) {
406
- $slug = $name . '-' . $taxonomy; // a convenient slug which may be modified later by the user
407
  }
408
 
409
- // if the term already exists in another language
410
  if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
411
  if ( isset( $_POST['term_lang_choice'] ) ) {
412
  $slug = $name . '-' . $this->model->get_language( $_POST['term_lang_choice'] )->slug;
@@ -416,9 +415,9 @@ class PLL_Admin_Filters_Term {
416
  $slug = $name . '-' . $this->model->get_language( $_POST['inline_lang_choice'] )->slug;
417
  }
418
 
419
- // *post* bulk edit, in case a new term is created
420
  elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
421
- // bulk edit does not modify the language
422
  if ( -1 == $_GET['inline_lang_choice'] ) {
423
  $slug = $name . '-' . $this->model->post->get_language( $this->post_id )->slug;
424
  }
@@ -432,8 +431,8 @@ class PLL_Admin_Filters_Term {
432
  }
433
 
434
  /**
435
- * called when a category or post tag is deleted
436
- * deletes language and translations
437
  *
438
  * @since 0.1
439
  *
@@ -445,7 +444,7 @@ class PLL_Admin_Filters_Term {
445
  }
446
 
447
  /**
448
- * ajax response for edit term form
449
  *
450
  * @since 0.2
451
  */
@@ -468,7 +467,7 @@ class PLL_Admin_Filters_Term {
468
  $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
469
  ob_end_clean();
470
 
471
- // parent dropdown list ( only for hierarchical taxonomies )
472
  // $args copied from edit_tags.php except echo
473
  if ( is_taxonomy_hierarchical( $taxonomy ) ) {
474
  $args = array(
@@ -484,8 +483,8 @@ class PLL_Admin_Filters_Term {
484
  $x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) );
485
  }
486
 
487
- // tag cloud
488
- // tests copied from edit_tags.php
489
  else {
490
  $tax = get_taxonomy( $taxonomy );
491
  if ( ! is_null( $tax->labels->popular_items ) ) {
@@ -500,14 +499,14 @@ class PLL_Admin_Filters_Term {
500
  }
501
  }
502
 
503
- // flag
504
  $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
505
 
506
  $x->send();
507
  }
508
 
509
  /**
510
- * ajax response for input in translation autocomplete input box
511
  *
512
  * @since 1.5
513
  */
@@ -527,7 +526,7 @@ class PLL_Admin_Filters_Term {
527
 
528
  $return = array();
529
 
530
- // it is more efficient to use one common query for all languages as soon as there are more than 2
531
  // pll_get_terms_not_translated arg to identify this query in terms_clauses filter
532
  foreach ( get_terms( $taxonomy, 'hide_empty=0&pll_get_terms_not_translated=1&name__like=' . $s ) as $term ) {
533
  $lang = $this->model->term->get_language( $term->term_id );
@@ -541,8 +540,8 @@ class PLL_Admin_Filters_Term {
541
  }
542
  }
543
 
544
- // add current translation in list
545
- // not in add term for as term_id is not set
546
  if ( 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) {
547
  $term = get_term( $term_id, $taxonomy );
548
  array_unshift( $return, array(
@@ -557,7 +556,7 @@ class PLL_Admin_Filters_Term {
557
 
558
 
559
  /**
560
- * get the language(s) to filter get_terms
561
  *
562
  * @since 1.7.6
563
  *
@@ -566,17 +565,17 @@ class PLL_Admin_Filters_Term {
566
  * @return object|string|bool the language(s) to use in the filter, false otherwise
567
  */
568
  protected function get_queried_language( $taxonomies, $args ) {
569
- // does nothing except on taxonomies which are filterable
570
  if ( ! $this->model->is_translated_taxonomy( $taxonomies ) ) {
571
  return false;
572
  }
573
 
574
- // if get_terms is queried with a 'lang' parameter
575
  if ( ! empty( $args['lang'] ) ) {
576
  return $args['lang'];
577
  }
578
 
579
- // do not filter 'get_terms_not_translated'
580
  if ( ! empty( $args['pll_get_terms_not_translated'] ) ) {
581
  return false;
582
  }
@@ -585,21 +584,21 @@ class PLL_Admin_Filters_Term {
585
  $screen = get_current_screen(); // since WP 3.1, may not be available the first time(s) get_terms is called
586
  }
587
 
588
- // don't filter nav menus on nav menus screen
589
  if ( isset( $screen ) && 'nav-menus' == $screen->base && in_array( 'nav_menu', $taxonomies ) ) {
590
  return false;
591
  }
592
 
593
- // does nothing in Languages and dasboard admin panels
594
  if ( isset( $screen ) && in_array( $screen->base, array( 'toplevel_page_mlang', 'dashboard' ) ) ) {
595
  return false;
596
  }
597
 
598
- // ajax actions
599
  if ( isset( $_POST['action'] ) ) {
600
- // admin language filter for:
601
- // ajax paginate_links in taxonomies metabox in nav menus panel
602
- // and taxonomies menus items in customizer menus ( since WP 4.3 )
603
  if ( ! empty( $this->curlang ) && in_array( $_POST['action'], array( 'menu-get-metabox', 'load-available-menu-items-customizer' ) ) ) {
604
  return $this->curlang;
605
  }
@@ -615,30 +614,30 @@ class PLL_Admin_Filters_Term {
615
  }
616
  }
617
 
618
- // ajax response for changing the language in the post metabox ( or in the edit-tags panels )
619
  if ( isset( $_POST['lang'] ) ) {
620
  $lang = $this->model->get_language( $_POST['lang'] );
621
  }
622
 
623
- // the post ( or term ) is created with the 'add new' ( translation ) link
624
- // test of $args['page'] to avoid filtering the terms list table in edit-tags panel
625
  elseif ( ! empty( $_GET['new_lang'] ) && empty( $args['page'] ) ) {
626
  $lang = $this->model->get_language( $_GET['new_lang'] );
627
  }
628
 
629
  // FIXME can we simplify how we deal with the admin language filter?
630
- // the language filter selection has just changed
631
- // test $screen->base to avoid interference between the language filter and the post language selection and the category parent dropdown list
632
  elseif ( ! empty( $_GET['lang'] ) && ! ( isset( $screen ) && in_array( $screen->base, array( 'post', 'edit-tags', 'term' ) ) ) ) {
633
  if ( 'all' != $_GET['lang'] ) {
634
  $lang = $this->model->get_language( $_GET['lang'] );
635
  }
636
  elseif ( in_array( $screen->base, array( 'edit-tags', 'term' ) ) && isset( $args['class'] ) ) {
637
- $lang = $this->pref_lang; // parent dropdown
638
  }
639
  }
640
 
641
- // again the language filter
642
  elseif ( ! empty( $this->curlang ) && ( isset( $screen ) && 'post' != $screen->base && ! ( in_array( $screen->base, array( 'edit-tags', 'term' ) ) && isset( $args['class'] ) ) ) ) { // don't apply to post edit and the category parent dropdown list
643
  $lang = $this->curlang;
644
  }
@@ -647,17 +646,17 @@ class PLL_Admin_Filters_Term {
647
  $lang = $this->model->post->get_language( $_GET['post'] );
648
  }
649
 
650
- // for the parent dropdown list in edit term
651
  elseif ( isset( $_GET['tag_ID'] ) ) {
652
  $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] );
653
  }
654
 
655
- // when a new category is created in the edit post panel
656
  elseif ( isset( $_POST['term_lang_choice'] ) ) {
657
  $lang = $this->model->get_language( $_POST['term_lang_choice'] );
658
  }
659
 
660
- // for a new post ( or the parent dropdown list of a new term )
661
  elseif ( isset( $screen ) && ( 'post' == $screen->base || ( in_array( $screen->base, array( 'edit-tags', 'term' ) ) && isset( $args['class'] ) ) ) ) {
662
  $lang = $this->pref_lang;
663
  }
@@ -666,8 +665,8 @@ class PLL_Admin_Filters_Term {
666
  }
667
 
668
  /**
669
- * adds language dependent cache domain when querying terms
670
- * useful as the 'lang' parameter is not included in cache key by WordPress
671
  *
672
  * @since 1.3
673
  *
@@ -684,7 +683,7 @@ class PLL_Admin_Filters_Term {
684
  }
685
 
686
  /**
687
- * filters categories and post tags by language(s) when needed on admin side
688
  *
689
  * @since 0.5
690
  *
@@ -699,8 +698,8 @@ class PLL_Admin_Filters_Term {
699
  }
700
 
701
  /**
702
- * hack to avoid displaying delete link for the default category in all languages
703
- * also returns the default category in the right language when called from wp_delete_term
704
  *
705
  * @since 1.2
706
  *
@@ -721,17 +720,17 @@ class PLL_Admin_Filters_Term {
721
  }
722
  }
723
 
724
- // filters the default category in note below the category list table and in settings->writing dropdown
725
- elseif ( false !== stripos( $traces[3]['file'], 'edit-tags.php' ) || false !== stripos( $traces[3]['file'], 'options-writing.php' ) ) {
726
- return $this->model->term->get( $value, $this->pref_lang );
727
  }
728
 
729
  return $value;
730
  }
731
 
732
  /**
733
- * checks if the new default category is translated in all languages
734
- * if not, create the translations
735
  *
736
  * @since 1.7
737
  *
@@ -741,7 +740,7 @@ class PLL_Admin_Filters_Term {
741
  public function update_option_default_category( $old_value, $value ) {
742
  $default_cat_lang = $this->model->term->get_language( $value );
743
 
744
- // assign a default language to default category
745
  if ( ! $default_cat_lang ) {
746
  $default_cat_lang = $this->model->get_language( $this->options['default_lang'] );
747
  $this->model->term->set_language( (int) $value, $default_cat_lang );
@@ -755,8 +754,8 @@ class PLL_Admin_Filters_Term {
755
  }
756
 
757
  /**
758
- * updates the translations term ids when splitting a shared term
759
- * splits translations if these are shared terms too
760
  *
761
  * @since 1.7
762
  *
@@ -770,7 +769,7 @@ class PLL_Admin_Filters_Term {
770
  return;
771
  }
772
 
773
- // avoid recursion
774
  static $avoid_recursion = false;
775
  if ( $avoid_recursion ) {
776
  return;
@@ -787,7 +786,7 @@ class PLL_Admin_Filters_Term {
787
  $tr_term = get_term( $tr_id, $taxonomy );
788
  $translations[ $key ] = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id );
789
 
790
- // hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
791
  if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) {
792
  $_POST['term_tr_lang'][ $key ] = $translations[ $key ];
793
  }
1
  <?php
2
 
3
  /**
4
+ * Manages filters and actions related to terms on admin side
5
  *
6
  * @since 1.2
7
  */
8
  class PLL_Admin_Filters_Term {
9
  public $links, $model, $options, $curlang, $pref_lang;
10
+ protected $pre_term_name; // Used to store the term name before creating a slug if needed
11
+ protected $post_id; // Used to store the current post_id when bulk editing posts
12
 
13
  /**
14
+ * Constructor: setups filters and actions
15
  *
16
  * @param object $polylang
17
  */
23
  $this->pref_lang = &$polylang->pref_lang;
24
 
25
  foreach ( $this->model->get_translated_taxonomies() as $tax ) {
26
+ // Adds the language field in the 'Categories' and 'Post Tags' panels
27
  add_action( $tax.'_add_form_fields', array( &$this, 'add_term_form' ) );
28
 
29
+ // Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
30
  add_action( $tax.'_edit_form_fields', array( &$this, 'edit_term_form' ) );
31
 
32
+ // Adds action related to languages when deleting categories and post tags
33
  add_action( 'delete_'.$tax, array( &$this, 'delete_term' ) );
34
  }
35
 
36
+ // Adds actions related to languages when creating or saving categories and post tags
37
  add_filter( 'wp_dropdown_cats', array( &$this, 'wp_dropdown_cats' ) );
38
  add_action( 'create_term', array( &$this, 'save_term' ), 999, 3 );
39
  add_action( 'edit_term', array( &$this, 'save_term' ), 999, 3 ); // late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast
41
  add_filter( 'pre_term_name', array( &$this, 'pre_term_name' ) );
42
  add_filter( 'pre_term_slug', array( &$this, 'pre_term_slug' ), 10, 2 );
43
 
44
+ // Ajax response for edit term form
45
  add_action( 'wp_ajax_term_lang_choice', array( &$this, 'term_lang_choice' ) );
46
  add_action( 'wp_ajax_pll_terms_not_translated', array( &$this, 'ajax_terms_not_translated' ) );
47
 
48
+ // Adds cache domain when querying terms
49
  add_filter( 'get_terms_args', array( &$this, 'get_terms_args' ), 10, 2 );
50
 
51
+ // Filters categories and post tags by language
52
  add_filter( 'terms_clauses', array( &$this, 'terms_clauses' ), 10, 3 );
53
 
54
+ // Allows to get the default categories in all languages
55
  add_filter( 'option_default_category', array( &$this, 'option_default_category' ) );
56
  add_action( 'update_option_default_category', array( &$this, 'update_option_default_category' ), 10, 2 );
57
 
58
+ // Updates the translations term ids when splitting a shared term
59
  add_action( 'split_shared_term', array( &$this, 'split_shared_term' ), 10, 4 ); // WP 4.2
60
  }
61
 
62
  /**
63
+ * Adds the language field in the 'Categories' and 'Post Tags' panels
64
  *
65
  * @since 0.1
66
  */
97
  printf( '<input type="hidden" name="from_tag" value="%d" />', $_GET['from_tag'] );
98
  }
99
 
100
+ // Adds translation fields
101
  echo '<div id="term-translations" class="form-field">';
102
  if ( $lang ) {
103
  include( PLL_ADMIN_INC.'/view-translations-term.php' );
106
  }
107
 
108
  /**
109
+ * Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
110
  *
111
  * @since 0.1
112
  */
122
 
123
  $dropdown = new PLL_Walker_Dropdown();
124
 
125
+ // Disable the language dropdown and the translations input fields for default categories to prevent removal
126
  $disabled = in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) );
127
 
128
  wp_nonce_field( 'pll_language', '_pll_nonce' );
156
  }
157
 
158
  /**
159
+ * Translates term parent if exists when using "Add new" ( translation )
160
  *
161
  * @since 0.7
162
  *
187
  }
188
 
189
  /**
190
+ * Allows to set a language by default for terms if it has no language yet
191
  *
192
  * @since 1.5.4
193
  *
196
  */
197
  protected function set_default_language( $term_id, $taxonomy ) {
198
  if ( ! $this->model->term->get_language( $term_id ) ) {
199
+ // Sets language from term parent if exists thanks to Scott Kingsley Clark
200
  if ( ( $term = get_term( $term_id, $taxonomy ) ) && ! empty( $term->parent ) && $parent_lang = $this->model->term->get_language( $term->parent ) ) {
201
  $this->model->term->set_language( $term_id, $parent_lang );
202
  }
207
  }
208
 
209
  /**
210
+ * Saves language
211
  *
212
  * @since 1.5
213
  *
216
  */
217
  protected function save_language( $term_id, $taxonomy ) {
218
  global $wpdb;
219
+ // Security checks are necessary to accept language modifications
220
  // as 'wp_update_term' can be called from outside WP admin
221
 
222
+ // Edit tags
223
  if ( isset( $_POST['term_lang_choice'] ) ) {
224
  if ( 'add-' . $taxonomy == $_POST['action'] ) {
225
  check_ajax_referer( $_POST['action'], '_ajax_nonce-add-' . $taxonomy ); // category metabox
231
  $this->model->term->set_language( $term_id, $this->model->get_language( $_POST['term_lang_choice'] ) );
232
  }
233
 
234
+ // *Post* bulk edit, in case a new term is created
235
  elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
236
  check_admin_referer( 'bulk-posts' );
237
 
238
+ // Bulk edit does not modify the language
239
+ // So we possibly create a tag in several languages
240
  if ( -1 == $_GET['inline_lang_choice'] ) {
241
+ // The language of the current term is set a according to the language of the current post
242
  $this->model->term->set_language( $term_id, $this->model->post->get_language( $this->post_id ) );
243
  $term = get_term( $term_id, $taxonomy );
244
 
245
+ // Get all terms with the same name
246
  // FIXME backward compatibility WP < 4.2
247
+ // No WP function to get all terms with the exact same name so let's use a custom query
248
  // $terms = get_terms( $taxonomy, array( 'name' => $term->name, 'hide_empty' => false, 'fields' => 'ids' ) ); should be OK in 4.2
249
  // I may need to rework the loop below
250
  $terms = $wpdb->get_results( $wpdb->prepare( "
254
  $taxonomy, $term->name
255
  ) );
256
 
257
+ // If we have several terms with the same name, they are translations of each other
258
  if ( count( $terms ) > 1 ) {
259
  foreach ( $terms as $term ) {
260
  $translations[ $this->model->term->get_language( $term->term_id )->slug ] = $term->term_id;
269
  }
270
  }
271
 
272
+ // Quick edit
273
  elseif ( isset( $_POST['inline_lang_choice'] ) ) {
274
  check_ajax_referer(
275
+ isset( $_POST['action'] ) && 'inline-save' == $_POST['action'] ? 'inlineeditnonce' : 'taxinlineeditnonce', // Post quick edit or tag quick edit ?
276
  '_inline_edit'
277
  );
278
 
279
+ $old_lang = $this->model->term->get_language( $term_id ); // Stores the old language
280
+ $lang = $this->model->get_language( $_POST['inline_lang_choice'] ); // New language
281
  $translations = $this->model->term->get_translations( $term_id );
282
 
283
+ // Checks if the new language already exists in the translation group
284
  if ( $old_lang && $old_lang->slug != $lang->slug ) {
285
  if ( array_key_exists( $lang->slug, $translations ) ) {
286
  $this->model->term->delete_translation( $term_id );
292
  }
293
  }
294
 
295
+ $this->model->term->set_language( $term_id, $lang ); // Set new language
296
  }
297
 
298
+ // Edit post
299
+ elseif ( isset( $_POST['post_lang_choice'] ) ) { // FIXME should be useless now
300
  check_admin_referer( 'pll_language', '_pll_nonce' );
301
  $this->model->term->set_language( $term_id, $this->model->get_language( $_POST['post_lang_choice'] ) );
302
  }
307
  }
308
 
309
  /**
310
+ * Save translations from our form
311
  *
312
  * @since 1.5
313
  *
315
  * @return array
316
  */
317
  protected function save_translations( $term_id ) {
318
+ // Security check as 'wp_update_term' can be called from outside WP admin
 
319
  check_admin_referer( 'pll_language', '_pll_nonce' );
320
 
321
+ // Save translations after checking the translated term is in the right language ( as well as cast id to int )
322
  foreach ( $_POST['term_tr_lang'] as $lang => $tr_id ) {
323
  $tr_lang = $this->model->term->get_language( (int) $tr_id );
324
  $translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? (int) $tr_id : 0;
330
  }
331
 
332
  /**
333
+ * Called when a category or post tag is created or edited
334
+ * Saves language and translations
335
  *
336
  * @since 0.1
337
  *
340
  * @param string $taxonomy
341
  */
342
  public function save_term( $term_id, $tt_id, $taxonomy ) {
343
+ // Does nothing except on taxonomies which are filterable
344
  if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
345
  return;
346
  }
347
 
348
+ // Capability check
349
+ // As 'wp_update_term' can be called from outside WP admin
350
  // 2nd test for creating tags when creating / editing a post
351
  $tax = get_taxonomy( $taxonomy );
352
  if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) {
368
  do_action( 'pll_save_term', $term_id, $taxonomy, empty( $translations ) ? $this->model->term->get_translations( $term_id ) : $translations );
369
  }
370
 
371
+ // Attempts to set a default language even if no capability
372
  else {
373
  $this->set_default_language( $term_id, $taxonomy );
374
  }
375
  }
376
 
377
  /**
378
+ * Stores the term name for use in pre_term_slug
379
  *
380
  * @since 0.9.5
381
  *
387
  }
388
 
389
  /**
390
+ * Creates the term slug in case the term already exists in another language
391
  *
392
  * @since 0.9.5
393
  *
398
  public function pre_term_slug( $slug, $taxonomy ) {
399
  $name = sanitize_title( $this->pre_term_name );
400
 
401
+ // If the new term has the same name as a language, we *need* to differentiate the term
402
+ // See http://core.trac.wordpress.org/ticket/23199
403
+ // Backward compatibility with WP < 4.1
404
  if ( version_compare( $GLOBALS['wp_version'], '4.1', '<' ) && term_exists( $name, 'language' ) && ! term_exists( $name, $taxonomy ) && ( ! $slug || $slug == $name ) ) {
405
+ $slug = $name . '-' . $taxonomy; // A convenient slug which may be modified later by the user
406
  }
407
 
408
+ // If the term already exists in another language
409
  if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
410
  if ( isset( $_POST['term_lang_choice'] ) ) {
411
  $slug = $name . '-' . $this->model->get_language( $_POST['term_lang_choice'] )->slug;
415
  $slug = $name . '-' . $this->model->get_language( $_POST['inline_lang_choice'] )->slug;
416
  }
417
 
418
+ // *Post* bulk edit, in case a new term is created
419
  elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
420
+ // Bulk edit does not modify the language
421
  if ( -1 == $_GET['inline_lang_choice'] ) {
422
  $slug = $name . '-' . $this->model->post->get_language( $this->post_id )->slug;
423
  }
431
  }
432
 
433
  /**
434
+ * Called when a category or post tag is deleted
435
+ * Deletes language and translations
436
  *
437
  * @since 0.1
438
  *
444
  }
445
 
446
  /**
447
+ * Ajax response for edit term form
448
  *
449
  * @since 0.2
450
  */
467
  $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
468
  ob_end_clean();
469
 
470
+ // Parent dropdown list ( only for hierarchical taxonomies )
471
  // $args copied from edit_tags.php except echo
472
  if ( is_taxonomy_hierarchical( $taxonomy ) ) {
473
  $args = array(
483
  $x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) );
484
  }
485
 
486
+ // Tag cloud
487
+ // Tests copied from edit_tags.php
488
  else {
489
  $tax = get_taxonomy( $taxonomy );
490
  if ( ! is_null( $tax->labels->popular_items ) ) {
499
  }
500
  }
501
 
502
+ // Flag
503
  $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
504
 
505
  $x->send();
506
  }
507
 
508
  /**
509
+ * Ajax response for input in translation autocomplete input box
510
  *
511
  * @since 1.5
512
  */
526
 
527
  $return = array();
528
 
529
+ // It is more efficient to use one common query for all languages as soon as there are more than 2
530
  // pll_get_terms_not_translated arg to identify this query in terms_clauses filter
531
  foreach ( get_terms( $taxonomy, 'hide_empty=0&pll_get_terms_not_translated=1&name__like=' . $s ) as $term ) {
532
  $lang = $this->model->term->get_language( $term->term_id );
540
  }
541
  }
542
 
543
+ // Add current translation in list
544
+ // Not in add term for as term_id is not set
545
  if ( 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) {
546
  $term = get_term( $term_id, $taxonomy );
547
  array_unshift( $return, array(
556
 
557
 
558
  /**
559
+ * Get the language(s) to filter get_terms
560
  *
561
  * @since 1.7.6
562
  *
565
  * @return object|string|bool the language(s) to use in the filter, false otherwise
566
  */
567
  protected function get_queried_language( $taxonomies, $args ) {
568
+ // Does nothing except on taxonomies which are filterable
569
  if ( ! $this->model->is_translated_taxonomy( $taxonomies ) ) {
570
  return false;
571
  }
572
 
573
+ // If get_terms is queried with a 'lang' parameter
574
  if ( ! empty( $args['lang'] ) ) {
575
  return $args['lang'];
576
  }
577
 
578
+ // Do not filter 'get_terms_not_translated'
579
  if ( ! empty( $args['pll_get_terms_not_translated'] ) ) {
580
  return false;
581
  }
584
  $screen = get_current_screen(); // since WP 3.1, may not be available the first time(s) get_terms is called
585
  }
586
 
587
+ // Don't filter nav menus on nav menus screen
588
  if ( isset( $screen ) && 'nav-menus' == $screen->base && in_array( 'nav_menu', $taxonomies ) ) {
589
  return false;
590
  }
591
 
592
+ // Does nothing in Languages and dasboard admin panels
593
  if ( isset( $screen ) && in_array( $screen->base, array( 'toplevel_page_mlang', 'dashboard' ) ) ) {
594
  return false;
595
  }
596
 
597
+ // Ajax actions
598
  if ( isset( $_POST['action'] ) ) {
599
+ // Admin language filter for:
600
+ // Ajax paginate_links in taxonomies metabox in nav menus panel
601
+ // And taxonomies menus items in customizer menus ( since WP 4.3 )
602
  if ( ! empty( $this->curlang ) && in_array( $_POST['action'], array( 'menu-get-metabox', 'load-available-menu-items-customizer' ) ) ) {
603
  return $this->curlang;
604
  }
614
  }
615
  }
616
 
617
+ // Ajax response for changing the language in the post metabox ( or in the edit-tags panels )
618
  if ( isset( $_POST['lang'] ) ) {
619
  $lang = $this->model->get_language( $_POST['lang'] );
620
  }
621
 
622
+ // The post ( or term ) is created with the 'add new' ( translation ) link
623
+ // Test of $args['page'] to avoid filtering the terms list table in edit-tags panel
624
  elseif ( ! empty( $_GET['new_lang'] ) && empty( $args['page'] ) ) {
625
  $lang = $this->model->get_language( $_GET['new_lang'] );
626
  }
627
 
628
  // FIXME can we simplify how we deal with the admin language filter?
629
+ // The language filter selection has just changed
630
+ // Test $screen->base to avoid interference between the language filter and the post language selection and the category parent dropdown list
631
  elseif ( ! empty( $_GET['lang'] ) && ! ( isset( $screen ) && in_array( $screen->base, array( 'post', 'edit-tags', 'term' ) ) ) ) {
632
  if ( 'all' != $_GET['lang'] ) {
633
  $lang = $this->model->get_language( $_GET['lang'] );
634
  }
635
  elseif ( in_array( $screen->base, array( 'edit-tags', 'term' ) ) && isset( $args['class'] ) ) {
636
+ $lang = $this->pref_lang; // Parent dropdown
637
  }
638
  }
639
 
640
+ // Again the language filter
641
  elseif ( ! empty( $this->curlang ) && ( isset( $screen ) && 'post' != $screen->base && ! ( in_array( $screen->base, array( 'edit-tags', 'term' ) ) && isset( $args['class'] ) ) ) ) { // don't apply to post edit and the category parent dropdown list
642
  $lang = $this->curlang;
643
  }
646
  $lang = $this->model->post->get_language( $_GET['post'] );
647
  }
648
 
649
+ // For the parent dropdown list in edit term
650
  elseif ( isset( $_GET['tag_ID'] ) ) {
651
  $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] );
652
  }
653
 
654
+ // When a new category is created in the edit post panel
655
  elseif ( isset( $_POST['term_lang_choice'] ) ) {
656
  $lang = $this->model->get_language( $_POST['term_lang_choice'] );
657
  }
658
 
659
+ // For a new post ( or the parent dropdown list of a new term )
660
  elseif ( isset( $screen ) && ( 'post' == $screen->base || ( in_array( $screen->base, array( 'edit-tags', 'term' ) ) && isset( $args['class'] ) ) ) ) {
661
  $lang = $this->pref_lang;
662
  }
665
  }
666
 
667
  /**
668
+ * Adds language dependent cache domain when querying terms
669
+ * Useful as the 'lang' parameter is not included in cache key by WordPress
670
  *
671
  * @since 1.3
672
  *
683
  }
684
 
685
  /**
686
+ * Filters categories and post tags by language(s) when needed on admin side
687
  *
688
  * @since 0.5
689
  *
698
  }
699
 
700
  /**
701
+ * Hack to avoid displaying delete link for the default category in all languages
702
+ * Also returns the default category in the right language when called from wp_delete_term
703
  *
704
  * @since 1.2
705
  *
720
  }
721
  }
722
 
723
+ // Filters the default category in note below the category list table and in settings->writing dropdown
724
+ elseif ( isset( $traces[3]['file'] ) && ( false !== stripos( $traces[3]['file'], 'edit-tags.php' ) || false !== stripos( $traces[3]['file'], 'options-writing.php' ) ) ) {
725
+ return $this->model->term->get( $value, $this->pref_lang );
726
  }
727
 
728
  return $value;
729
  }
730
 
731
  /**
732
+ * Checks if the new default category is translated in all languages
733
+ * If not, create the translations
734
  *
735
  * @since 1.7
736
  *
740
  public function update_option_default_category( $old_value, $value ) {
741
  $default_cat_lang = $this->model->term->get_language( $value );
742
 
743
+ // Assign a default language to default category
744
  if ( ! $default_cat_lang ) {
745
  $default_cat_lang = $this->model->get_language( $this->options['default_lang'] );
746
  $this->model->term->set_language( (int) $value, $default_cat_lang );
754
  }
755
 
756
  /**
757
+ * Updates the translations term ids when splitting a shared term
758
+ * Splits translations if these are shared terms too
759
  *
760
  * @since 1.7
761
  *
769
  return;
770
  }
771
 
772
+ // Avoid recursion
773
  static $avoid_recursion = false;
774
  if ( $avoid_recursion ) {
775
  return;
786
  $tr_term = get_term( $tr_id, $taxonomy );
787
  $translations[ $key ] = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id );
788
 
789
+ // Hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
790
  if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) {
791
  $_POST['term_tr_lang'][ $key ] = $translations[ $key ];
792
  }
include/language.php CHANGED
@@ -223,17 +223,19 @@ class PLL_Language {
223
  public function get_locale( $filter = 'raw' ) {
224
  if ( 'display' == $filter ) {
225
  static $valid_locales = array(
226
- 'bel' => 'be',
227
- 'bre' => 'br',
228
- 'de_DE_formal' => 'de_DE',
229
- 'dzo' => 'dz',
230
- 'ido' => 'io',
231
- 'kin' => 'rw',
232
- 'oci' => 'oc',
233
- 'mri' => 'mi',
234
- 'roh' => 'rm',
235
- 'srd' => 'sc',
236
- 'tuk' => 'tk',
 
 
237
  );
238
  $locale = isset( $valid_locales[ $this->locale ] ) ? $valid_locales[ $this->locale ] : $this->locale;
239
  return str_replace( '_', '-', $locale );
223
  public function get_locale( $filter = 'raw' ) {
224
  if ( 'display' == $filter ) {
225
  static $valid_locales = array(
226
+ 'bel' => 'be',
227
+ 'bre' => 'br',
228
+ 'de_CH_informal' => 'de_CH',
229
+ 'de_DE_formal' => 'de_DE',
230
+ 'dzo' => 'dz',
231
+ 'ido' => 'io',
232
+ 'kin' => 'rw',
233
+ 'oci' => 'oc',
234
+ 'mri' => 'mi',
235
+ 'nl_NL_formal' => 'nl_NL',
236
+ 'roh' => 'rm',
237
+ 'srd' => 'sc',
238
+ 'tuk' => 'tk',
239
  );
240
  $locale = isset( $valid_locales[ $this->locale ] ) ? $valid_locales[ $this->locale ] : $this->locale;
241
  return str_replace( '_', '-', $locale );
include/links-directory.php CHANGED
@@ -21,11 +21,9 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
21
 
22
  if ( did_action( 'pll_init' ) ) {
23
  $this->init();
24
- }
25
- else {
26
  add_action( 'pll_init', array( &$this, 'init' ) );
27
  }
28
-
29
  }
30
 
31
  /**
@@ -36,8 +34,7 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
36
  public function init() {
37
  if ( did_action( 'setup_theme' ) ) {
38
  $this->add_permastruct();
39
- }
40
- else {
41
  add_action( 'setup_theme', array( &$this, 'add_permastruct' ), 2 );
42
  }
43
 
@@ -184,6 +181,21 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
184
  foreach ( $rules as $key => $rule ) {
185
  // Special case for translated post types and taxonomies to allow canonical redirection
186
  if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  if ( isset( $slug ) ) {
188
  $newrules[ $slug . str_replace( $wp_rewrite->root, '', $key ) ] = str_replace(
189
  array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
@@ -200,7 +212,13 @@ class PLL_Links_Directory extends PLL_Links_Permalinks {
200
  }
201
 
202
  // Rewrite rules filtered by language
203
- elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) {
 
 
 
 
 
 
204
  if ( isset( $slug ) ) {
205
  $newrules[ $slug . str_replace( $wp_rewrite->root, '', $key ) ] = str_replace(
206
  array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
21
 
22
  if ( did_action( 'pll_init' ) ) {
23
  $this->init();
24
+ } else {
 
25
  add_action( 'pll_init', array( &$this, 'init' ) );
26
  }
 
27
  }
28
 
29
  /**
34
  public function init() {
35
  if ( did_action( 'setup_theme' ) ) {
36
  $this->add_permastruct();
37
+ } else {
 
38
  add_action( 'setup_theme', array( &$this, 'add_permastruct' ), 2 );
39
  }
40
 
181
  foreach ( $rules as $key => $rule ) {
182
  // Special case for translated post types and taxonomies to allow canonical redirection
183
  if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) {
184
+
185
+ /**
186
+ * Filters the rewrite rules to modify
187
+ *
188
+ * @since 1.9.1
189
+ *
190
+ * @param bool $modify whether to modify or not the rule, defaults to true
191
+ * @param array $rule original rewrite rule
192
+ * @param string $filter current set of rules being modified
193
+ * @param string|bool $archive custom post post type archive name or false if it is not a cpt archive
194
+ */
195
+ if ( ! apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, false ) ) {
196
+ continue;
197
+ }
198
+
199
  if ( isset( $slug ) ) {
200
  $newrules[ $slug . str_replace( $wp_rewrite->root, '', $key ) ] = str_replace(
201
  array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
212
  }
213
 
214
  // Rewrite rules filtered by language
215
+ elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule, $matches ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) {
216
+
217
+ /** This filter is documented in include/links-directory.php */
218
+ if ( ! apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, empty( $matches[1] ) ? false : $matches[1] ) ) {
219
+ continue;
220
+ }
221
+
222
  if ( isset( $slug ) ) {
223
  $newrules[ $slug . str_replace( $wp_rewrite->root, '', $key ) ] = str_replace(
224
  array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
include/links.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
 
3
  /**
4
- * manages links related functions
5
  *
6
  * @since 1.2
7
  */
@@ -9,7 +9,7 @@ class PLL_Links {
9
  public $links_model, $model, $options;
10
 
11
  /**
12
- * constructor
13
  *
14
  * @since 1.2
15
  *
@@ -22,12 +22,12 @@ class PLL_Links {
22
  }
23
 
24
  /**
25
- * returns the home url in the requested language
26
  *
27
  * @since 1.3
28
  *
29
  * @param object|string $language
30
- * @param bool $is_search optional wether we need the home url for a search form, defaults to false
31
  */
32
  public function get_home_url( $language, $is_search = false ) {
33
  $language = is_object( $language ) ? $language : $this->model->get_language( $language );
@@ -35,7 +35,7 @@ class PLL_Links {
35
  }
36
 
37
  /**
38
- * checks if the current user can read the post
39
  *
40
  * @since 1.5
41
  *
@@ -44,12 +44,17 @@ class PLL_Links {
44
  */
45
  public function current_user_can_read( $post_id ) {
46
  $post = get_post( $post_id );
 
 
 
 
 
47
  if ( in_array( $post->post_status, get_post_stati( array( 'public' => true ) ) ) ) {
48
  return true;
49
  }
50
 
51
  $post_type_object = get_post_type_object( $post->post_type );
52
  $user = wp_get_current_user();
53
- return is_user_logged_in() && ( current_user_can( $post_type_object->cap->read_private_posts ) || $user->ID == $post->post_author );
54
  }
55
  }
1
  <?php
2
 
3
  /**
4
+ * Manages links related functions
5
  *
6
  * @since 1.2
7
  */
9
  public $links_model, $model, $options;
10
 
11
  /**
12
+ * Constructor
13
  *
14
  * @since 1.2
15
  *
22
  }
23
 
24
  /**
25
+ * Returns the home url in the requested language
26
  *
27
  * @since 1.3
28
  *
29
  * @param object|string $language
30
+ * @param bool $is_search optional wether we need the home url for a search form, defaults to false
31
  */
32
  public function get_home_url( $language, $is_search = false ) {
33
  $language = is_object( $language ) ? $language : $this->model->get_language( $language );
35
  }
36
 
37
  /**
38
+ * Checks if the current user can read the post
39
  *
40
  * @since 1.5
41
  *
44
  */
45
  public function current_user_can_read( $post_id ) {
46
  $post = get_post( $post_id );
47
+
48
+ if ( 'inherit' === $post->post_status ) {
49
+ $post = get_post( $post->post_parent );
50
+ }
51
+
52
  if ( in_array( $post->post_status, get_post_stati( array( 'public' => true ) ) ) ) {
53
  return true;
54
  }
55
 
56
  $post_type_object = get_post_type_object( $post->post_type );
57
  $user = wp_get_current_user();
58
+ 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!
59
  }
60
  }
include/translated-object.php CHANGED
@@ -177,7 +177,9 @@ abstract class PLL_Translated_Object {
177
  $translations = empty( $term ) ? array() : unserialize( $term->description );
178
 
179
  // make sure we return only translations ( thus we allow plugins to store other informations in the array )
180
- $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) );
 
 
181
 
182
  // make sure to return at least the passed post or term in its translation array
183
  if ( empty( $translations ) && $lang = $this->get_language( $id ) ) {
177
  $translations = empty( $term ) ? array() : unserialize( $term->description );
178
 
179
  // make sure we return only translations ( thus we allow plugins to store other informations in the array )
180
+ if ( is_array( $translations ) ) {
181
+ $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) );
182
+ }
183
 
184
  // make sure to return at least the passed post or term in its translation array
185
  if ( empty( $translations ) && $lang = $this->get_language( $id ) ) {
modules/sync/admin-sync.php CHANGED
@@ -56,12 +56,16 @@ class PLL_Admin_Sync {
56
  if ( 'post-new.php' == $GLOBALS['pagenow'] && isset( $_GET['from_post'], $_GET['new_lang'] ) && $this->model->is_translated_post_type( $post->post_type ) ) {
57
  // capability check already done in post-new.php
58
  $from_post_id = (int) $_GET['from_post'];
 
59
  $lang = $this->model->get_language( $_GET['new_lang'] );
60
 
 
 
 
 
61
  $this->copy_taxonomies( $from_post_id, $post->ID, $lang->slug );
62
  $this->copy_post_metas( $from_post_id, $post->ID, $lang->slug );
63
 
64
- $from_post = get_post( $from_post_id );
65
  foreach ( array( 'menu_order', 'comment_status', 'ping_status' ) as $property ) {
66
  $post->$property = $from_post->$property;
67
  }
56
  if ( 'post-new.php' == $GLOBALS['pagenow'] && isset( $_GET['from_post'], $_GET['new_lang'] ) && $this->model->is_translated_post_type( $post->post_type ) ) {
57
  // capability check already done in post-new.php
58
  $from_post_id = (int) $_GET['from_post'];
59
+ $from_post = get_post( $from_post_id );
60
  $lang = $this->model->get_language( $_GET['new_lang'] );
61
 
62
+ if ( ! $from_post || ! $lang ) {
63
+ return;
64
+ }
65
+
66
  $this->copy_taxonomies( $from_post_id, $post->ID, $lang->slug );
67
  $this->copy_post_metas( $from_post_id, $post->ID, $lang->slug );
68
 
 
69
  foreach ( array( 'menu_order', 'comment_status', 'ping_status' ) as $property ) {
70
  $post->$property = $from_post->$property;
71
  }
polylang.php CHANGED
@@ -3,7 +3,7 @@
3
  /*
4
  Plugin Name: Polylang
5
  Plugin URI: https://polylang.pro
6
- Version: 1.9
7
  Author: Frédéric Demarle
8
  Author uri: https://polylang.pro
9
  Description: Adds multilingual capability to WordPress
@@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) {
35
  exit; // don't access directly
36
  };
37
 
38
- define( 'POLYLANG_VERSION', '1.9' );
39
  define( 'PLL_MIN_WP_VERSION', '4.0' );
40
 
41
  define( 'POLYLANG_FILE', __FILE__ ); // this file
3
  /*
4
  Plugin Name: Polylang
5
  Plugin URI: https://polylang.pro
6
+ Version: 1.9.1
7
  Author: Frédéric Demarle
8
  Author uri: https://polylang.pro
9
  Description: Adds multilingual capability to WordPress
35
  exit; // don't access directly
36
  };
37
 
38
+ define( 'POLYLANG_VERSION', '1.9.1' );
39
  define( 'PLL_MIN_WP_VERSION', '4.0' );
40
 
41
  define( 'POLYLANG_FILE', __FILE__ ); // this file
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://polylang.pro
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
  Requires at least: 4.0
6
  Tested up to: 4.5
7
- Stable tag: 1.9
8
  License: GPLv2 or later
9
 
10
  Making WordPress multilingual
@@ -73,7 +73,14 @@ Don't hesitate to [give your feedback](http://wordpress.org/support/view/plugin-
73
 
74
  == Changelog ==
75
 
76
- = 1.9 ( 2016-04-27 ) =
 
 
 
 
 
 
 
77
 
78
  * Pro: add the possibility to translate custom post types slugs, taxonomies slugs and more
79
  * Pro: add the possibility to share the same post or term slug accross languages
4
  Tags: multilingual, bilingual, translate, translation, language, multilanguage, international, localization
5
  Requires at least: 4.0
6
  Tested up to: 4.5
7
+ Stable tag: 1.9.1
8
  License: GPLv2 or later
9
 
10
  Making WordPress multilingual
73
 
74
  == Changelog ==
75
 
76
+ = 1.9.1 (2016-05-23) =
77
+
78
+ * Pro: add compatibility with Beaver Builder
79
+ * Pro: fix media wrongly created when adding a new media translation
80
+ * Add azb, ceb, de_CH_informal, es_GT, mr, nl_NL_formal to the predefined list of languages
81
+ * Fix the language switcher not linking to media translations for anonymous visitors
82
+
83
+ = 1.9 (2016-04-27) =
84
 
85
  * Pro: add the possibility to translate custom post types slugs, taxonomies slugs and more
86
  * Pro: add the possibility to share the same post or term slug accross languages
settings/languages.php CHANGED
@@ -19,15 +19,18 @@ $languages = array(
19
  'ar' => array( 'ar', 'ar', 'العربية', 'rtl', 'arab' ),
20
  'ary' => array( 'ar', 'ary', 'العربية المغربية', 'rtl', 'ma' ),
21
  'az' => array( 'az', 'az', 'Azərbaycan', 'ltr', 'az' ),
 
22
  'bel' => array( 'be', 'bel', 'Беларуская мова', 'ltr', 'by' ),
23
  'bg_BG' => array( 'bg', 'bg_BG', 'български', 'ltr', 'bg' ),
24
  'bn_BD' => array( 'bn', 'bn_BD', 'বাংলা', 'ltr', 'bd' ),
25
  'bs_BA' => array( 'bs', 'bs_BA', 'Bosanski', 'ltr', 'ba' ),
26
  'ca' => array( 'ca', 'ca', 'Català', 'ltr', 'catalonia' ),
 
27
  'cs_CZ' => array( 'cs', 'cs_CZ', 'Čeština', 'ltr', 'cz' ),
28
  'cy' => array( 'cy', 'cy', 'Cymraeg', 'ltr', 'wales' ),
29
  'da_DK' => array( 'da', 'da_DK', 'Dansk', 'ltr', 'dk' ),
30
  'de_CH' => array( 'de', 'de_CH', 'Deutsch', 'ltr', 'ch' ),
 
31
  'de_DE' => array( 'de', 'de_DE', 'Deutsch', 'ltr', 'de' ),
32
  'de_DE_formal' => array( 'de', 'de_DE_formal', 'Deutsch', 'ltr', 'de' ),
33
  'el' => array( 'el', 'el', 'Ελληνικά', 'ltr', 'gr' ),
@@ -42,6 +45,7 @@ $languages = array(
42
  'es_CL' => array( 'es', 'es_CL', 'Español', 'ltr', 'cl' ),
43
  'es_CO' => array( 'es', 'es_CO', 'Español', 'ltr', 'co' ),
44
  'es_ES' => array( 'es', 'es_ES', 'Español', 'ltr', 'es' ),
 
45
  'es_MX' => array( 'es', 'es_MX', 'Español', 'ltr', 'mx' ),
46
  'es_PE' => array( 'es', 'es_PE', 'Español', 'ltr', 'pe' ),
47
  'es_VE' => array( 'es', 'es_VE', 'Español', 'ltr', 've' ),
@@ -77,11 +81,13 @@ $languages = array(
77
  'lv' => array( 'lv', 'lv', 'Latviešu valoda', 'ltr', 'lv' ),
78
  'mk_MK' => array( 'mk', 'mk_MK', 'македонски јазик', 'ltr', 'mk' ),
79
  'mn' => array( 'mn', 'mn', 'Монгол хэл', 'ltr', 'mn' ),
 
80
  'ms_MY' => array( 'ms', 'ms_MY', 'Bahasa Melayu', 'ltr', 'my' ),
81
  'my_MM' => array( 'my', 'my_MM', 'ဗမာစာ', 'ltr', 'mm' ),
82
  'nb_NO' => array( 'nb', 'nb_NO', 'Norsk Bokmål', 'ltr', 'no' ),
83
  'ne_NP' => array( 'ne', 'ne_NP', 'नेपाली', 'ltr', 'np' ),
84
  'nl_NL' => array( 'nl', 'nl_NL', 'Nederlands', 'ltr', 'nl' ),
 
85
  'nn_NO' => array( 'nn', 'nn_NO', 'Norsk Nynorsk', 'ltr', 'no' ),
86
  'oci' => array( 'oc', 'oci', 'Occitan', 'ltr', 'occitania' ),
87
  'pl_PL' => array( 'pl', 'pl_PL', 'Polski', 'ltr', 'pl' ),
19
  'ar' => array( 'ar', 'ar', 'العربية', 'rtl', 'arab' ),
20
  'ary' => array( 'ar', 'ary', 'العربية المغربية', 'rtl', 'ma' ),
21
  'az' => array( 'az', 'az', 'Azərbaycan', 'ltr', 'az' ),
22
+ 'azb' => array( 'az', 'azb', 'گؤنئی آذربایجان', 'rtl', 'az' ),
23
  'bel' => array( 'be', 'bel', 'Беларуская мова', 'ltr', 'by' ),
24
  'bg_BG' => array( 'bg', 'bg_BG', 'български', 'ltr', 'bg' ),
25
  'bn_BD' => array( 'bn', 'bn_BD', 'বাংলা', 'ltr', 'bd' ),
26
  'bs_BA' => array( 'bs', 'bs_BA', 'Bosanski', 'ltr', 'ba' ),
27
  'ca' => array( 'ca', 'ca', 'Català', 'ltr', 'catalonia' ),
28
+ 'ceb' => array( 'ceb', 'ceb', 'Cebuano', 'ltr', 'ph' ),
29
  'cs_CZ' => array( 'cs', 'cs_CZ', 'Čeština', 'ltr', 'cz' ),
30
  'cy' => array( 'cy', 'cy', 'Cymraeg', 'ltr', 'wales' ),
31
  'da_DK' => array( 'da', 'da_DK', 'Dansk', 'ltr', 'dk' ),
32
  'de_CH' => array( 'de', 'de_CH', 'Deutsch', 'ltr', 'ch' ),
33
+ 'de_CH_informal' => array( 'de', 'de_CH_informal', 'Deutsch', 'ltr', 'ch' ),
34
  'de_DE' => array( 'de', 'de_DE', 'Deutsch', 'ltr', 'de' ),
35
  'de_DE_formal' => array( 'de', 'de_DE_formal', 'Deutsch', 'ltr', 'de' ),
36
  'el' => array( 'el', 'el', 'Ελληνικά', 'ltr', 'gr' ),
45
  'es_CL' => array( 'es', 'es_CL', 'Español', 'ltr', 'cl' ),
46
  'es_CO' => array( 'es', 'es_CO', 'Español', 'ltr', 'co' ),
47
  'es_ES' => array( 'es', 'es_ES', 'Español', 'ltr', 'es' ),
48
+ 'es_GT' => array( 'es', 'es_GT', 'Español', 'ltr', 'gt' ),
49
  'es_MX' => array( 'es', 'es_MX', 'Español', 'ltr', 'mx' ),
50
  'es_PE' => array( 'es', 'es_PE', 'Español', 'ltr', 'pe' ),
51
  'es_VE' => array( 'es', 'es_VE', 'Español', 'ltr', 've' ),
81
  'lv' => array( 'lv', 'lv', 'Latviešu valoda', 'ltr', 'lv' ),
82
  'mk_MK' => array( 'mk', 'mk_MK', 'македонски јазик', 'ltr', 'mk' ),
83
  'mn' => array( 'mn', 'mn', 'Монгол хэл', 'ltr', 'mn' ),
84
+ 'mr' => array( 'mr', 'mr', 'मराठी', 'ltr', 'in' ),
85
  'ms_MY' => array( 'ms', 'ms_MY', 'Bahasa Melayu', 'ltr', 'my' ),
86
  'my_MM' => array( 'my', 'my_MM', 'ဗမာစာ', 'ltr', 'mm' ),
87
  'nb_NO' => array( 'nb', 'nb_NO', 'Norsk Bokmål', 'ltr', 'no' ),
88
  'ne_NP' => array( 'ne', 'ne_NP', 'नेपाली', 'ltr', 'np' ),
89
  'nl_NL' => array( 'nl', 'nl_NL', 'Nederlands', 'ltr', 'nl' ),
90
+ 'nl_NL_formal' => array( 'nl', 'nl_NL_formal', 'Nederlands', 'ltr', 'nl' ),
91
  'nn_NO' => array( 'nn', 'nn_NO', 'Norsk Nynorsk', 'ltr', 'no' ),
92
  'oci' => array( 'oc', 'oci', 'Occitan', 'ltr', 'occitania' ),
93
  'pl_PL' => array( 'pl', 'pl_PL', 'Polski', 'ltr', 'pl' ),