PluginProbe
Polylang / 3.7.1
Polylang v3.7.1
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-term.php +387 -104 2.73.7.1 View file →
@@ -1,203 +1,486 @@
1 1 <?php
2 +/**
3 + * @package Polylang
4 + */
2 5
6 +use WP_Syntex\Polylang\Options\Options;
7 +
8 +defined( 'ABSPATH' ) || exit;
9 +
3 10 /**
4 - * Setups the taxonomies languages and translations model
11 + * Sets the taxonomies languages and translations model up.
5 12 *
6 13 * @since 1.8
14 + *
15 + * @phpstan-import-type DBInfoWithType from PLL_Translatable_Object_With_Types_Interface
7 16 */
8 -class PLL_Translated_Term extends PLL_Translated_Object {
17 +class PLL_Translated_Term extends PLL_Translated_Object implements PLL_Translatable_Object_With_Types_Interface {
18 + use PLL_Translatable_Object_With_Types_Trait;
9 19
10 20 /**
11 - * Constructor
21 + * Taxonomy name for the languages.
12 22 *
23 + * @var string
24 + *
25 + * @phpstan-var non-empty-string
26 + */
27 + protected $tax_language = 'term_language';
28 +
29 + /**
30 + * Object type to use when registering the taxonomy.
31 + *
32 + * @var string
33 + *
34 + * @phpstan-var non-empty-string
35 + */
36 + protected $object_type = 'term';
37 +
38 + /**
39 + * Identifier that must be unique for each type of content.
40 + * Also used when checking capabilities.
41 + *
42 + * @var string
43 + *
44 + * @phpstan-var non-empty-string
45 + */
46 + protected $type = 'term';
47 +
48 + /**
49 + * Identifier for each type of content to used for cache type.
50 + *
51 + * @var string
52 + *
53 + * @phpstan-var non-empty-string
54 + */
55 + protected $cache_type = 'terms';
56 +
57 +
58 + /**
59 + * Taxonomy name for the translation groups.
60 + *
61 + * @var string
62 + *
63 + * @phpstan-var non-empty-string
64 + */
65 + protected $tax_translations = 'term_translations';
66 +
67 + /**
68 + * Constructor.
69 + *
13 70 * @since 1.8
14 71 *
15 - * @param object $model
72 + * @param PLL_Model $model Instance of `PLL_Model`.
16 73 */
17 - public function __construct( &$model ) {
18 - $this->object_type = 'term'; // For taxonomies
19 - $this->type = 'term'; // For capabilities
20 - $this->tax_language = 'term_language';
21 - $this->tax_translations = 'term_translations';
22 - $this->tax_tt = 'tl_term_taxonomy_id';
74 + public function __construct( PLL_Model $model ) {
75 + parent::__construct( $model );
23 76
24 - parent::__construct( $model );
77 + // Keep hooks in constructor for backward compatibility.
78 + $this->init();
79 + }
25 80
26 - // Filters to prime terms cache
81 + /**
82 + * Adds hooks.
83 + *
84 + * @since 3.4
85 + *
86 + * @return static
87 + */
88 + public function init() {
27 89 add_filter( 'get_terms', array( $this, '_prime_terms_cache' ), 10, 2 );
28 - add_filter( 'wp_get_object_terms', array( $this, 'wp_get_object_terms' ), 10, 3 );
29 -
30 90 add_action( 'clean_term_cache', array( $this, 'clean_term_cache' ) );
91 + return parent::init();
31 92 }
32 93
33 94 /**
34 - * Stores the term language in the database
95 + * Stores the term's language into the database.
35 96 *
36 97 * @since 0.6
98 + * @since 3.4 Renamed the parameter $term_id into $id.
37 99 *
38 - * @param int $term_id term id
39 - * @param int|string|object $lang language ( term_id or slug or object )
100 + * @param int $id Term ID.
101 + * @param PLL_Language|string|int $lang Language (object, slug, or term ID).
102 + * @return bool True when successfully assigned. False otherwise (or if the given language is already assigned to
103 + * the object).
40 104 */
41 - public function set_language( $term_id, $lang ) {
42 - $term_id = (int) $term_id;
105 + public function set_language( $id, $lang ) {
106 + if ( ! parent::set_language( $id, $lang ) ) {
107 + return false;
108 + }
43 109
44 - $old_lang = $this->get_language( $term_id );
45 - $old_lang = $old_lang ? $old_lang->tl_term_id : '';
46 - $lang = $lang ? $this->model->get_language( $lang )->tl_term_id : '';
110 + $id = $this->sanitize_int_id( $id );
47 111
48 - if ( $old_lang !== $lang ) {
49 - wp_set_object_terms( $term_id, $lang, 'term_language' );
112 + // Add translation group for correct WXR export.
113 + $translations = $this->get_translations( $id );
50 114
51 - // Add translation group for correct WXR export
52 - $translations = $this->get_translations( $term_id );
53 - if ( $slug = array_search( $term_id, $translations ) ) {
54 - unset( $translations[ $slug ] );
55 - }
115 + if ( ! empty( $translations ) ) {
116 + $translations = array_diff( $translations, array( $id ) );
117 + }
56 118
57 - $this->save_translations( $term_id, $translations );
119 + $this->save_translations( $id, $translations );
120 +
121 + return true;
122 + }
123 +
124 + /**
125 + * Returns the language of a term.
126 + *
127 + * @since 0.1
128 + * @since 3.4 Renamed the parameter $value into $id.
129 + * @since 3.4 Deprecated to retrieve the language by term slug + taxonomy anymore.
130 + *
131 + * @param int $id Term ID.
132 + * @return PLL_Language|false A `PLL_Language` object. `false` if no language is associated to that term or if the
133 + * ID is invalid.
134 + */
135 + public function get_language( $id ) {
136 + if ( func_num_args() > 1 ) {
137 + // Backward compatibility.
138 + _deprecated_argument( __METHOD__ . '()', '3.4' );
139 +
140 + $term = get_term_by( 'slug', $id, func_get_arg( 1 ) ); // @phpstan-ignore-line
141 + $id = $term instanceof WP_Term ? $term->term_id : 0;
58 142 }
143 +
144 + return parent::get_language( $id );
59 145 }
60 146
61 147 /**
62 - * Removes the term language in database
148 + * Deletes a translation of a term.
63 149 *
64 150 * @since 0.5
65 151 *
66 - * @param int $term_id term id
152 + * @param int $id Term ID.
153 + * @return void
67 154 */
68 - public function delete_language( $term_id ) {
69 - wp_delete_object_term_relationships( $term_id, 'term_language' );
155 + public function delete_translation( $id ) {
156 + global $wpdb;
157 +
158 + $id = $this->sanitize_int_id( $id );
159 +
160 + if ( empty( $id ) ) {
161 + return;
162 + }
163 +
164 + $slug = array_search( $id, $this->get_translations( $id ) ); // In case some plugin stores the same value with different key.
165 +
166 + parent::delete_translation( $id );
167 + wp_delete_object_term_relationships( $id, $this->tax_translations );
168 +
169 + if ( doing_action( 'pre_delete_term' ) ) {
170 + return;
171 + }
172 +
173 + if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->terms WHERE term_id = %d;", $id ) ) ) {
174 + return;
175 + }
176 +
177 + // Always keep a group for terms to allow relationships remap when importing from a WXR file.
178 + $group = uniqid( 'pll_' );
179 + $translations = array( $slug => $id );
180 + wp_insert_term( $group, $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
181 + wp_set_object_terms( $id, $group, $this->tax_translations );
70 182 }
71 183
72 184 /**
73 - * Returns the language of a term
185 + * Returns object types (taxonomy names) that need to be translated.
186 + * The taxonomies list is cached for better performance.
187 + * The method waits for 'after_setup_theme' to apply the cache to allow themes adding the filter in functions.php.
74 188 *
75 - * @since 0.1
189 + * @since 3.4
76 190 *
77 - * @param int|string $value term id or term slug
78 - * @param string $taxonomy optional taxonomy needed when the term slug is passed as first parameter
79 - * @return bool|object PLL_Language object, false if no language is associated to that term
191 + * @param bool $filter True if we should return only valid registered object types.
192 + * @return string[] Object type names for which Polylang manages languages.
193 + *
194 + * @phpstan-return array<non-empty-string, non-empty-string>
80 195 */
81 - public function get_language( $value, $taxonomy = '' ) {
82 - if ( is_numeric( $value ) ) {
83 - $term_id = $value;
196 + public function get_translated_object_types( $filter = true ) {
197 + $taxonomies = $this->cache->get( 'taxonomies' );
198 +
199 + if ( false === $taxonomies ) {
200 + $taxonomies = array( 'category' => 'category', 'post_tag' => 'post_tag' );
201 +
202 + if ( ! empty( $this->options['taxonomies'] ) ) {
203 + $taxonomies = array_merge( $taxonomies, array_combine( $this->options['taxonomies'], $this->options['taxonomies'] ) );
204 + }
205 +
206 + /**
207 + * Filters the list of taxonomies available for translation.
208 + * The default are taxonomies which have the parameter ‘public’ set to true.
209 + * The filter must be added soon in the WordPress loading process:
210 + * in a function hooked to ‘plugins_loaded’ or directly in functions.php for themes.
211 + *
212 + * @since 0.8
213 + *
214 + * @param string[] $taxonomies List of taxonomy names (as array keys and values).
215 + * @param bool $is_settings True when displaying the list of custom taxonomies in Polylang settings.
216 + */
217 + $taxonomies = (array) apply_filters( 'pll_get_taxonomies', $taxonomies, false );
218 +
219 + if ( did_action( 'after_setup_theme' ) && ! doing_action( 'switch_blog' ) ) {
220 + $this->cache->set( 'taxonomies', $taxonomies );
221 + }
84 222 }
85 223
86 - // get_term_by still not cached in WP 3.5.1 but internally, the function is always called by term_id
87 - elseif ( is_string( $value ) && $taxonomy ) {
88 - $term_id = get_term_by( 'slug', $value, $taxonomy )->term_id;
224 + /** @var array<non-empty-string, non-empty-string> $taxonomies */
225 + return $filter ? array_intersect( $taxonomies, get_taxonomies() ) : $taxonomies;
226 + }
227 +
228 + /**
229 + * Caches the language and translations when terms are queried by get_terms().
230 + *
231 + * @since 1.2
232 + *
233 + * @param WP_Term[]|int[] $terms Queried terms.
234 + * @param string[] $taxonomies Queried taxonomies.
235 + * @return WP_Term[]|int[] Unmodified $terms.
236 + *
237 + * @phpstan-param array<WP_Term|positive-int> $terms
238 + * @phpstan-param array<non-empty-string> $taxonomies
239 + * @phpstan-return array<WP_Term|positive-int>
240 + */
241 + public function _prime_terms_cache( $terms, $taxonomies ) {
242 + $ids = array();
243 +
244 + if ( is_array( $terms ) && $this->is_translated_object_type( $taxonomies ) ) {
245 + foreach ( $terms as $term ) {
246 + $ids[] = is_object( $term ) ? $term->term_id : (int) $term;
247 + }
89 248 }
90 249
91 - // Get the language and make sure it is a PLL_Language object
92 - return isset( $term_id ) && ( $lang = $this->get_object_term( $term_id, 'term_language' ) ) ? $this->model->get_language( $lang->term_id ) : false;
250 + if ( ! empty( $ids ) ) {
251 + update_object_term_cache( array_unique( $ids ), 'term' ); // Adds language and translation of terms to cache.
252 + }
253 + return $terms;
93 254 }
94 255
95 256 /**
96 - * Tells whether a translation term must updated
257 + * When the term cache is cleaned, cleans the object term cache too.
97 258 *
259 + * @since 2.0
260 + *
261 + * @param int[] $ids An array of term IDs.
262 + * @return void
263 + *
264 + * @phpstan-param array<positive-int> $ids
265 + */
266 + public function clean_term_cache( $ids ) {
267 + clean_object_term_cache( $this->sanitize_int_ids_list( $ids ), 'term' );
268 + }
269 +
270 + /**
271 + * Tells whether a translation term must be updated.
272 + *
98 273 * @since 2.3
99 274 *
100 - * @param array $id Post id or term id
101 - * @param array $translations An associative array of translations with language code as key and translation id as value
275 + * @param int $id Term ID.
276 + * @param int[] $translations An associative array of translations with language code as key and translation ID as
277 + * value. Make sure to sanitize this.
278 + * @return bool
279 + *
280 + * @phpstan-param array<non-empty-string, positive-int> $translations
102 281 */
103 282 protected function should_update_translation_group( $id, $translations ) {
104 - // Don't do anything if no translations have been added to the group
283 + // Don't do anything if no translations have been added to the group.
105 284 $old_translations = $this->get_translations( $id );
106 - if ( count( $translations ) > 1 && count( array_diff_assoc( $translations, $old_translations ) ) > 0 ) {
285 + if ( count( $translations ) > 1 && ! empty( array_diff_assoc( $translations, $old_translations ) ) ) {
107 286 return true;
108 287 }
109 288
110 289 // But we need a translation group for terms to allow relationships remap when importing from a WXR file
111 290 $term = $this->get_object_term( $id, $this->tax_translations );
112 - return empty( $term ) || count( array_diff_assoc( $translations, $old_translations ) );
291 + return empty( $term ) || ! empty( array_diff_assoc( $translations, $old_translations ) );
113 292 }
114 293
115 294 /**
116 - * Deletes a translation
295 + * Assigns a language to terms in mass.
117 296 *
118 - * @since 0.5
297 + * @since 1.2
298 + * @since 3.4 Moved from PLL_Admin_Model class.
119 299 *
120 - * @param int $id term id
300 + * @param int[] $ids Array of post ids or term ids.
301 + * @param PLL_Language $lang Language to assign to the posts or terms.
302 + * @return void
121 303 */
122 - public function delete_translation( $id ) {
123 - global $wpdb;
124 - $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
304 + public function set_language_in_mass( $ids, $lang ) {
305 + parent::set_language_in_mass( $ids, $lang );
125 306
126 - parent::delete_translation( $id );
127 - wp_delete_object_term_relationships( $id, 'term_translations' );
307 + $translations = array();
128 308
129 - if ( ! doing_action( 'pre_delete_term' ) && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->terms WHERE term_id = %d;", $id ) ) ) {
130 - // Always keep a group for terms to allow relationships remap when importing from a WXR file
131 - $translations = array( $slug => $id );
132 - wp_insert_term( $group = uniqid( 'pll_' ), 'term_translations', array( 'description' => maybe_serialize( $translations ) ) );
133 - wp_set_object_terms( $id, $group, 'term_translations' );
309 + foreach ( $ids as $id ) {
310 + $translations[] = array( $lang->slug => $id );
134 311 }
312 +
313 + if ( ! empty( $translations ) ) {
314 + $this->set_translation_in_mass( $translations );
315 + }
135 316 }
136 317
137 318 /**
138 - * A join clause to add to sql queries when filtering by language is needed directly in query
319 + * Returns the description to use for the "language properties" in the REST API.
139 320 *
140 - * @since 1.2
141 - * @since 2.6 The `$alias` parameter was added.
321 + * @since 3.7
322 + * @see WP_Syntex\Polylang\REST\V2\Languages::get_item_schema()
142 323 *
143 - * @param string $alias Alias for $wpdb->terms table
144 - * @return string join clause
324 + * @return string
145 325 */
146 - public function join_clause( $alias = 't' ) {
147 - global $wpdb;
148 - return " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = $alias.term_id";
326 + public function get_rest_description(): string {
327 + return __( 'Language taxonomy properties for terms.', 'polylang' );
149 328 }
150 329
151 330 /**
152 - * Cache language and translations when terms are queried by get_terms
331 + * Returns database-related information that can be used in some of this class methods.
332 + * These are specific to the table containing the objects.
153 333 *
154 - * @since 1.2
334 + * @see PLL_Translatable_Object::join_clause()
335 + * @see PLL_Translatable_Object::get_raw_objects_with_no_lang()
155 336 *
156 - * @param array $terms queried terms
157 - * @param array $taxonomies queried taxonomies
158 - * @return array unmodified $terms
337 + * @since 3.4.3
338 + *
339 + * @return string[] {
340 + * @type string $table Name of the table.
341 + * @type string $id_column Name of the column containing the object's ID.
342 + * @type string $type_column Name of the column containing the object's type.
343 + * @type string $default_alias Default alias corresponding to the object's table.
344 + * }
345 + * @phpstan-return DBInfoWithType
159 346 */
160 - public function _prime_terms_cache( $terms, $taxonomies ) {
161 - $term_ids = array();
347 + protected function get_db_infos() {
348 + return array(
349 + 'table' => $GLOBALS['wpdb']->term_taxonomy,
350 + 'id_column' => 'term_id',
351 + 'type_column' => 'taxonomy',
352 + 'default_alias' => 't',
353 + );
354 + }
162 355
163 - if ( is_array( $terms ) && $this->model->is_translated_taxonomy( $taxonomies ) ) {
164 - foreach ( $terms as $term ) {
165 - $term_ids[] = is_object( $term ) ? $term->term_id : (int) $term;
166 - }
356 + /**
357 + * Wraps `wp_insert_term` with language feature.
358 + *
359 + * @since 3.7
360 + *
361 + * @param string $term The term name to add.
362 + * @param string $taxonomy The taxonomy to which to add the term.
363 + * @param PLL_Language $language The term language.
364 + * @param array $args {
365 + * Optional. Array of arguments for inserting a term.
366 + *
367 + * @type string $alias_of Slug of the term to make this term an alias of.
368 + * Default empty string. Accepts a term slug.
369 + * @type string $description The term description. Default empty string.
370 + * @type int $parent The id of the parent term. Default 0.
371 + * @type string $slug The term slug to use. Default empty string.
372 + * @type string[] $translations The translation group to assign to the term with language slug as keys and `term_id` as values.
373 + * }
374 + * @return array|WP_Error {
375 + * An array of the new term data, `WP_Error` otherwise.
376 + *
377 + * @type int $term_id The new term ID.
378 + * @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
379 + * }
380 + */
381 + public function insert( string $term, string $taxonomy, PLL_Language $language, $args = array() ) {
382 + $parent = $args['parent'] ?? 0;
383 + $this->toggle_inserted_term_filters( $language, $parent );
384 + $term = wp_insert_term( $term, $taxonomy, $args );
385 + $this->toggle_inserted_term_filters( $language, $parent );
386 +
387 + if ( is_wp_error( $term ) ) {
388 + // Something went wrong!
389 + return $term;
167 390 }
168 391
169 - if ( ! empty( $term_ids ) ) {
170 - update_object_term_cache( array_unique( $term_ids ), 'term' ); // Adds language and translation of terms to cache
392 + $this->set_language( (int) $term['term_id'], $language );
393 +
394 + if ( ! empty( $args['translations'] ) ) {
395 + $this->save_translations( (int) $term['term_id'], $args['translations'] );
171 396 }
172 - return $terms;
397 +
398 + return $term;
173 399 }
174 400
175 401 /**
176 - * When terms are found for posts, add their language and translations to cache
402 + * Wraps `wp_update_term` with language feature.
177 403 *
178 - * @since 1.2
404 + * @since 3.7
179 405 *
180 - * @param array $terms terms found
181 - * @param array $object_ids not used
182 - * @param array $taxonomies terms taxonomies
183 - * @return array unmodified $terms
406 + * @param int $term_id The ID of the term.
407 + * @param array $args {
408 + * Optional. Array of arguments for updating a term.
409 + *
410 + * @type string $alias_of Slug of the term to make this term an alias of.
411 + * Default empty string. Accepts a term slug.
412 + * @type string $description The term description. Default empty string.
413 + * @type int $parent The id of the parent term. Default 0.
414 + * @type string $slug The term slug to use. Default empty string.
415 + * @type PLL_Language $lang The term language object.
416 + * @type string[] $translations The translation group to assign to the term with language slug as keys and `term_id` as values.
417 + * }
418 + * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
419 + * WP_Error otherwise.
184 420 */
185 - public function wp_get_object_terms( $terms, $object_ids, $taxonomies ) {
186 - $taxonomies = explode( "', '", trim( $taxonomies, "'" ) );
187 - if ( ! in_array( 'term_translations', $taxonomies ) ) {
188 - $this->_prime_terms_cache( $terms, $taxonomies );
421 + public function update( int $term_id, array $args = array() ) {
422 + $term = get_term( $term_id );
423 + if ( ! $term instanceof WP_Term ) {
424 + return new WP_Error( 'invalid_term', __( 'Empty Term.', 'polylang' ) );
189 425 }
190 - return $terms;
426 +
427 + /** @var PLL_Language $language */
428 + $language = $this->get_language( $term_id );
429 + if ( ! empty( $args['lang'] ) ) {
430 + $language = $this->languages->get( $args['lang'] );
431 + if ( ! $language instanceof PLL_Language ) {
432 + return new WP_Error( 'invalid_language', __( 'Please provide a valid language.', 'polylang' ) );
433 + }
434 +
435 + $this->set_language( $term_id, $language );
436 + }
437 +
438 + $parent = $args['parent'] ?? $term->parent;
439 + $this->toggle_inserted_term_filters( $language, $parent );
440 + $term = wp_update_term( $term->term_id, $term->taxonomy, $args );
441 + $this->toggle_inserted_term_filters( $language, $parent );
442 +
443 + if ( is_wp_error( $term ) ) {
444 + // Something went wrong!
445 + return $term;
446 + }
447 +
448 + if ( ! empty( $args['translations'] ) ) {
449 + $this->save_translations( $term_id, $args['translations'] );
450 + }
451 +
452 + return $term;
191 453 }
192 454
193 455 /**
194 - * When the term cache is cleaned, clean the object term cache too
456 + * Toggles Polylang term slug filters management.
457 + * Must be used before and after any term slug modification or insertion.
195 458 *
196 - * @since 2.0
459 + * @since 3.7
197 460 *
198 - * @param array $ids An array of term IDs.
461 + * @param PLL_Language $language The language to use.
462 + * @param int $parent The parent term id to use.
463 + * @return void
199 464 */
200 - public function clean_term_cache( $ids ) {
201 - clean_object_term_cache( $ids, 'term' );
465 + private function toggle_inserted_term_filters( PLL_Language $language, int $parent ): void {
466 + static $callbacks = array();
467 + if ( isset( $callbacks[ $language->slug ], $callbacks[ (string) $parent ] ) ) {
468 + // Clean up!
469 + remove_filter( 'pll_inserted_term_language', $callbacks[ $language->slug ] );
470 + remove_filter( 'pll_inserted_term_parent', $callbacks[ (string) $parent ] );
471 + unset( $callbacks[ $language->slug ], $callbacks[ (string) $parent ] );
472 + return;
473 + }
474 +
475 + $callbacks[ $language->slug ] = function () use ( $language ) {
476 + return $language;
477 + };
478 + $callbacks[ (string) $parent ] = function () use ( $parent ) {
479 + return $parent;
480 + };
481 +
482 + // Set term parent and language for suffixed slugs.
483 + add_filter( 'pll_inserted_term_language', $callbacks[ $language->slug ] );
484 + add_filter( 'pll_inserted_term_parent', $callbacks[ (string) $parent ] );
202 485 }
203 486 }