PluginProbe
Polylang / 2.6.2
Polylang v2.6.2
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 2.6.2, at modules/sync/sync-tax.php

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