PLL_Admin_Sync

Manages copy and synchronization of terms and post metas.

Defined (1)

The class is defined in the following location(s).

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