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

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

363 lines 12.8 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 if ( is_sticky( $from_post_id ) ) {
77 stick_post( $post->ID );
78 }
79 }
80 }
81
82 /**
83 * Get the list of taxonomies to copy or to synchronize
84 *
85 * @since 1.7
86 *
87 * @param bool $sync true if it is synchronization, false if it is a copy
88 * @return array list of taxonomy names
89 */
90 public function get_taxonomies_to_copy( $sync ) {
91 $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
92 if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
93 $taxonomies[] = 'post_format';
94 }
95
96 /**
97 * Filter the taxonomies to copy or synchronize
98 *
99 * @since 1.7
100 *
101 * @param array $taxonomies list of taxonomy names
102 * @param bool $sync true if it is synchronization, false if it is a copy
103 */
104 return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync ) );
105 }
106
107 /**
108 * Copy or synchronize terms
109 *
110 * @since 1.8
111 *
112 * @param int $from id of the post from which we copy informations
113 * @param int $to id of the post to which we paste informations
114 * @param string $lang language slug
115 * @param bool $sync true if it is synchronization, false if it is a copy, defaults to false
116 */
117 public function copy_taxonomies( $from, $to, $lang, $sync = false ) {
118 // Get taxonomies to sync for this post type
119 $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( $sync ) );
120
121 // Update the term cache to reduce the number of queries in the loop
122 update_object_term_cache( $sync ? array( $from, $to ) : $from, get_post_type( $from ) );
123
124 // Copy or synchronize terms
125 // FIXME quite a lot of query in foreach
126 foreach ( $taxonomies as $tax ) {
127 $terms = get_the_terms( $from, $tax );
128
129 // Translated taxonomy
130 if ( $this->model->is_translated_taxonomy( $tax ) ) {
131 $newterms = array();
132 if ( is_array( $terms ) ) {
133 foreach ( $terms as $term ) {
134 if ( $term_id = $this->model->term->get_translation( $term->term_id, $lang ) ) {
135 $newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags
136 }
137 }
138 }
139
140 // For some reasons, the user may have untranslated terms in the translation. don't forget them.
141 if ( $sync ) {
142 $tr_terms = get_the_terms( $to, $tax );
143 if ( is_array( $tr_terms ) ) {
144 foreach ( $tr_terms as $term ) {
145 if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $from ) ) ) {
146 $newterms[] = (int) $term->term_id;
147 }
148 }
149 }
150 }
151
152 if ( ! empty( $newterms ) || $sync ) {
153 wp_set_object_terms( $to, $newterms, $tax ); // replace terms in translation
154 }
155 }
156
157 // Untranslated taxonomy ( post format )
158 // Don't use simple get_post_format / set_post_format to generalize the case to other taxonomies
159 else {
160 wp_set_object_terms( $to, is_array( $terms ) ? array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) ) : null, $tax );
161 }
162 }
163 }
164
165 /**
166 * Copy or synchronize metas (custom fields)
167 *
168 * @since 0.9
169 *
170 * @param int $from id of the post from which we copy informations
171 * @param int $to id of the post to which we paste informations
172 * @param string $lang language slug
173 * @param bool $sync true if it is synchronization, false if it is a copy, defaults to false
174 */
175 public function copy_post_metas( $from, $to, $lang, $sync = false ) {
176 // Copy or synchronize post metas and allow plugins to do the same
177 $metas = get_post_custom( $from );
178 $keys = array();
179
180 // Get public meta keys ( including from translated post in case we just deleted a custom field )
181 if ( ! $sync || in_array( 'post_meta', $this->options['sync'] ) ) {
182 foreach ( $keys = array_unique( array_merge( array_keys( $metas ), array_keys( get_post_custom( $to ) ) ) ) as $k => $meta_key ) {
183 if ( is_protected_meta( $meta_key ) ) {
184 unset( $keys[ $k ] );
185 }
186 }
187 }
188
189 // Add page template and featured image
190 foreach ( array( '_wp_page_template', '_thumbnail_id' ) as $meta ) {
191 if ( ! $sync || in_array( $meta, $this->options['sync'] ) ) {
192 $keys[] = $meta;
193 }
194 }
195
196 /**
197 * Filter the custom fields to copy or synchronize
198 *
199 * @since 0.6
200 * @since 1.9.2 The `$from`, `$to`, `$lang` parameters were added.
201 *
202 * @param array $keys list of custom fields names
203 * @param bool $sync true if it is synchronization, false if it is a copy
204 * @param int $from id of the post from which we copy informations
205 * @param int $to id of the post to which we paste informations
206 * @param string $lang language slug
207 */
208 $keys = array_unique( apply_filters( 'pll_copy_post_metas', $keys, $sync, $from, $to, $lang ) );
209
210 // And now copy / synchronize
211 foreach ( $keys as $key ) {
212 delete_post_meta( $to, $key ); // The synchronization process of multiple values custom fields is easier if we delete all metas first
213 if ( isset( $metas[ $key ] ) ) {
214 foreach ( $metas[ $key ] as $value ) {
215 // Important: always maybe_unserialize value coming from get_post_custom. See codex.
216 // Thanks to goncalveshugo http://wordpress.org/support/topic/plugin-polylang-pll_copy_post_meta
217 $value = maybe_unserialize( $value );
218 // Special case for featured images which can be translated
219 add_post_meta( $to, $key, ( '_thumbnail_id' == $key && $tr_value = $this->model->post->get_translation( $value, $lang ) ) ? $tr_value : $value );
220 }
221 }
222 }
223 }
224
225 /**
226 * Synchronizes terms and metas in translations
227 *
228 * @since 1.2
229 *
230 * @param int $post_id post id
231 * @param object $post post object
232 * @param array $translations post translations
233 */
234 public function pll_save_post( $post_id, $post, $translations ) {
235 global $wpdb, $post_type;
236
237 // Prepare properties to synchronize
238 foreach ( array( 'comment_status', 'ping_status', 'menu_order', 'post_date' ) as $property ) {
239 if ( in_array( $property, $this->options['sync'] ) ) {
240 $postarr[ $property ] = $post->$property;
241 }
242 }
243
244 if ( in_array( 'post_date', $this->options['sync'] ) ) {
245 $postarr['post_date_gmt'] = $post->post_date_gmt;
246 }
247
248 // Synchronize terms and metas in translations
249 foreach ( $translations as $lang => $tr_id ) {
250 if ( ! $tr_id || $tr_id === $post_id ) {
251 continue;
252 }
253
254 // Synchronize terms and metas
255 $this->copy_taxonomies( $post_id, $tr_id, $lang, true );
256 $this->copy_post_metas( $post_id, $tr_id, $lang, true );
257
258 // Sticky posts
259 if ( in_array( 'sticky_posts', $this->options['sync'] ) ) {
260 isset( $_REQUEST['sticky'] ) ? stick_post( $tr_id ) : unstick_post( $tr_id );
261 }
262
263 // Add comment status, ping status, menu order... to synchronization
264 $tr_arr = empty( $postarr ) ? array() : $postarr;
265
266 // Add post parent to synchronization
267 // Make sure not to impact media translations when creating them at the same time as post
268 // Do not udpate the translation parent if the user set a parent with no translation
269 if ( in_array( 'post_parent', $this->options['sync'] ) && $post_type === $post->post_type ) {
270 $post_parent = ( $parent_id = wp_get_post_parent_id( $post_id ) ) ? $this->model->post->get_translation( $parent_id, $lang ) : 0;
271 if ( ! ( $parent_id && ! $post_parent ) ) {
272 $tr_arr['post_parent'] = $post_parent;
273 }
274 }
275
276 // Update all the row at once
277 // Don't use wp_update_post to avoid infinite loop
278 if ( ! empty( $tr_arr ) ) {
279 $wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) );
280 clean_post_cache( $tr_id );
281 }
282 }
283 }
284
285 /**
286 * Synchronize translations of a term in all posts
287 *
288 * @since 1.2
289 *
290 * @param int $term_id term id
291 * @param string $taxonomy taxonomy name of the term
292 * @param array $translations translations of the term
293 */
294 public function pll_save_term( $term_id, $taxonomy, $translations ) {
295 // Check if the taxonomy is synchronized
296 if ( ! $this->model->is_translated_taxonomy( $taxonomy ) || ! in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) {
297 return;
298 }
299
300 // Get all posts associated to this term
301 $posts = get_posts( array(
302 'numberposts' => -1,
303 'nopaging' => true,
304 'post_type' => 'any',
305 'post_status' => 'any',
306 'fields' => 'ids',
307 'tax_query' => array( array(
308 'taxonomy' => $taxonomy,
309 'field' => 'id',
310 'terms' => array_merge( array( $term_id ), array_values( $translations ) ),
311 'include_children' => false,
312 ) ),
313 ) );
314
315 // Associate translated term to translated post
316 // FIXME quite a lot of query in foreach
317 foreach ( $this->model->get_languages_list() as $language ) {
318 if ( $translated_term = $this->model->term->get( $term_id, $language ) ) {
319 foreach ( $posts as $post_id ) {
320 if ( $translated_post = $this->model->post->get( $post_id, $language ) ) {
321 wp_set_object_terms( $translated_post, $translated_term, $taxonomy, true );
322 }
323 }
324 }
325 }
326
327 // Synchronize parent in translations
328 // Calling clean_term_cache *after* this is mandatory otherwise the $taxonomy_children option is not correctly updated
329 // 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
330 // This is the reason to use the edit_term filter and not edited_term
331 // Take care that $_POST contains the only valid values for the current term
332 // FIXME can I synchronize parent without using $_POST instead?
333 if ( isset( $_POST['term_tr_lang'] ) ) {
334 foreach ( $_POST['term_tr_lang'] as $lang => $tr_id ) {
335 if ( $tr_id ) {
336 if ( isset( $_POST['parent'] ) && -1 != $_POST['parent'] ) { // Since WP 3.1
337 $term_parent = $this->model->term->get_translation( (int) $_POST['parent'], $lang );
338 }
339
340 global $wpdb;
341 $wpdb->update( $wpdb->term_taxonomy,
342 array( 'parent' => isset( $term_parent ) ? $term_parent : 0 ),
343 array( 'term_taxonomy_id' => get_term( (int) $tr_id, $taxonomy )->term_taxonomy_id )
344 );
345
346 clean_term_cache( $tr_id, $taxonomy ); // OK since WP 3.9
347 }
348 }
349 }
350 }
351
352 /**
353 * Synchronizes terms and metas in translations for media
354 *
355 * @since 1.8
356 *
357 * @param int $post_id post id
358 */
359 public function edit_attachment( $post_id ) {
360 $this->pll_save_post( $post_id, get_post( $post_id ), $this->model->post->get_translations( $post_id ) );
361 }
362 }
363