PluginProbe
Polylang / 3.6.7
Polylang v3.6.7
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
← All changes | include/translated-object.php +432 -179 2.73.6.7 View file →
@@ -1,209 +1,310 @@
1 1 <?php
2 +/**
3 + * @package Polylang
4 + */
2 5
6 +defined( 'ABSPATH' ) || exit;
7 +
3 8 /**
4 - * Setups the objects languages and translations model
9 + * Abstract class to use for object types that support translations.
5 10 *
6 11 * @since 1.8
7 12 */
8 -abstract class PLL_Translated_Object {
9 - public $model;
10 - protected $object_type, $type, $tax_language, $tax_translations, $tax_tt;
13 +abstract class PLL_Translated_Object extends PLL_Translatable_Object {
11 14
12 15 /**
13 - * Constructor
16 + * Taxonomy name for the translation groups.
14 17 *
18 + * @var string
19 + *
20 + * @phpstan-var non-empty-string
21 + */
22 + protected $tax_translations;
23 +
24 + /**
25 + * Constructor.
26 + *
15 27 * @since 1.8
16 28 *
17 - * @param object $model
29 + * @param PLL_Model $model Instance of `PLL_Model`.
18 30 */
19 - public function __construct( &$model ) {
20 - $this->model = &$model;
31 + public function __construct( PLL_Model &$model ) {
32 + parent::__construct( $model );
21 33
22 - // register our taxonomies as soon as possible
23 - // this is early registration, not ready for rewrite rules as wp_rewrite will be setup later
24 - $args = array( 'label' => false, 'public' => false, 'query_var' => false, 'rewrite' => false, '_pll' => true );
25 - register_taxonomy( $this->tax_language, $this->object_type, $args );
26 - $args['update_count_callback'] = '_update_generic_term_count'; // count *all* posts to avoid deleting in clean_translations_terms
27 - register_taxonomy( $this->tax_translations, $this->object_type, $args );
34 + $this->tax_to_cache[] = $this->tax_translations;
35 +
36 + /*
37 + * Register our taxonomy as soon as possible.
38 + */
39 + register_taxonomy(
40 + $this->tax_translations,
41 + (array) $this->object_type,
42 + array(
43 + 'label' => false,
44 + 'public' => false,
45 + 'query_var' => false,
46 + 'rewrite' => false,
47 + '_pll' => true,
48 + 'update_count_callback' => '_update_generic_term_count', // Count *all* objects to correctly detect unused terms.
49 + )
50 + );
28 51 }
29 52
30 53 /**
31 - * Wrap wp_get_object_terms to cache it and return only one object
32 - * inspired by the function get_the_terms
54 + * Returns the translations group taxonomy name.
33 55 *
34 - * @since 1.2
56 + * @since 3.4
35 57 *
36 - * @param int $object_id post_id or term_id
37 - * @param string $taxonomy Polylang taxonomy depending if we are looking for a post ( or term ) language ( or translation )
38 - * @return bool|object the term associated to the object in the requested taxonomy if exists, false otherwise
58 + * @return string
59 + *
60 + * @phpstan-return non-empty-string
39 61 */
40 - public function get_object_term( $object_id, $taxonomy ) {
41 - if ( empty( $object_id ) || is_wp_error( $object_id ) ) {
62 + public function get_tax_translations() {
63 + return $this->tax_translations;
64 + }
65 +
66 + /**
67 + * Assigns a language to an object, taking care of the translations group.
68 + *
69 + * @since 3.4
70 + *
71 + * @param int $id Object ID.
72 + * @param PLL_Language|string|int $lang Language to assign to the object.
73 + * @return bool True when successfully assigned. False otherwise (or if the given language is already assigned to
74 + * the object).
75 + */
76 + public function set_language( $id, $lang ) {
77 + if ( ! parent::set_language( $id, $lang ) ) {
42 78 return false;
43 79 }
44 80
45 - $object_id = (int) $object_id;
46 - $term = get_object_term_cache( $object_id, $taxonomy );
81 + $id = $this->sanitize_int_id( $id );
47 82
48 - if ( false === $term ) {
49 - // query language and translations at the same time
50 - $taxonomies = array( $this->tax_language, $this->tax_translations );
83 + $translations = $this->get_translations( $id );
51 84
52 - // query terms
53 - $terms = array();
54 - foreach ( wp_get_object_terms( $object_id, $taxonomies, array( 'update_term_meta_cache' => false ) ) as $t ) {
55 - $terms[ $t->taxonomy ] = $t;
56 - if ( $t->taxonomy == $taxonomy ) {
57 - $term = $t;
58 - }
59 - }
60 -
61 - // store it the way WP wants it
62 - // set an empty cache if no term found in the taxonomy
63 - foreach ( $taxonomies as $tax ) {
64 - wp_cache_add( $object_id, empty( $terms[ $tax ] ) ? array() : array( $terms[ $tax ] ), $tax . '_relationships' );
65 - }
85 + // Don't create translation groups with only 1 value.
86 + if ( ! empty( $translations ) ) {
87 + // Remove the object's former language from the new translations group before adding the new value.
88 + $translations = array_diff( $translations, array( $id ) );
89 + $this->save_translations( $id, $translations );
66 90 }
67 - else {
68 - $term = reset( $term );
69 - }
70 91
71 - return empty( $term ) ? false : $term;
92 + return true;
72 93 }
73 94
74 95 /**
75 - * Tells whether a translation term must be updated
96 + * Returns a list of object translations, given a `tax_translations` term ID.
76 97 *
77 - * @since 2.3
98 + * @since 3.2
78 99 *
79 - * @param array $id Post id or term id
80 - * @param array $translations An associative array of translations with language code as key and translation id as value
100 + * @param int $term_id A `tax_translations` term ID.
101 + * @return int[] An associative array of translations with language code as key and translation ID as value.
102 + *
103 + * @phpstan-return array<non-empty-string, positive-int>
81 104 */
82 - protected function should_update_translation_group( $id, $translations ) {
83 - // Don't do anything if no translations have been added to the group
84 - $old_translations = $this->get_translations( $id ); // Includes at least $id itself
85 - return count( array_diff_assoc( $translations, $old_translations ) ) > 0;
105 + public function get_translations_from_term_id( $term_id ) {
106 + $term_id = $this->sanitize_int_id( $term_id );
107 +
108 + if ( empty( $term_id ) ) {
109 + return array();
110 + }
111 +
112 + $translations_term = get_term( $term_id, $this->tax_translations );
113 +
114 + if ( ! $translations_term instanceof WP_Term || empty( $translations_term->description ) ) {
115 + return array();
116 + }
117 +
118 + // Lang slugs as array keys, translation IDs as array values.
119 + $translations = maybe_unserialize( $translations_term->description );
120 + $translations = is_array( $translations ) ? $translations : array();
121 +
122 + return $this->validate_translations( $translations, 0, 'display' );
86 123 }
87 124
88 125 /**
89 - * Saves translations for posts or terms
126 + * Saves the object's translations.
90 127 *
91 128 * @since 0.5
92 129 *
93 - * @param int $id Post id or term id
94 - * @param array $translations An associative array of translations with language code as key and translation id as value
130 + * @param int $id Object ID.
131 + * @param int[] $translations An associative array of translations with language code as key and translation ID as value.
132 + * @return int[] An associative array with language codes as key and object IDs as values.
133 + *
134 + * @phpstan-return array<non-empty-string, positive-int>
95 135 */
96 - public function save_translations( $id, $translations ) {
97 - $id = (int) $id;
136 + public function save_translations( $id, array $translations = array() ) {
137 + $id = $this->sanitize_int_id( $id );
98 138
99 - if ( ( $lang = $this->get_language( $id ) ) && isset( $translations ) && is_array( $translations ) ) {
100 - // sanitize the translations array
101 - $translations = array_map( 'intval', $translations );
102 - $translations = array_merge( array( $lang->slug => $id ), $translations ); // make sure this object is in translations
103 - $translations = array_diff( $translations, array( 0 ) ); // don't keep non translated languages
104 - $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) ); // keep only valid languages slugs as keys
139 + if ( empty( $id ) ) {
140 + return array();
141 + }
105 142
106 - // unlink removed translations
107 - $old_translations = $this->get_translations( $id );
108 - foreach ( array_diff_assoc( $old_translations, $translations ) as $object_id ) {
109 - $this->delete_translation( $object_id );
110 - }
143 + $lang = $this->get_language( $id );
111 144
112 - // Check id we need to create or update the translation group
113 - if ( $this->should_update_translation_group( $id, $translations ) ) {
114 - $terms = wp_get_object_terms( $translations, $this->tax_translations );
115 - $term = reset( $terms );
145 + if ( empty( $lang ) ) {
146 + return array();
147 + }
116 148
117 - // create a new term if necessary
118 - if ( empty( $term ) ) {
119 - wp_insert_term( $group = uniqid( 'pll_' ), $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
120 - }
121 - else {
122 - // take care not to overwrite extra data stored in description field, if any
123 - $d = maybe_unserialize( $term->description );
124 - $d = is_array( $d ) ? array_diff_key( $d, $old_translations ) : array(); // remove old translations
125 - $d = array_merge( $d, $translations ); // add new one
126 - wp_update_term( $group = (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
127 - }
149 + // Sanitize and validate the translations array.
150 + $translations = $this->validate_translations( $translations, $id );
128 151
129 - // link all translations to the new term
130 - foreach ( $translations as $p ) {
131 - wp_set_object_terms( $p, $group, $this->tax_translations );
132 - }
152 + // Unlink removed translations.
153 + $old_translations = $this->get_translations( $id );
133 154
134 - // clean now unused translation groups
135 - foreach ( wp_list_pluck( $terms, 'term_id' ) as $term_id ) {
136 - $term = get_term( $term_id, $this->tax_translations );
137 - if ( empty( $term->count ) ) {
138 - wp_delete_term( $term_id, $this->tax_translations );
139 - }
140 - }
155 + foreach ( array_diff_assoc( $old_translations, $translations ) as $id ) {
156 + $this->delete_translation( $id );
157 + }
158 +
159 + // Check ID we need to create or update the translation group.
160 + if ( ! $this->should_update_translation_group( $id, $translations ) ) {
161 + return $translations;
162 + }
163 +
164 + $terms = wp_get_object_terms( $translations, $this->tax_translations );
165 + $term = is_array( $terms ) && ! empty( $terms ) ? reset( $terms ) : false;
166 +
167 + if ( empty( $term ) ) {
168 + // Create a new term if necessary.
169 + $group = uniqid( 'pll_' );
170 + wp_insert_term( $group, $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
171 + } else {
172 + // Take care not to overwrite extra data stored in the description field, if any.
173 + $group = (int) $term->term_id;
174 + $descr = maybe_unserialize( $term->description );
175 + $descr = is_array( $descr ) ? array_diff_key( $descr, $old_translations ) : array(); // Remove old translations.
176 + $descr = array_merge( $descr, $translations ); // Add new one.
177 + wp_update_term( $group, $this->tax_translations, array( 'description' => maybe_serialize( $descr ) ) );
178 + }
179 +
180 + // Link all translations to the new term.
181 + foreach ( $translations as $p ) {
182 + wp_set_object_terms( $p, $group, $this->tax_translations );
183 + }
184 +
185 + if ( ! is_array( $terms ) ) {
186 + return $translations;
187 + }
188 +
189 + // Clean now unused translation groups.
190 + foreach ( $terms as $term ) {
191 + // Get fresh count value.
192 + $term = get_term( $term->term_id, $this->tax_translations );
193 +
194 + if ( $term instanceof WP_Term && empty( $term->count ) ) {
195 + wp_delete_term( $term->term_id, $this->tax_translations );
141 196 }
142 197 }
198 +
199 + return $translations;
143 200 }
144 201
145 202 /**
146 - * Deletes a translation of a post or term
203 + * Deletes a translation of an object.
147 204 *
148 205 * @since 0.5
149 206 *
150 - * @param int $id post id or term id
207 + * @param int $id Object ID.
208 + * @return void
151 209 */
152 210 public function delete_translation( $id ) {
153 - $id = (int) $id;
211 + $id = $this->sanitize_int_id( $id );
212 +
213 + if ( empty( $id ) ) {
214 + return;
215 + }
216 +
154 217 $term = $this->get_object_term( $id, $this->tax_translations );
155 218
156 - if ( ! empty( $term ) ) {
157 - $d = maybe_unserialize( $term->description );
158 - $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
159 - unset( $d[ $slug ] );
219 + if ( empty( $term ) ) {
220 + return;
221 + }
160 222
161 - if ( empty( $d ) ) {
162 - wp_delete_term( (int) $term->term_id, $this->tax_translations );
223 + $descr = maybe_unserialize( $term->description );
224 +
225 + if ( ! empty( $descr ) && is_array( $descr ) ) {
226 + $slug = array_search( $id, $this->get_translations( $id ) ); // In case some plugin stores the same value with different key.
227 +
228 + if ( false !== $slug ) {
229 + unset( $descr[ $slug ] );
163 230 }
164 - else {
165 - wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
166 - }
167 231 }
232 +
233 + if ( empty( $descr ) || ! is_array( $descr ) ) {
234 + wp_delete_term( (int) $term->term_id, $this->tax_translations );
235 + } else {
236 + wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $descr ) ) );
237 + }
168 238 }
169 239
170 240 /**
171 - * Returns an array of translations of a post or term
241 + * Returns an array of valid translations of an object.
172 242 *
173 243 * @since 0.5
174 244 *
175 - * @param int $id post id or term id
176 - * @return array an associative array of translations with language code as key and translation id as value
245 + * @param int $id Object ID.
246 + * @return int[] An associative array of translations with language code as key and translation ID as value.
247 + *
248 + * @phpstan-return array<non-empty-string, positive-int>
177 249 */
178 250 public function get_translations( $id ) {
251 + $id = $this->sanitize_int_id( $id );
252 +
253 + if ( empty( $id ) ) {
254 + return array();
255 + }
256 +
257 + $translations = $this->get_raw_translations( $id );
258 +
259 + return $this->validate_translations( $translations, $id, 'display' );
260 + }
261 +
262 + /**
263 + * Returns an unvalidated array of translations of an object.
264 + * It is generally preferable to use `get_translations()`.
265 + *
266 + * @since 3.4
267 + *
268 + * @param int $id Object ID.
269 + * @return int[] An associative array of translations with language code as key and translation ID as value.
270 + *
271 + * @phpstan-return array<non-empty-string, positive-int>
272 + */
273 + public function get_raw_translations( $id ) {
274 + $id = $this->sanitize_int_id( $id );
275 +
276 + if ( empty( $id ) ) {
277 + return array();
278 + }
279 +
179 280 $term = $this->get_object_term( $id, $this->tax_translations );
180 - $translations = empty( $term ) ? array() : maybe_unserialize( $term->description );
181 281
182 - // make sure we return only translations ( thus we allow plugins to store other information in the array )
183 - if ( is_array( $translations ) ) {
184 - $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) );
282 + if ( empty( $term->description ) ) {
283 + return array();
185 284 }
186 285
187 - // make sure to return at least the passed post or term in its translation array
188 - if ( empty( $translations ) && $lang = $this->get_language( $id ) ) {
189 - $translations = array( $lang->slug => $id );
190 - }
286 + $translations = maybe_unserialize( $term->description );
287 + $translations = is_array( $translations ) ? $translations : array();
191 288
192 289 return $translations;
193 290 }
194 291
195 292 /**
196 - * Returns the id of the translation of a post or term
293 + * Returns the ID of the translation of an object.
197 294 *
198 295 * @since 0.5
199 296 *
200 - * @param int $id post id or term id
201 - * @param object|string $lang object or slug
202 - * @return bool|int post id or term id of the translation, false if there is none
297 + * @param int $id Object ID.
298 + * @param PLL_Language|string $lang Language (slug or object).
299 + * @return int|false Object ID of the translation, `false` if there is none.
300 + *
301 + * @phpstan-return positive-int|false
203 302 */
204 303 public function get_translation( $id, $lang ) {
205 - if ( ! $lang = $this->model->get_language( $lang ) ) {
304 + $lang = $this->model->get_language( $lang );
305 +
306 + if ( empty( $lang ) ) {
206 307 return false;
207 308 }
208 309
209 310 $translations = $this->get_translations( $id );
@@ -211,93 +312,71 @@
211 312 return isset( $translations[ $lang->slug ] ) ? $translations[ $lang->slug ] : false;
212 313 }
213 314
214 315 /**
215 - * Among the object and its translations, returns the id of the object which is in $lang
316 + * Among the object and its translations, returns the ID of the object which is in `$lang`.
216 317 *
217 318 * @since 0.1
319 + * @since 3.4 Returns 0 instead of false.
218 320 *
219 - * @param int $id post id or term id
220 - * @param int|string|object $lang language ( term_id or slug or object )
221 - * @return bool|int the translation post id or term id if exists, otherwise the post id or term id, false if the post has no language
321 + * @param int $id Object ID.
322 + * @param PLL_Language|string|int $lang Language (object, slug, or term ID).
323 + * @return int The translation object ID if exists, otherwise the passed ID. `0` if the passed object has no language.
324 + *
325 + * @phpstan-return int<0, max>
222 326 */
223 327 public function get( $id, $lang ) {
224 - $id = (int) $id;
225 - $obj_lang = $this->get_language( $id ); // FIXME is this necessary?
226 - if ( ! $lang || ! $obj_lang ) {
227 - return false;
328 + $id = $this->sanitize_int_id( $id );
329 +
330 + if ( empty( $id ) ) {
331 + return 0;
228 332 }
229 333
230 334 $lang = $this->model->get_language( $lang );
231 - return $obj_lang->term_id == $lang->term_id ? $id : $this->get_translation( $id, $lang );
232 - }
233 335
234 - /**
235 - * A where clause to add to sql queries when filtering by language is needed directly in query
236 - *
237 - * @since 1.2
238 - *
239 - * @param object|array|string $lang a PLL_Language object or a comma separated list of language slug or an array of language slugs
240 - * @return string where clause
241 - */
242 - public function where_clause( $lang ) {
243 - $tt_id = $this->tax_tt;
336 + if ( empty( $lang ) ) {
337 + return 0;
338 + }
244 339
245 - // $lang is an object
246 - // generally the case if the query is coming from Polylang
247 - if ( is_object( $lang ) ) {
248 - return ' AND pll_tr.term_taxonomy_id = ' . absint( $lang->$tt_id );
249 - }
340 + $obj_lang = $this->get_language( $id );
250 341
251 - // $lang is a comma separated list of slugs ( or an array of slugs )
252 - // generally the case is the query is coming from outside with 'lang' parameter
253 - $slugs = is_array( $lang ) ? $lang : explode( ',', $lang );
254 - $languages = array();
255 - foreach ( $slugs as $slug ) {
256 - $languages[] = absint( $this->model->get_language( $slug )->$tt_id );
342 + if ( empty( $obj_lang ) ) {
343 + return 0;
257 344 }
258 345
259 - return ' AND pll_tr.term_taxonomy_id IN ( ' . implode( ',', $languages ) . ' )';
346 + return $obj_lang->term_id === $lang->term_id ? $id : (int) $this->get_translation( $id, $lang );
260 347 }
261 348
262 349 /**
263 - * Returns ids of objects in a language similarly to get_objects_in_term for a taxonomy
264 - * faster than get_objects_in_term as it avoids a JOIN
350 + * Checks if a user can synchronize translations.
265 351 *
266 - * @since 1.4
267 - *
268 - * @param object $lang a PLL_Language object
269 - * @return array
270 - */
271 - public function get_objects_in_language( $lang ) {
272 - global $wpdb;
273 - $tt_id = $this->tax_tt;
274 - return $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $lang->$tt_id ) );
275 - }
276 -
277 - /**
278 - * Check if a user can synchronize translations
279 - *
280 352 * @since 2.6
281 353 *
282 - * @param int $id Object id
354 + * @param int $id Object ID.
283 355 * @return bool
284 356 */
285 357 public function current_user_can_synchronize( $id ) {
358 + $id = $this->sanitize_int_id( $id );
359 +
360 + if ( empty( $id ) ) {
361 + return false;
362 + }
363 +
286 364 /**
287 - * Filters whether a synchronization capability check should take place
365 + * Filters whether a synchronization capability check should take place.
288 366 *
289 367 * @since 2.6
290 368 *
291 - * @param $check null to enable the capability check,
292 - * true to always allow the synchronization,
293 - * false to always disallow the synchronization.
294 - * Defaults to true.
295 - * @param $id The synchronization source object id
369 + * @param bool|null $check Null to enable the capability check,
370 + * true to always allow the synchronization,
371 + * false to always disallow the synchronization.
372 + * Defaults to true.
373 + * @param int $id The synchronization source object ID.
296 374 */
297 375 $check = apply_filters( "pll_pre_current_user_can_synchronize_{$this->type}", true, $id );
376 +
298 377 if ( null !== $check ) {
299 - return $check;
378 + return (bool) $check;
300 379 }
301 380
302 381 if ( ! current_user_can( "edit_{$this->type}", $id ) ) {
303 382 return false;
@@ -309,6 +388,180 @@
309 388 }
310 389 }
311 390
312 391 return true;
392 + }
393 +
394 + /**
395 + * Tells whether a translation term must be updated.
396 + *
397 + * @since 2.3
398 + *
399 + * @param int $id Object ID.
400 + * @param int[] $translations An associative array of translations with language code as key and translation ID as
401 + * value. Make sure to sanitize this.
402 + * @return bool
403 + *
404 + * @phpstan-param array<non-empty-string, positive-int> $translations
405 + */
406 + protected function should_update_translation_group( $id, $translations ) {
407 + // Don't do anything if no translations have been added to the group.
408 + $old_translations = $this->get_translations( $id ); // Includes at least $id itself.
409 + return ! empty( array_diff_assoc( $translations, $old_translations ) );
410 + }
411 +
412 + /**
413 + * Validates and sanitizes translations.
414 + * This will:
415 + * - Make sure to return only translations in existing languages (and only translations).
416 + * - Sanitize the values.
417 + * - Make sure the provided translation (`$id`) is in the list.
418 + * - Check that the translated objects are in the right language, if `$context` is set to 'save'.
419 + *
420 + * @since 3.1
421 + * @since 3.2 Doesn't return `0` ID values.
422 + * @since 3.2 Added parameters `$id` and `$context`.
423 + *
424 + * @param int[] $translations An associative array of translations with language code as key and translation ID as
425 + * value.
426 + * @param int $id Optional. The object ID for which the translations are validated. When provided, the
427 + * process makes sure it is added to the list. Default 0.
428 + * @param string $context Optional. The operation for which the translations are validated. When set to
429 + * 'save', a check is done to verify that the IDs and langs correspond.
430 + * 'display' should be used otherwise. Default 'save'.
431 + * @return int[]
432 + *
433 + * @phpstan-param non-empty-string $context
434 + * @phpstan-return array<non-empty-string, positive-int>
435 + */
436 + protected function validate_translations( $translations, $id = 0, $context = 'save' ) {
437 + if ( ! is_array( $translations ) ) {
438 + $translations = array();
439 + }
440 +
441 + /**
442 + * Remove translations in non-existing languages, and non-translation data (we allow plugins to store other
443 + * information in the array).
444 + */
445 + $translations = array_intersect_key(
446 + $translations,
447 + array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) )
448 + );
449 +
450 + // Make sure values are clean before working with them.
451 + /** @phpstan-var array<non-empty-string, positive-int> $translations */
452 + $translations = $this->sanitize_int_ids_list( $translations );
453 +
454 + if ( 'save' === $context ) {
455 + /**
456 + * Check that the translated objects are in the right language.
457 + * For better performance, this should be done only when saving the data into the database, not when
458 + * retrieving data from it.
459 + */
460 + $valid_translations = array();
461 +
462 + foreach ( $translations as $lang_slug => $tr_id ) {
463 + $tr_lang = $this->get_language( $tr_id );
464 +
465 + if ( ! empty( $tr_lang ) && $tr_lang->slug === $lang_slug ) {
466 + $valid_translations[ $lang_slug ] = $tr_id;
467 + }
468 + }
469 +
470 + $translations = $valid_translations;
471 + }
472 +
473 + $id = $this->sanitize_int_id( $id );
474 +
475 + if ( empty( $id ) ) {
476 + return $translations;
477 + }
478 +
479 + // Make sure to return at least the passed object in its translation array.
480 + $lang = $this->get_language( $id );
481 +
482 + if ( empty( $lang ) ) {
483 + return $translations;
484 + }
485 +
486 + /** @phpstan-var array<non-empty-string, positive-int> $translations */
487 + return array_merge( array( $lang->slug => $id ), $translations );
488 + }
489 + /**
490 + * Creates translations groups in mass.
491 + *
492 + * @since 1.6.3
493 + * @since 3.4 Moved from PLL_Admin_Model class.
494 + *
495 + * @param int[][] $translations Array of translations arrays.
496 + * @return void
497 + *
498 + * @phpstan-param array<array<string,int>> $translations
499 + */
500 + public function set_translation_in_mass( $translations ) {
501 + global $wpdb;
502 +
503 + $terms = array();
504 + $slugs = array();
505 + $description = array();
506 + $count = array();
507 +
508 + foreach ( $translations as $t ) {
509 + $term = uniqid( 'pll_' ); // the term name
510 + $terms[] = $wpdb->prepare( '( %s, %s )', $term, $term );
511 + $slugs[] = $wpdb->prepare( '%s', $term );
512 + $description[ $term ] = maybe_serialize( $t );
513 + $count[ $term ] = count( $t );
514 + }
515 +
516 + // Insert terms
517 + if ( ! empty( $terms ) ) {
518 + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
519 + $wpdb->query( "INSERT INTO {$wpdb->terms} ( slug, name ) VALUES " . implode( ',', array_unique( $terms ) ) );
520 + }
521 +
522 + // Get all terms with their term_id
523 + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
524 + $terms = $wpdb->get_results( "SELECT term_id, slug FROM {$wpdb->terms} WHERE slug IN ( " . implode( ',', $slugs ) . ' )' );
525 + $term_ids = array();
526 + $tts = array();
527 +
528 + // Prepare terms taxonomy relationship
529 + foreach ( $terms as $term ) {
530 + $term_ids[] = $term->term_id;
531 + $tts[] = $wpdb->prepare( '( %d, %s, %s, %d )', $term->term_id, $this->tax_translations, $description[ $term->slug ], $count[ $term->slug ] );
532 + }
533 +
534 + // Insert term_taxonomy
535 + if ( ! empty( $tts ) ) {
536 + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
537 + $wpdb->query( "INSERT INTO {$wpdb->term_taxonomy} ( term_id, taxonomy, description, count ) VALUES " . implode( ',', array_unique( $tts ) ) );
538 + }
539 +
540 + // Get all terms with term_taxonomy_id
541 + $terms = get_terms( array( 'taxonomy' => $this->tax_translations, 'hide_empty' => false ) );
542 + $trs = array();
543 +
544 + // Prepare objects relationships.
545 + if ( is_array( $terms ) ) {
546 + foreach ( $terms as $term ) {
547 + $t = maybe_unserialize( $term->description );
548 + if ( is_array( $t ) && in_array( $t, $translations ) ) {
549 + foreach ( $t as $object_id ) {
550 + if ( ! empty( $object_id ) ) {
551 + $trs[] = $wpdb->prepare( '( %d, %d )', $object_id, $term->term_taxonomy_id );
552 + }
553 + }
554 + }
555 + }
556 + }
557 +
558 + // Insert term_relationships
559 + if ( ! empty( $trs ) ) {
560 + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
561 + $wpdb->query( "INSERT INTO {$wpdb->term_relationships} ( object_id, term_taxonomy_id ) VALUES " . implode( ',', $trs ) );
562 + $trs = array_unique( $trs );
563 + }
564 +
565 + clean_term_cache( $term_ids, $this->tax_translations );
313 566 }
314 567 }