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

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

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