PluginProbe
Polylang / 3.2.3
Polylang v3.2.3
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 / modules / sync / sync-tax.php

sync-tax.php in Polylang 3.2.3, at modules/sync/sync-tax.php

312 lines 9.7 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 sychronization 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 array
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 object $polylang
31 */
32 public function __construct( &$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 informations, optional, defaults to null.
51 * @param int $to Id of the post to which we paste informations, 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 informations.
70 * @param int $to Id of the post to which we paste informations.
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', $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 /**
156 * When assigning terms to a post, assign translated terms to the translated posts (synchronisation).
157 *
158 * @since 2.3
159 *
160 * @param int $object_id Object ID.
161 * @param array $terms An array of object terms.
162 * @param int[] $tt_ids An array of term taxonomy IDs.
163 * @param string $taxonomy Taxonomy slug.
164 * @param bool $append Whether to append new terms to the old terms.
165 * @return void
166 */
167 public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) {
168 static $avoid_recursion = false;
169 $taxonomy_object = get_taxonomy( $taxonomy );
170
171 // Make sure that the taxonomy is registered for a post type
172 if ( ! $avoid_recursion && ! empty( $taxonomy_object ) && array_filter( $taxonomy_object->object_type, 'post_type_exists' ) ) {
173 $avoid_recursion = true;
174
175 $tr_ids = $this->model->post->get_translations( $object_id );
176
177 foreach ( $tr_ids as $lang => $tr_id ) {
178 if ( $tr_id !== $object_id ) {
179 if ( $this->model->post->current_user_can_synchronize( $object_id ) ) {
180 $this->copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append );
181 } else {
182 // No permission to synchronize, so let's synchronize in reverse order
183 $orig_lang = array_search( $object_id, $tr_ids );
184 $tr_terms = get_the_terms( $tr_id, $taxonomy );
185
186 if ( false === $tr_terms ) {
187 $tr_terms = array();
188 }
189
190 if ( is_array( $tr_terms ) ) {
191 $tr_terms = wp_list_pluck( $tr_terms, 'term_id' );
192 $this->copy_object_terms( $tr_id, $object_id, $orig_lang, $tr_terms, $taxonomy, $append );
193 }
194 break;
195 }
196 }
197 }
198
199 $avoid_recursion = false;
200 }
201 }
202
203 /**
204 * Copy terms fron one post to a translation, does not sync
205 *
206 * @since 2.3
207 *
208 * @param int $from Id of the source post
209 * @param int $to Id of the target post
210 * @param string $lang Language slug
211 * @return void
212 */
213 public function copy( $from, $to, $lang ) {
214 remove_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 6 );
215
216 // Get taxonomies to sync for this post type
217 $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) );
218
219 // Update the term cache to reduce the number of queries in the loop
220 update_object_term_cache( array( $from ), get_post_type( $from ) );
221
222 // Copy
223 foreach ( $taxonomies as $tax ) {
224 if ( $terms = get_the_terms( $from, $tax ) ) {
225 $terms = array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) );
226 $newterms = $this->maybe_translate_terms( $from, $terms, $tax, $lang );
227
228 if ( ! empty( $newterms ) ) {
229 wp_set_object_terms( $to, $newterms, $tax );
230 }
231 }
232 }
233
234 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 6 );
235 }
236
237 /**
238 * When creating a new term, associate it to posts having translations associated to the translated terms.
239 *
240 * @since 2.3
241 *
242 * @param int $term_id Id of the created term.
243 * @param string $taxonomy Taxonomy.
244 * @param int[] $translations Ids of the translations of the created term.
245 * @return void
246 */
247 public function create_term( $term_id, $taxonomy, $translations ) {
248 if ( doing_action( 'create_term' ) && in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) {
249 // Get all posts associated to the translated terms
250 $tr_posts = get_posts(
251 array(
252 'numberposts' => -1,
253 'nopaging' => true,
254 'post_type' => 'any',
255 'post_status' => 'any',
256 'fields' => 'ids',
257 'tax_query' => array(
258 array(
259 'taxonomy' => $taxonomy,
260 'field' => 'id',
261 'terms' => array_merge( array( $term_id ), array_values( $translations ) ),
262 'include_children' => false,
263 ),
264 ),
265 )
266 );
267
268 $lang = $this->model->term->get_language( $term_id ); // Language of the created term
269 $posts = array();
270
271 foreach ( $tr_posts as $post_id ) {
272 $post = $this->model->post->get_translation( $post_id, $lang );
273
274 if ( $post ) {
275 $posts[] = $post;
276 }
277 }
278
279 $posts = array_unique( $posts );
280
281 foreach ( $posts as $post_id ) {
282 if ( current_user_can( 'assign_term', $term_id ) ) {
283 wp_set_object_terms( $post_id, $term_id, $taxonomy, true );
284 }
285 }
286 }
287 }
288
289 /**
290 * Deactivate the synchronization of terms before deleting a term
291 * to avoid translated terms to be removed from translated posts
292 *
293 * @since 2.3.2
294 *
295 * @return void
296 */
297 public function pre_delete_term() {
298 remove_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
299 }
300
301 /**
302 * Re-activate the synchronization of terms after a term is deleted
303 *
304 * @since 2.3.2
305 *
306 * @return void
307 */
308 public function delete_term() {
309 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
310 }
311 }
312