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

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

311 lines 9.6 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 *
48 * @param bool $sync True if it is synchronization, false if it is a copy.
49 * @param int $from Id of the post from which we copy informations, optional, defaults to null.
50 * @param int $to Id of the post to which we paste informations, optional, defaults to null.
51 * @param string $lang Language slug, optional, defaults to null.
52 * @return string[] List of taxonomy names.
53 */
54 protected function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) {
55 $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
56 if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
57 $taxonomies[] = 'post_format';
58 }
59
60 /**
61 * Filters the taxonomies to copy or synchronize.
62 *
63 * @since 1.7
64 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
65 *
66 * @param string[] $taxonomies List of taxonomy names.
67 * @param bool $sync True if it is synchronization, false if it is a copy.
68 * @param int $from Id of the post from which we copy informations.
69 * @param int $to Id of the post to which we paste informations.
70 * @param string $lang Language slug.
71 */
72 return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync, $from, $to, $lang ) );
73 }
74
75 /**
76 * When copying or synchronizing terms, translate terms in translatable taxonomies
77 *
78 * @since 2.3
79 *
80 * @param int $object_id Object ID.
81 * @param int[] $terms List of terms ids assigned to the source post.
82 * @param string $taxonomy Taxonomy name.
83 * @param string $lang Language slug.
84 * @return int[] List of terms ids to assign to the target post.
85 */
86 protected function maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ) {
87 if ( is_array( $terms ) && $this->model->is_translated_taxonomy( $taxonomy ) ) {
88 $newterms = array();
89
90 // Convert to term ids if we got tag names
91 $strings = array_map( 'is_string', $terms );
92 if ( in_array( true, $strings, true ) ) {
93 $terms = get_the_terms( $object_id, $taxonomy );
94 $terms = wp_list_pluck( $terms, 'term_id' );
95 }
96
97 foreach ( $terms as $term ) {
98 /**
99 * Filter the translated term when a post translation is created or synchronized
100 *
101 * @since 2.3
102 *
103 * @param int $tr_term Translated term id
104 * @param int $term Source term id
105 * @param string $lang Language slug
106 */
107 if ( $term_id = apply_filters( 'pll_maybe_translate_term', $this->model->term->get_translation( $term, $lang ), $term, $lang ) ) {
108 $newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags
109 }
110 }
111
112 return $newterms;
113 }
114
115 return $terms; // Empty $terms or untranslated taxonomy
116 }
117
118 /**
119 * Maybe copy taxonomy terms from one post to the other.
120 *
121 * @since 2.6
122 *
123 * @param int $object_id Source object ID.
124 * @param int $tr_id Target object ID.
125 * @param string $lang Target language.
126 * @param array $terms An array of object terms.
127 * @param string $taxonomy Taxonomy slug.
128 * @param bool $append Whether to append new terms to the old terms.
129 * @return void
130 */
131 protected function copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ) {
132 $to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang );
133
134 if ( in_array( $taxonomy, $to_copy ) ) {
135 $newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang );
136
137 // For some reasons, the user may have untranslated terms in the translation. Don't forget them.
138 if ( $this->model->is_translated_taxonomy( $taxonomy ) ) {
139 $tr_terms = get_the_terms( $tr_id, $taxonomy );
140 if ( is_array( $tr_terms ) ) {
141 foreach ( $tr_terms as $term ) {
142 if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) {
143 $newterms[] = (int) $term->term_id;
144 }
145 }
146 }
147 }
148
149 wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append );
150 }
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_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 fron 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' ), 10, 6 );
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, 6 );
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' ), 10, 5 );
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