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

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

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