PluginProbe
Polylang / 3.8.9
Polylang v3.8.9
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
polylang / src / translated-term.php

translated-term.php in Polylang 3.8.9, at src/translated-term.php

486 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 use WP_Syntex\Polylang\Options\Options;
7
8 defined( 'ABSPATH' ) || exit;
9
10 /**
11 * Sets the taxonomies languages and translations model up.
12 *
13 * @since 1.8
14 *
15 * @phpstan-import-type DBInfoWithType from PLL_Translatable_Object_With_Types_Interface
16 */
17 class PLL_Translated_Term extends PLL_Translated_Object implements PLL_Translatable_Object_With_Types_Interface {
18 use PLL_Translatable_Object_With_Types_Trait;
19
20 /**
21 * Taxonomy name for the languages.
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 *
70 * @since 1.8
71 *
72 * @param PLL_Model $model Instance of `PLL_Model`.
73 */
74 public function __construct( PLL_Model $model ) {
75 parent::__construct( $model );
76
77 // Keep hooks in constructor for backward compatibility.
78 $this->init();
79 }
80
81 /**
82 * Adds hooks.
83 *
84 * @since 3.4
85 *
86 * @return static
87 */
88 public function init() {
89 add_filter( 'get_terms', array( $this, '_prime_terms_cache' ), 10, 2 );
90 add_action( 'clean_term_cache', array( $this, 'clean_term_cache' ) );
91 return parent::init();
92 }
93
94 /**
95 * Stores the term's language into the database.
96 *
97 * @since 0.6
98 * @since 3.4 Renamed the parameter $term_id into $id.
99 *
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).
104 */
105 public function set_language( $id, $lang ) {
106 if ( ! parent::set_language( $id, $lang ) ) {
107 return false;
108 }
109
110 $id = $this->sanitize_int_id( $id );
111
112 // Add translation group for correct WXR export.
113 $translations = $this->get_translations( $id );
114
115 if ( ! empty( $translations ) ) {
116 $translations = array_diff( $translations, array( $id ) );
117 }
118
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;
142 }
143
144 return parent::get_language( $id );
145 }
146
147 /**
148 * Deletes a translation of a term.
149 *
150 * @since 0.5
151 *
152 * @param int $id Term ID.
153 * @return void
154 */
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 );
182 }
183
184 /**
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.
188 *
189 * @since 3.4
190 *
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>
195 */
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 }
222 }
223
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-return array<WP_Term|positive-int>
239 */
240 public function _prime_terms_cache( $terms, $taxonomies ) {
241 $ids = array();
242
243 if ( is_array( $terms ) && $this->is_translated_object_type( $taxonomies ) ) {
244 foreach ( $terms as $term ) {
245 $ids[] = is_object( $term ) ? $term->term_id : (int) $term;
246 }
247 }
248
249 if ( ! empty( $ids ) ) {
250 update_object_term_cache( array_unique( $ids ), 'term' ); // Adds language and translation of terms to cache.
251 }
252 return $terms;
253 }
254
255 /**
256 * When the term cache is cleaned, cleans the object term cache too.
257 *
258 * @since 2.0
259 *
260 * @param int[] $ids An array of term IDs.
261 * @return void
262 *
263 * @phpstan-param array<positive-int> $ids
264 */
265 public function clean_term_cache( $ids ) {
266 clean_object_term_cache( $this->sanitize_int_ids_list( $ids ), 'term' );
267 }
268
269 /**
270 * Tells whether a translation term must be updated.
271 *
272 * @since 2.3
273 *
274 * @param int $id Term ID.
275 * @param int[] $translations An associative array of translations with language code as key and translation ID as
276 * value. Make sure to sanitize this.
277 * @return bool
278 *
279 * @phpstan-param array<non-empty-string, positive-int> $translations
280 */
281 protected function should_update_translation_group( $id, $translations ) {
282 // Don't do anything if no translations have been added to the group.
283 $old_translations = $this->get_translations( $id );
284 if ( count( $translations ) > 1 && ! empty( array_diff_assoc( $translations, $old_translations ) ) ) {
285 return true;
286 }
287
288 // But we need a translation group for terms to allow relationships remap when importing from a WXR file
289 $term = $this->get_object_term( $id, $this->tax_translations );
290 return empty( $term ) || ! empty( array_diff_assoc( $translations, $old_translations ) );
291 }
292
293 /**
294 * Assigns a language to terms in mass.
295 *
296 * @since 1.2
297 * @since 3.4 Moved from PLL_Admin_Model class.
298 *
299 * @param int[] $ids Array of post ids or term ids.
300 * @param PLL_Language $lang Language to assign to the posts or terms.
301 * @return void
302 */
303 public function set_language_in_mass( $ids, $lang ) {
304 parent::set_language_in_mass( $ids, $lang );
305
306 $translations = array();
307
308 foreach ( $ids as $id ) {
309 $translations[] = array( $lang->slug => $id );
310 }
311
312 if ( ! empty( $translations ) ) {
313 $this->set_translation_in_mass( $translations );
314 }
315 }
316
317 /**
318 * Returns the description to use for the "language properties" in the REST API.
319 *
320 * @since 3.7
321 * @see WP_Syntex\Polylang\REST\V2\Languages::get_item_schema()
322 *
323 * @return string
324 */
325 public function get_rest_description(): string {
326 return __( 'Language taxonomy properties for terms.', 'polylang' );
327 }
328
329 /**
330 * Returns database-related information that can be used in some of this class methods.
331 * These are specific to the table containing the objects.
332 *
333 * @see PLL_Translatable_Object::join_clause()
334 * @see PLL_Translatable_Object::get_raw_objects_with_no_lang()
335 *
336 * @since 3.4.3
337 *
338 * @return string[] {
339 * @type string $table Name of the table.
340 * @type string $id_column Name of the column containing the object's ID.
341 * @type string $type_column Name of the column containing the object's type.
342 * @type string $default_alias Default alias corresponding to the object's table.
343 * }
344 * @phpstan-return DBInfoWithType
345 */
346 protected function get_db_infos() {
347 return array(
348 'table' => $GLOBALS['wpdb']->term_taxonomy,
349 'id_column' => 'term_id',
350 'type_column' => 'taxonomy',
351 'default_alias' => 't',
352 );
353 }
354
355 /**
356 * Wraps `wp_insert_term` with language feature.
357 *
358 * @since 3.7
359 *
360 * @param string $term The term name to add.
361 * @param string $taxonomy The taxonomy to which to add the term.
362 * @param PLL_Language $language The term language.
363 * @param array $args {
364 * Optional. Array of arguments for inserting a term.
365 *
366 * @type string $alias_of Slug of the term to make this term an alias of.
367 * Default empty string. Accepts a term slug.
368 * @type string $description The term description. Default empty string.
369 * @type int $parent The id of the parent term. Default 0.
370 * @type string $slug The term slug to use. Default empty string.
371 * @type string[] $translations The translation group to assign to the term with language slug as keys and `term_id` as values.
372 * }
373 * @return array|WP_Error {
374 * An array of the new term data, `WP_Error` otherwise.
375 *
376 * @type int $term_id The new term ID.
377 * @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
378 * }
379 */
380 public function insert( string $term, string $taxonomy, PLL_Language $language, $args = array() ) {
381 $parent = $args['parent'] ?? 0;
382 $this->toggle_inserted_term_filters( $language, $parent );
383 $term = wp_insert_term( $term, $taxonomy, $args );
384 $this->toggle_inserted_term_filters( $language, $parent );
385
386 if ( is_wp_error( $term ) ) {
387 // Something went wrong!
388 return $term;
389 }
390
391 $this->set_language( (int) $term['term_id'], $language );
392
393 if ( ! empty( $args['translations'] ) ) {
394 $this->save_translations( (int) $term['term_id'], $args['translations'] );
395 }
396
397 return $term;
398 }
399
400 /**
401 * Wraps `wp_update_term` with language feature.
402 *
403 * @since 3.7
404 *
405 * @param int $term_id The ID of the term.
406 * @param array $args {
407 * Optional. Array of arguments for updating a term.
408 *
409 * @type string $alias_of Slug of the term to make this term an alias of.
410 * Default empty string. Accepts a term slug.
411 * @type string $description The term description. Default empty string.
412 * @type int $parent The id of the parent term. Default 0.
413 * @type string $slug The term slug to use. Default empty string.
414 * @type PLL_Language $lang The term language object.
415 * @type string[] $translations The translation group to assign to the term with language slug as keys and `term_id` as values.
416 * }
417 * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
418 * WP_Error otherwise.
419 */
420 public function update( int $term_id, array $args = array() ) {
421 $term = get_term( $term_id );
422 if ( ! $term instanceof WP_Term ) {
423 return new WP_Error( 'invalid_term', __( 'Empty Term.', 'polylang' ) );
424 }
425
426 /** @var PLL_Language $language */
427 $language = $this->get_language( $term_id );
428 if ( ! empty( $args['lang'] ) ) {
429 $language = $this->languages->get( $args['lang'] );
430 if ( ! $language instanceof PLL_Language ) {
431 return new WP_Error( 'invalid_language', __( 'Please provide a valid language.', 'polylang' ) );
432 }
433
434 $this->set_language( $term_id, $language );
435 }
436
437 $parent = $args['parent'] ?? $term->parent;
438 $this->toggle_inserted_term_filters( $language, $parent );
439 $term = wp_update_term( $term->term_id, $term->taxonomy, $args );
440 $this->toggle_inserted_term_filters( $language, $parent );
441
442 if ( is_wp_error( $term ) ) {
443 // Something went wrong!
444 return $term;
445 }
446
447 if ( ! empty( $args['translations'] ) ) {
448 $this->save_translations( $term_id, $args['translations'] );
449 }
450
451 return $term;
452 }
453
454 /**
455 * Toggles Polylang term slug filters management.
456 * Must be used before and after any term slug modification or insertion.
457 *
458 * @since 3.7
459 *
460 * @param PLL_Language $language The language to use.
461 * @param int $parent The parent term id to use.
462 * @return void
463 */
464 private function toggle_inserted_term_filters( PLL_Language $language, int $parent ): void {
465 static $callbacks = array();
466 if ( isset( $callbacks[ $language->slug ], $callbacks[ (string) $parent ] ) ) {
467 // Clean up!
468 remove_filter( 'pll_inserted_term_language', $callbacks[ $language->slug ] );
469 remove_filter( 'pll_inserted_term_parent', $callbacks[ (string) $parent ] );
470 unset( $callbacks[ $language->slug ], $callbacks[ (string) $parent ] );
471 return;
472 }
473
474 $callbacks[ $language->slug ] = function () use ( $language ) {
475 return $language;
476 };
477 $callbacks[ (string) $parent ] = function () use ( $parent ) {
478 return $parent;
479 };
480
481 // Set term parent and language for suffixed slugs.
482 add_filter( 'pll_inserted_term_language', $callbacks[ $language->slug ] );
483 add_filter( 'pll_inserted_term_parent', $callbacks[ (string) $parent ] );
484 }
485 }
486