PluginProbe
Polylang / 2.1.6
Polylang v2.1.6
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 / admin-sync.php

admin-sync.php in Polylang 2.1.6, at modules/sync/admin-sync.php

396 lines 14.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Manages copy and synchronization of terms and post metas
5 *
6 * @since 1.2
7 */
8 class PLL_Admin_Sync {
9
10 /**
11 * Constructor
12 *
13 * @since 1.2
14 *
15 * @param object $polylang
16 */
17 public function __construct( &$polylang ) {
18 $this->model = &$polylang->model;
19 $this->options = &$polylang->options;
20
21 add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 3 );
22 add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 5, 2 ); // Before Types which populates custom fields in same hook with priority 10
23
24 add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
25 add_action( 'pll_save_term', array( $this, 'pll_save_term' ), 10, 3 );
26
27 if ( $this->options['media_support'] ) {
28 add_action( 'pll_translate_media', array( $this, 'copy_taxonomies' ), 10, 3 );
29 add_action( 'pll_translate_media', array( $this, 'copy_post_metas' ), 10, 3 );
30 add_action( 'edit_attachment', array( $this, 'edit_attachment' ) );
31 }
32 }
33
34 /**
35 * Translate post parent if exists when using "Add new" ( translation )
36 *
37 * @since 0.6
38 *
39 * @param int $post_parent Post parent ID
40 * @param int $post_id Post ID, unused
41 * @param array $postarr Array of parsed post data
42 * @return int
43 */
44 public function wp_insert_post_parent( $post_parent, $post_id, $postarr ) {
45 // Make sure not to impact media translations created at the same time
46 return isset( $_GET['from_post'], $_GET['new_lang'], $_GET['post_type'] ) && $_GET['post_type'] === $postarr['post_type'] && ( $id = wp_get_post_parent_id( (int) $_GET['from_post'] ) ) && ( $parent = $this->model->post->get_translation( $id, $_GET['new_lang'] ) ) ? $parent : $post_parent;
47 }
48
49 /**
50 * Copy post metas, menu order, comment and ping status when using "Add new" ( translation )
51 * formerly used dbx_post_advanced deprecated in WP 3.7
52 *
53 * @since 1.2
54 *
55 * @param string $post_type unused
56 * @param object $post current post object
57 */
58 public function add_meta_boxes( $post_type, $post ) {
59 if ( 'post-new.php' == $GLOBALS['pagenow'] && isset( $_GET['from_post'], $_GET['new_lang'] ) && $this->model->is_translated_post_type( $post->post_type ) ) {
60 // Capability check already done in post-new.php
61 $from_post_id = (int) $_GET['from_post'];
62 $from_post = get_post( $from_post_id );
63 $lang = $this->model->get_language( $_GET['new_lang'] );
64
65 if ( ! $from_post || ! $lang ) {
66 return;
67 }
68
69 $this->copy_taxonomies( $from_post_id, $post->ID, $lang->slug );
70 $this->copy_post_metas( $from_post_id, $post->ID, $lang->slug );
71
72 foreach ( array( 'menu_order', 'comment_status', 'ping_status' ) as $property ) {
73 $post->$property = $from_post->$property;
74 }
75
76 // Copy the date only if the synchronization is activated
77 if ( in_array( 'post_date', $this->options['sync'] ) ) {
78 $post->post_date = $from_post->post_date;
79 $post->post_date_gmt = $from_post->post_date_gmt;
80 }
81
82 if ( is_sticky( $from_post_id ) ) {
83 stick_post( $post->ID );
84 }
85 }
86 }
87
88 /**
89 * Get the list of taxonomies to copy or to synchronize
90 *
91 * @since 1.7
92 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
93 *
94 * @param bool $sync true if it is synchronization, false if it is a copy
95 * @param int $from id of the post from which we copy informations, optional, defaults to null
96 * @param int $to id of the post to which we paste informations, optional, defaults to null
97 * @param string $lang language slug, optional, defaults to null
98 * @return array list of taxonomy names
99 */
100 public function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) {
101 $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
102 if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
103 $taxonomies[] = 'post_format';
104 }
105
106 /**
107 * Filter the taxonomies to copy or synchronize
108 *
109 * @since 1.7
110 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
111 *
112 * @param array $taxonomies list of taxonomy names
113 * @param bool $sync true if it is synchronization, false if it is a copy
114 * @param int $from id of the post from which we copy informations
115 * @param int $to id of the post to which we paste informations
116 * @param string $lang language slug
117 */
118 return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync, $from, $to, $lang ) );
119 }
120
121 /**
122 * Copy or synchronize terms
123 *
124 * @since 1.8
125 *
126 * @param int $from id of the post from which we copy informations
127 * @param int $to id of the post to which we paste informations
128 * @param string $lang language slug
129 * @param bool $sync true if it is synchronization, false if it is a copy, defaults to false
130 */
131 public function copy_taxonomies( $from, $to, $lang, $sync = false ) {
132 // Get taxonomies to sync for this post type
133 $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( $sync, $from, $to, $lang ) );
134
135 // Update the term cache to reduce the number of queries in the loop
136 update_object_term_cache( $sync ? array( $from, $to ) : $from, get_post_type( $from ) );
137
138 // Copy or synchronize terms
139 // FIXME quite a lot of query in foreach
140 foreach ( $taxonomies as $tax ) {
141 $terms = get_the_terms( $from, $tax );
142
143 // Translated taxonomy
144 if ( $this->model->is_translated_taxonomy( $tax ) ) {
145 $newterms = array();
146 if ( is_array( $terms ) ) {
147 foreach ( $terms as $term ) {
148 if ( $term_id = $this->model->term->get_translation( $term->term_id, $lang ) ) {
149 $newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags
150 }
151 }
152 }
153
154 // For some reasons, the user may have untranslated terms in the translation. don't forget them.
155 if ( $sync ) {
156 $tr_terms = get_the_terms( $to, $tax );
157 if ( is_array( $tr_terms ) ) {
158 foreach ( $tr_terms as $term ) {
159 if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $from ) ) ) {
160 $newterms[] = (int) $term->term_id;
161 }
162 }
163 }
164 }
165
166 if ( ! empty( $newterms ) || $sync ) {
167 wp_set_object_terms( $to, $newterms, $tax ); // replace terms in translation
168 }
169 }
170
171 // Untranslated taxonomy ( post format )
172 // Don't use simple get_post_format / set_post_format to generalize the case to other taxonomies
173 else {
174 wp_set_object_terms( $to, is_array( $terms ) ? array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) ) : null, $tax );
175 }
176 }
177 }
178
179 /**
180 * Copy or synchronize metas (custom fields)
181 *
182 * @since 0.9
183 *
184 * @param int $from id of the post from which we copy informations
185 * @param int $to id of the post to which we paste informations
186 * @param string $lang language slug
187 * @param bool $sync true if it is synchronization, false if it is a copy, defaults to false
188 */
189 public function copy_post_metas( $from, $to, $lang, $sync = false ) {
190 // Copy or synchronize post metas and allow plugins to do the same
191 $metas = get_post_custom( $from );
192 $keys = array();
193
194 // Get public meta keys ( including from translated post in case we just deleted a custom field )
195 if ( ! $sync || in_array( 'post_meta', $this->options['sync'] ) ) {
196 foreach ( $keys = array_unique( array_merge( array_keys( $metas ), array_keys( get_post_custom( $to ) ) ) ) as $k => $meta_key ) {
197 if ( is_protected_meta( $meta_key ) ) {
198 unset( $keys[ $k ] );
199 }
200 }
201 }
202
203 // Add page template and featured image
204 foreach ( array( '_wp_page_template', '_thumbnail_id' ) as $meta ) {
205 if ( ! $sync || in_array( $meta, $this->options['sync'] ) ) {
206 $keys[] = $meta;
207 }
208 }
209
210 /**
211 * Filter the custom fields to copy or synchronize
212 *
213 * @since 0.6
214 * @since 1.9.2 The `$from`, `$to`, `$lang` parameters were added.
215 *
216 * @param array $keys list of custom fields names
217 * @param bool $sync true if it is synchronization, false if it is a copy
218 * @param int $from id of the post from which we copy informations
219 * @param int $to id of the post to which we paste informations
220 * @param string $lang language slug
221 */
222 $keys = array_unique( apply_filters( 'pll_copy_post_metas', $keys, $sync, $from, $to, $lang ) );
223
224 // And now copy / synchronize
225 foreach ( $keys as $key ) {
226 delete_post_meta( $to, $key ); // The synchronization process of multiple values custom fields is easier if we delete all metas first
227 if ( isset( $metas[ $key ] ) ) {
228 foreach ( $metas[ $key ] as $value ) {
229 // Important: always maybe_unserialize value coming from get_post_custom. See codex.
230 // Thanks to goncalveshugo http://wordpress.org/support/topic/plugin-polylang-pll_copy_post_meta
231 $value = maybe_unserialize( $value );
232 // Special case for featured images which can be translated
233 add_post_meta( $to, $key, ( '_thumbnail_id' == $key && $tr_value = $this->model->post->get_translation( $value, $lang ) ) ? $tr_value : $value );
234 }
235 }
236 }
237 }
238
239 /**
240 * Synchronizes terms and metas in translations
241 *
242 * @since 1.2
243 *
244 * @param int $post_id post id
245 * @param object $post post object
246 * @param array $translations post translations
247 */
248 public function pll_save_post( $post_id, $post, $translations ) {
249 global $wpdb;
250
251 // Prepare properties to synchronize
252 foreach ( array( 'comment_status', 'ping_status', 'menu_order' ) as $property ) {
253 if ( in_array( $property, $this->options['sync'] ) ) {
254 $postarr[ $property ] = $post->$property;
255 }
256 }
257
258 if ( in_array( 'post_date', $this->options['sync'] ) ) {
259 // For new drafts, save the date now otherwise it is overriden by WP. Thanks to JoryHogeveen. See #32.
260 if ( 'post-new.php' === $GLOBALS['pagenow'] && isset( $_GET['from_post'], $_GET['new_lang'] ) ) {
261 $original = get_post( (int) $_GET['from_post'] );
262 $wpdb->update(
263 $wpdb->posts, array(
264 'post_date' => $original->post_date,
265 'post_date_gmt' => $original->post_date_gmt,
266 ),
267 array( 'ID' => $post_id )
268 );
269 } else {
270 $postarr['post_date'] = $post->post_date;
271 $postarr['post_date_gmt'] = $post->post_date_gmt;
272 }
273 }
274
275 // Synchronize terms and metas in translations
276 foreach ( $translations as $lang => $tr_id ) {
277 if ( ! $tr_id || $tr_id === $post_id ) {
278 continue;
279 }
280
281 // Synchronize terms and metas
282 $this->copy_taxonomies( $post_id, $tr_id, $lang, true );
283 $this->copy_post_metas( $post_id, $tr_id, $lang, true );
284
285 // Sticky posts
286 if ( in_array( 'sticky_posts', $this->options['sync'] ) ) {
287 isset( $_REQUEST['sticky'] ) && 'sticky' === $_REQUEST['sticky'] ? stick_post( $tr_id ) : unstick_post( $tr_id );
288 }
289
290 // Add comment status, ping status, menu order... to synchronization
291 $tr_arr = empty( $postarr ) ? array() : $postarr;
292
293 if ( isset( $GLOBALS['post_type'] ) ) {
294 $post_type = $GLOBALS['post_type'];
295 } elseif ( isset( $_REQUEST['post_type'] ) ){
296 $post_type = $_REQUEST['post_type']; // 2nd case for quick edit
297 }
298
299 // Add post parent to synchronization
300 // Make sure not to impact media translations when creating them at the same time as post
301 // Do not udpate the translation parent if the user set a parent with no translation
302 if ( in_array( 'post_parent', $this->options['sync'] ) && isset( $post_type ) && $post_type === $post->post_type ) {
303 $post_parent = ( $parent_id = wp_get_post_parent_id( $post_id ) ) ? $this->model->post->get_translation( $parent_id, $lang ) : 0;
304 if ( ! ( $parent_id && ! $post_parent ) ) {
305 $tr_arr['post_parent'] = $post_parent;
306 }
307 }
308
309 // Update all the row at once
310 // Don't use wp_update_post to avoid infinite loop
311 if ( ! empty( $tr_arr ) ) {
312 $wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) );
313 clean_post_cache( $tr_id );
314 }
315 }
316 }
317
318 /**
319 * Synchronize translations of a term in all posts
320 *
321 * @since 1.2
322 *
323 * @param int $term_id term id
324 * @param string $taxonomy taxonomy name of the term
325 * @param array $translations translations of the term
326 */
327 public function pll_save_term( $term_id, $taxonomy, $translations ) {
328 // Check if the taxonomy is synchronized
329 if ( ! $this->model->is_translated_taxonomy( $taxonomy ) || ! in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) {
330 return;
331 }
332
333 // Get all posts associated to this term
334 $posts = get_posts( array(
335 'numberposts' => -1,
336 'nopaging' => true,
337 'post_type' => 'any',
338 'post_status' => 'any',
339 'fields' => 'ids',
340 'tax_query' => array( array(
341 'taxonomy' => $taxonomy,
342 'field' => 'id',
343 'terms' => array_merge( array( $term_id ), array_values( $translations ) ),
344 'include_children' => false,
345 ) ),
346 ) );
347
348 // Associate translated term to translated post
349 // FIXME quite a lot of query in foreach
350 foreach ( $this->model->get_languages_list() as $language ) {
351 if ( $translated_term = $this->model->term->get( $term_id, $language ) ) {
352 foreach ( $posts as $post_id ) {
353 if ( $translated_post = $this->model->post->get( $post_id, $language ) ) {
354 wp_set_object_terms( $translated_post, $translated_term, $taxonomy, true );
355 }
356 }
357 }
358 }
359
360 // Synchronize parent in translations
361 // Calling clean_term_cache *after* this is mandatory otherwise the $taxonomy_children option is not correctly updated
362 // Before WP 3.9 clean_term_cache could be called ( efficiently ) only one time due to static array which prevented to update the option more than once
363 // This is the reason to use the edit_term filter and not edited_term
364 // Take care that $_POST contains the only valid values for the current term
365 // FIXME can I synchronize parent without using $_POST instead?
366 if ( isset( $_POST['term_tr_lang'] ) ) {
367 foreach ( $_POST['term_tr_lang'] as $lang => $tr_id ) {
368 if ( $tr_id ) {
369 if ( isset( $_POST['parent'] ) && -1 != $_POST['parent'] ) { // Since WP 3.1
370 $term_parent = $this->model->term->get_translation( (int) $_POST['parent'], $lang );
371 }
372
373 global $wpdb;
374 $wpdb->update( $wpdb->term_taxonomy,
375 array( 'parent' => isset( $term_parent ) ? $term_parent : 0 ),
376 array( 'term_taxonomy_id' => get_term( (int) $tr_id, $taxonomy )->term_taxonomy_id )
377 );
378
379 clean_term_cache( $tr_id, $taxonomy ); // OK since WP 3.9
380 }
381 }
382 }
383 }
384
385 /**
386 * Synchronizes terms and metas in translations for media
387 *
388 * @since 1.8
389 *
390 * @param int $post_id post id
391 */
392 public function edit_attachment( $post_id ) {
393 $this->pll_save_post( $post_id, get_post( $post_id ), $this->model->post->get_translations( $post_id ) );
394 }
395 }
396