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
polylang / include / translated-term.php

translated-term.php in Polylang 3.7.1, at include/translated-term.php

487 lines 14.9 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-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 }
248 }
249
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;
254 }
255
256 /**
257 * When the term cache is cleaned, cleans the object term cache too.
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 *
273 * @since 2.3
274 *
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
281 */
282 protected function should_update_translation_group( $id, $translations ) {
283 // Don't do anything if no translations have been added to the group.
284 $old_translations = $this->get_translations( $id );
285 if ( count( $translations ) > 1 && ! empty( array_diff_assoc( $translations, $old_translations ) ) ) {
286 return true;
287 }
288
289 // But we need a translation group for terms to allow relationships remap when importing from a WXR file
290 $term = $this->get_object_term( $id, $this->tax_translations );
291 return empty( $term ) || ! empty( array_diff_assoc( $translations, $old_translations ) );
292 }
293
294 /**
295 * Assigns a language to terms in mass.
296 *
297 * @since 1.2
298 * @since 3.4 Moved from PLL_Admin_Model class.
299 *
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
303 */
304 public function set_language_in_mass( $ids, $lang ) {
305 parent::set_language_in_mass( $ids, $lang );
306
307 $translations = array();
308
309 foreach ( $ids as $id ) {
310 $translations[] = array( $lang->slug => $id );
311 }
312
313 if ( ! empty( $translations ) ) {
314 $this->set_translation_in_mass( $translations );
315 }
316 }
317
318 /**
319 * Returns the description to use for the "language properties" in the REST API.
320 *
321 * @since 3.7
322 * @see WP_Syntex\Polylang\REST\V2\Languages::get_item_schema()
323 *
324 * @return string
325 */
326 public function get_rest_description(): string {
327 return __( 'Language taxonomy properties for terms.', 'polylang' );
328 }
329
330 /**
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.
333 *
334 * @see PLL_Translatable_Object::join_clause()
335 * @see PLL_Translatable_Object::get_raw_objects_with_no_lang()
336 *
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
346 */
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 }
355
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;
390 }
391
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'] );
396 }
397
398 return $term;
399 }
400
401 /**
402 * Wraps `wp_update_term` with language feature.
403 *
404 * @since 3.7
405 *
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.
420 */
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' ) );
425 }
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;
453 }
454
455 /**
456 * Toggles Polylang term slug filters management.
457 * Must be used before and after any term slug modification or insertion.
458 *
459 * @since 3.7
460 *
461 * @param PLL_Language $language The language to use.
462 * @param int $parent The parent term id to use.
463 * @return void
464 */
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 ] );
485 }
486 }
487