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 / modules / sync / sync-tax.php

sync-tax.php in Polylang 3.8.9, at src/modules/sync/sync-tax.php

311 lines 9.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 /**
7 * A class to manage the synchronization of taxonomy terms across posts translations
8 *
9 * @since 2.3
10 */
11 class PLL_Sync_Tax {
12
13 /**
14 * Stores the plugin options.
15 *
16 * @var \WP_Syntex\Polylang\Options\Options
17 */
18 protected $options;
19
20 /**
21 * @var PLL_Model
22 */
23 protected $model;
24
25 /**
26 * Constructor.
27 *
28 * @since 2.3
29 *
30 * @param PLL_Base $polylang The Polylang object.
31 */
32 public function __construct( PLL_Base &$polylang ) {
33 $this->model = &$polylang->model;
34 $this->options = $polylang->options;
35
36 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
37 add_action( 'pll_save_term', array( $this, 'create_term' ), 10, 3 );
38 add_action( 'pre_delete_term', array( $this, 'pre_delete_term' ) );
39 add_action( 'delete_term', array( $this, 'delete_term' ) );
40 }
41
42 /**
43 * Get the list of taxonomies to copy or to synchronize.
44 *
45 * @since 1.7
46 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
47 * @since 3.2 Changed visibility from protected to public.
48 *
49 * @param bool $sync True if it is synchronization, false if it is a copy.
50 * @param int $from Id of the post from which we copy information, optional, defaults to null.
51 * @param int $to Id of the post to which we paste information, optional, defaults to null.
52 * @param string $lang Language slug, optional, defaults to null.
53 * @return string[] List of taxonomy names.
54 */
55 public function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) {
56 $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
57 if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
58 $taxonomies[] = 'post_format';
59 }
60
61 /**
62 * Filters the taxonomies to copy or synchronize.
63 *
64 * @since 1.7
65 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
66 *
67 * @param string[] $taxonomies List of taxonomy names.
68 * @param bool $sync True if it is synchronization, false if it is a copy.
69 * @param int $from Id of the post from which we copy information.
70 * @param int $to Id of the post to which we paste information.
71 * @param string $lang Language slug.
72 */
73 return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync, $from, $to, $lang ) );
74 }
75
76 /**
77 * When copying or synchronizing terms, translate terms in translatable taxonomies
78 *
79 * @since 2.3
80 *
81 * @param int $object_id Object ID.
82 * @param int[] $terms List of terms ids assigned to the source post.
83 * @param string $taxonomy Taxonomy name.
84 * @param string $lang Language slug.
85 * @return int[] List of terms ids to assign to the target post.
86 */
87 protected function maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ) {
88 if ( is_array( $terms ) && $this->model->is_translated_taxonomy( $taxonomy ) ) {
89 $newterms = array();
90
91 // Convert to term ids if we got tag names
92 $strings = array_map( 'is_string', $terms );
93 if ( in_array( true, $strings, true ) ) {
94 $terms = get_the_terms( $object_id, $taxonomy );
95 $terms = wp_list_pluck( $terms, 'term_id' );
96 }
97
98 foreach ( $terms as $term ) {
99 /**
100 * Filter the translated term when a post translation is created or synchronized
101 *
102 * @since 2.3
103 *
104 * @param int $tr_term Translated term id
105 * @param int $term Source term id
106 * @param string $lang Language slug
107 */
108 if ( $term_id = apply_filters( 'pll_maybe_translate_term', (int) $this->model->term->get_translation( $term, $lang ), $term, $lang ) ) {
109 $newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags
110 }
111 }
112
113 return $newterms;
114 }
115
116 return $terms; // Empty $terms or untranslated taxonomy
117 }
118
119 /**
120 * Maybe copy taxonomy terms from one post to the other.
121 *
122 * @since 2.6
123 *
124 * @param int $object_id Source object ID.
125 * @param int $tr_id Target object ID.
126 * @param string $lang Target language.
127 * @param array $terms An array of object terms.
128 * @param string $taxonomy Taxonomy slug.
129 * @param bool $append Whether to append new terms to the old terms.
130 * @return void
131 */
132 protected function copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ) {
133 $to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang );
134
135 if ( in_array( $taxonomy, $to_copy ) ) {
136 $newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang );
137
138 // For some reasons, the user may have untranslated terms in the translation. Don't forget them.
139 if ( $this->model->is_translated_taxonomy( $taxonomy ) ) {
140 $tr_terms = get_the_terms( $tr_id, $taxonomy );
141 if ( is_array( $tr_terms ) ) {
142 foreach ( $tr_terms as $term ) {
143 if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) {
144 $newterms[] = (int) $term->term_id;
145 }
146 }
147 }
148 }
149
150 wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append );
151 }
152 }
153
154 /**
155 * When assigning terms to a post, assign translated terms to the translated posts (synchronisation).
156 *
157 * @since 2.3
158 *
159 * @param int $object_id Object ID.
160 * @param array $terms An array of object terms.
161 * @param int[] $tt_ids An array of term taxonomy IDs.
162 * @param string $taxonomy Taxonomy slug.
163 * @param bool $append Whether to append new terms to the old terms.
164 * @return void
165 */
166 public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) {
167 static $avoid_recursion = false;
168 $taxonomy_object = get_taxonomy( $taxonomy );
169
170 // Make sure that the taxonomy is registered for a post type
171 if ( ! $avoid_recursion && ! empty( $taxonomy_object ) && array_filter( $taxonomy_object->object_type, 'post_type_exists' ) ) {
172 $avoid_recursion = true;
173
174 $tr_ids = $this->model->post->get_translations( $object_id );
175
176 foreach ( $tr_ids as $lang => $tr_id ) {
177 if ( $tr_id !== $object_id ) {
178 if ( $this->model->post->current_user_can_synchronize( $object_id ) ) {
179 $this->copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append );
180 } else {
181 // No permission to synchronize, so let's synchronize in reverse order
182 $orig_lang = array_search( $object_id, $tr_ids );
183 $tr_terms = get_the_terms( $tr_id, $taxonomy );
184
185 if ( false === $tr_terms ) {
186 $tr_terms = array();
187 }
188
189 if ( is_string( $orig_lang ) && is_array( $tr_terms ) ) {
190 $tr_terms = wp_list_pluck( $tr_terms, 'term_id' );
191 $this->copy_object_terms( $tr_id, $object_id, $orig_lang, $tr_terms, $taxonomy, $append );
192 }
193 break;
194 }
195 }
196 }
197
198 $avoid_recursion = false;
199 }
200 }
201
202 /**
203 * Copy terms from one post to a translation, does not sync
204 *
205 * @since 2.3
206 *
207 * @param int $from Id of the source post
208 * @param int $to Id of the target post
209 * @param string $lang Language slug
210 * @return void
211 */
212 public function copy( $from, $to, $lang ) {
213 remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) );
214
215 // Get taxonomies to sync for this post type
216 $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) );
217
218 // Update the term cache to reduce the number of queries in the loop
219 update_object_term_cache( array( $from ), get_post_type( $from ) );
220
221 // Copy
222 foreach ( $taxonomies as $tax ) {
223 if ( $terms = get_the_terms( $from, $tax ) ) {
224 $terms = array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) );
225 $newterms = $this->maybe_translate_terms( $from, $terms, $tax, $lang );
226
227 if ( ! empty( $newterms ) ) {
228 wp_set_object_terms( $to, $newterms, $tax );
229 }
230 }
231 }
232
233 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
234 }
235
236 /**
237 * When creating a new term, associate it to posts having translations associated to the translated terms.
238 *
239 * @since 2.3
240 *
241 * @param int $term_id Id of the created term.
242 * @param string $taxonomy Taxonomy.
243 * @param int[] $translations Ids of the translations of the created term.
244 * @return void
245 */
246 public function create_term( $term_id, $taxonomy, $translations ) {
247 if ( doing_action( 'create_term' ) && in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) {
248 // Get all posts associated to the translated terms
249 $tr_posts = get_posts(
250 array(
251 'numberposts' => -1,
252 'nopaging' => true,
253 'post_type' => 'any',
254 'post_status' => 'any',
255 'fields' => 'ids',
256 'tax_query' => array(
257 array(
258 'taxonomy' => $taxonomy,
259 'field' => 'id',
260 'terms' => array_merge( array( $term_id ), array_values( $translations ) ),
261 'include_children' => false,
262 ),
263 ),
264 )
265 );
266
267 $lang = $this->model->term->get_language( $term_id ); // Language of the created term
268 $posts = array();
269
270 foreach ( $tr_posts as $post_id ) {
271 $post = $this->model->post->get_translation( $post_id, $lang );
272
273 if ( $post ) {
274 $posts[] = $post;
275 }
276 }
277
278 $posts = array_unique( $posts );
279
280 foreach ( $posts as $post_id ) {
281 if ( current_user_can( 'assign_term', $term_id ) ) {
282 wp_set_object_terms( $post_id, $term_id, $taxonomy, true );
283 }
284 }
285 }
286 }
287
288 /**
289 * Deactivate the synchronization of terms before deleting a term
290 * to avoid translated terms to be removed from translated posts
291 *
292 * @since 2.3.2
293 *
294 * @return void
295 */
296 public function pre_delete_term() {
297 remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) );
298 }
299
300 /**
301 * Re-activate the synchronization of terms after a term is deleted
302 *
303 * @since 2.3.2
304 *
305 * @return void
306 */
307 public function delete_term() {
308 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
309 }
310 }
311