├── .gitignore
├── README.md
├── css
└── admin.css
├── include
├── class-PolylangPostClonerAdmin.php
└── class-PolylangPostClonerWatchMeta.php
├── index.php
└── languages
├── polylang-fix-relationships-de_DE.mo
└── polylang-fix-relationships-de_DE.po
/.gitignore:
--------------------------------------------------------------------------------
1 | ## generic files to ignore
2 | *~
3 | *.lock
4 | *.DS_Store
5 | *.swp
6 | *.out
7 | .svn/
8 | .svnignore
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Polylang Fix Relationships
2 | ==========================
3 |
4 | **This Plugin is discontinued.**
5 | You might like to take a look at [Polylang Sync](https://github.com/mcguffin/polylang-sync) here on GitHub, which addresses a similar issue.
6 |
7 | ---
8 |
9 | Manage post relationships like attachments, post parents and ACF Relational fields which are not
10 | covered by [Polylang](http://polylang.wordpress.com) plugin.
11 |
12 | Compatibility
13 | -------------
14 | Tested With WP 4.4.2 - 4.5.2, Polylang 1.8.4 - 1.9, ACF pro 5.x
15 |
16 |
17 | Features:
18 | ---------
19 | - Adds a clone post feature
20 | - Clones post attachments to corresponding languages
21 | - Changes post thumbnail and ACF Relations (like post object, image, ...) to their corresponding translations
22 |
23 | Plugin API
24 | ----------
25 |
26 | There are two filters allowing a developer to disable one of the two two features:
27 |
28 | // disables the Fix Relationships row action
29 | add_filter( 'polylang_relationships_enable_fix', '__return_false' );
30 |
31 | // disables the Create4 Translations row action
32 | add_filter( 'polylang_relationships_enable_clone', '__return_false' );
33 |
34 |
--------------------------------------------------------------------------------
/css/admin.css:
--------------------------------------------------------------------------------
1 | /* language columns in edit.php and edit-tags.php */
2 | th.column-polylang_cloner_actions, td.column-polylang_cloner_actions {
3 | width: 10em;
4 | }
5 |
--------------------------------------------------------------------------------
/include/class-PolylangPostClonerAdmin.php:
--------------------------------------------------------------------------------
1 | options['media_support'] ) {
38 | add_action( 'pll_save_post' , array( &$this , 'handle_attachments' ) , 15 , 3 );
39 | }
40 | }
41 |
42 | /**
43 | * Load css
44 | * @action 'admin_enqueue_scripts'
45 | */
46 | public function admin_enqueue_scripts() {
47 | wp_enqueue_style( 'polylang-cloner-admin' , plugins_url('css/admin.css', dirname(__FILE__)) );
48 | }
49 |
50 | /**
51 | * Setup columns
52 | * @action 'admin_init'
53 | */
54 | function admin_init() {
55 | add_filter('post_row_actions',array(&$this,'row_actions') , 10 , 2 );
56 | add_filter('page_row_actions',array(&$this,'row_actions') , 10 , 2 );
57 |
58 | // add_filter('page_row_actions',array(&$this,'row_actions') , 10 , 2 );
59 | }
60 |
61 | /**
62 | * Add row actions
63 | *
64 | * @filter 'post_row_actions', 'page_row_actions'
65 | */
66 | function row_actions( $actions , $post ) {
67 | if ( pll_is_translated_post_type( $post->post_type ) ) {
68 | $translations = PLL()->model->post->get_translations( $post->ID );
69 | $languages = pll_languages_list();
70 | $current_language = pll_get_post_language($post->ID);
71 |
72 |
73 | // translations post cloner
74 | if ( apply_filters( 'polylang_relationships_enable_clone', true )
75 | && count($translations) != count( $languages ) ) {
76 | $missing_translations = array();
77 | foreach ( $languages as $lang_slug )
78 | if ( ! isset( $translations[$lang_slug] ) && $lang_slug != $current_language)
79 | $missing_translations[] = $lang_slug;
80 |
81 | $action = 'polylang_get_translation';
82 | $url_params = array(
83 | 'polylang_action' => $action,
84 | 'new_langs' => $missing_translations,
85 | 'from_post' => $post->ID,
86 | );
87 |
88 | $href = wp_nonce_url( add_query_arg( $url_params ) , $action );
89 |
90 | $actions['clone_for_translation'] = sprintf( '%s',
91 | $href, _n('Create Translation','Create Translations',count($missing_translations),'polylang-fix-relationships')
92 | );
93 | }
94 |
95 | // fix relationships
96 | if ( apply_filters( 'polylang_relationships_enable_fix', true )
97 | && count( array_diff( array_keys( $translations ), array( $current_language ) ) ) ) {
98 | $action = 'polylang_fix_relations';
99 | $url_params = array(
100 | 'polylang_action' => $action,
101 | 'from_post' => $post->ID,
102 | );
103 | $href = wp_nonce_url( add_query_arg( $url_params ) , $action );
104 | $actions['fix_relations'] = sprintf( '%s',
105 | $href, __( 'Fix Relations' , 'polylang-fix-relationships' )
106 | );
107 | }
108 | }
109 | return $actions;
110 | }
111 |
112 |
113 | /**
114 | * Do Post cloning
115 | *
116 | * @action 'load-edit.php', 'load-upload.php'
117 | */
118 | function do_clone_action() {
119 | if ( isset( $_REQUEST['new_langs'] , $_REQUEST['from_post'] , $_REQUEST['polylang_action'] ) && current_user_can( 'edit_post' , (int) $_REQUEST['from_post'] ) ) {
120 | check_admin_referer( $_REQUEST['polylang_action'] );
121 | $source_post_ids = (array) $_REQUEST['from_post'];
122 | $source_post_ids = array_filter($source_post_ids , 'intval' );
123 | $this->translated_posts_ids = array();
124 | foreach ( $source_post_ids as $source_post_id ) {
125 | $source_post_lang = pll_get_post_language( $source_post_id );
126 |
127 | if ( $source_post = get_post( $source_post_id ) ) {
128 | switch ( $_REQUEST['polylang_action'] ) {
129 | case 'polylang_get_translation':
130 | $langs = (array) $_REQUEST['new_langs'];
131 | $translation_group = $this->create_translation_group( $source_post , $langs );
132 | break;
133 | case 'polylang_fix_relations':
134 | $translation_group = PLL()->model->post->get_translations( $source_post_id );
135 | break;
136 | }
137 | // trigger save action.
138 | unset($translation_group[$source_post_lang]);
139 | do_action( 'pll_save_post' , $source_post_id , get_post( $source_post_id ) , $translation_group );
140 | }
141 | }
142 | if ( count( $this->translated_posts_ids ) === 1 ) {
143 | $redirect = get_edit_post_link( $this->translated_posts_ids[0] , '' );
144 | } else {
145 | $redirect = remove_query_arg( array('new_langs','from_post','_wpnonce','polylang_action'));
146 | }
147 | wp_redirect($redirect);
148 | exit();
149 | }
150 | }
151 |
152 | /**
153 | * Clone post.
154 | * Creates translations out of $source_post where necessary.
155 | *
156 | * @param $source_post object Master Post
157 | * @param $langs array holding the target languages
158 | * @return array translation group with language slugs as keys and post ids as values
159 | */
160 | function create_translation_group( $source_post , $langs ) {
161 | $source_post_lang = pll_get_post_language( $source_post->ID );
162 | $translation_group = array(
163 | $source_post_lang => $source_post->ID,
164 | ) + PLL()->model->post->get_translations( $source_post->ID );
165 |
166 | foreach ( $langs as $lang ) {
167 | // check if is language
168 | if ( ($lang = PLL()->model->get_language($lang)) && ! isset( $translation_group[$lang->slug] ) ) {
169 | $new_post_id = $this->make_post_translation( $source_post , $lang );
170 | $translation_group[$lang->slug] = $new_post_id;
171 | $this->translated_posts_ids[] = $new_post_id;
172 | }
173 | }
174 | pll_save_post_translations($translation_group);
175 | return $translation_group;
176 | }
177 |
178 |
179 | /**
180 | * Set parent-child relations for attachments in source posts' translation group.
181 | *
182 | * @param $source_post object Master Post holding the correct parent-child relations
183 | * @param $langs array holding the target languages
184 | */
185 | function handle_attachments( $source_post_id , $source_post , $parent_translation_group ) {
186 | $attachments = get_children( array( 'post_parent' => $source_post_id , 'post_type' => 'attachment' ) );
187 | foreach ( $attachments as $attachment ) {
188 | $translation_group = $this->create_translation_group( $attachment , array_keys($parent_translation_group ) );
189 | // all good here. Nothing to be done.
190 | if ( $attachment->post_parent == $source_post_id )
191 | continue;
192 | foreach ( $translation_group as $lang => $translated_id ) {
193 | if ( isset( $parent_translation_group[ $lang ] ) ) {
194 | $post_arr = array(
195 | 'ID' => $translated_id,
196 | 'post_parent' => $parent_translation_group[ $lang ]
197 | );
198 | wp_update_post($post_arr);
199 | }
200 | }
201 | }
202 | }
203 |
204 | /**
205 | * Retrieve or create post translation.
206 | * If a translation can not be found it will do a deep-clone of $source_post,
207 | * including post meta but not comments and attachments.
208 | *
209 | * @param $source_post int|object Master Post (ID) holding the correct parent-child relations
210 | * @param $lang mixed language as passed to $polylang->model->get_language($lang)
211 | */
212 | function make_post_translation( $source_post , $lang ) {
213 | if ( is_numeric( $source_post ) )
214 | $source_post = get_post( $source_post );
215 |
216 | if ( $lang = PLL()->model->get_language($lang) ) {
217 | $source_post_lang = pll_get_post_language( $source_post->ID );
218 | // sourcelang is target lang, nothing to do!
219 | if ( $lang->slug == $source_post_lang )
220 | return new WP_Error('clone',__('Source language is target language','polylang-fix-relationships'));
221 |
222 | // translation exists. Go ahead!
223 | if ( $translation = pll_get_post($source_post->ID,$lang) )
224 | return $translation;
225 |
226 | $post_arr = get_object_vars( $source_post );
227 | $post_arr['ID'] = 0;
228 | $post_arr['comment_count'] = 0;
229 | $post_arr['post_status'] = apply_filters( 'polylang_cloned_post_status' , $post_arr['post_status'] );
230 | $post_arr['post_title'] .= sprintf( ' (%s)' , $lang->slug );
231 |
232 | // set translated parent
233 | if ( $post_arr['post_parent'] && ($translated_parent = pll_get_post( $post_arr['post_parent'] , $lang ) ) ) {
234 | $post_arr['post_parent'] = $translated_parent;
235 | }
236 |
237 | // prepare taxonomies
238 | if ( $cloned_post_id = wp_insert_post( $post_arr ) ) {
239 | pll_set_post_language( $cloned_post_id , $lang );
240 | PLL()->model->clean_languages_cache();
241 |
242 | // clone postmeta
243 | $ignore_meta_keys = array( '_edit_lock' , '_edit_last' );
244 | $meta = get_post_meta( $source_post->ID );
245 |
246 | foreach ( $meta as $meta_key => $values ) {
247 | if ( in_array( $meta_key , $ignore_meta_keys ) )
248 | continue;
249 | foreach ( $values as $value ) {
250 | update_post_meta( $cloned_post_id , $meta_key , maybe_unserialize( $value ) );
251 | }
252 | }
253 |
254 | // done.
255 | return $cloned_post_id;
256 | }
257 | return new WP_Error('clone',__('No such Language','polylang-fix-relationships'));
258 | }
259 | }
260 |
261 | }
262 | endif;
263 |
264 |
--------------------------------------------------------------------------------
/include/class-PolylangPostClonerWatchMeta.php:
--------------------------------------------------------------------------------
1 | options['media_support'] ) {
46 | $this->acf_types_to_watch[] = 'image';
47 | $this->acf_types_to_watch[] = 'gallery';
48 | $this->acf_types_to_watch[] = 'file';
49 | }
50 |
51 | }
52 |
53 | /**
54 | * Setup relational ACF fields.
55 | *
56 | * @action 'init'
57 | */
58 | function acf_init() {
59 | if ( class_exists( 'acf' ) ) {
60 | // Get all ACF fields...
61 | $all_acf_fields = get_posts(array(
62 | 'post_type' => 'acf-field',
63 | 'posts_per_page' => -1,
64 | ));
65 |
66 | // ... and add the relational ones to our watchlist.
67 | foreach( $all_acf_fields as $field ) {
68 | $field_settings = unserialize( $field->post_content );
69 | if ( in_array($field_settings['type'] , $this->acf_types_to_watch ) ) {
70 | if ( $this->is_repeater_child( $field ) ) {
71 | $meta_key = $this->get_repeater_meta_key_parts( $field );
72 | } else {
73 | $meta_key = $field->post_excerpt;
74 | }
75 | $this->watch_meta_keys[] = $meta_key; // add meta key name
76 | }
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * Return whether a field is part of a repeater field.
83 | *
84 | * @param $field object ACF Field Post object
85 | *
86 | * @return bool
87 | */
88 | function is_repeater_child( $field ) {
89 | return get_post_type($field->post_parent) === 'acf-field';
90 | }
91 |
92 |
93 | /**
94 | * Return meta key parts for an acf repeater child.
95 | *
96 | * @param $field object ACF Field Post object which is part of a repeater (check with is_repeater_child first)
97 | *
98 | * @return array holding the meta key parts
99 | */
100 | function get_repeater_meta_key_parts( $field ) {
101 | $meta_key = array( $field->post_excerpt );
102 | while ( ($field = get_post($field->post_parent)) && $field->post_type == 'acf-field' ) {
103 | $meta_key[] = $field->post_excerpt;
104 | }
105 | return array_reverse($meta_key);
106 | }
107 |
108 | /**
109 | * Handle meta keys of a translation group.
110 | * Resolve Relational meta keys. Tries to change post relations to their corresponding translated post.
111 | *
112 | * @param $source_post_id int source Post ID
113 | * @param $source_post object Post
114 | * @param $translation_group array like it is returned by $polylang->model->get_translations( 'post' , $post_id ),
115 | * having the language slug as key and the post id as value.
116 | * @action 'pll_save_post'
117 | */
118 | function handle_meta( $source_post_id , $source_post , $translation_group ) {
119 | global $wpdb;
120 | $watch_meta_keys = apply_filters( 'polylang_watch_meta_keys' , $this->watch_meta_keys );
121 | $force_update = false;
122 | foreach ( $translation_group as $lang => $new_post_id ) {
123 | // prepare meta keys
124 | $post_watch_meta_keys = array();
125 | foreach( $watch_meta_keys as $meta_key ) {
126 | if ( is_array($meta_key) ) {
127 | $query = $wpdb->prepare("SELECT meta_key FROM $wpdb->postmeta WHERE meta_key REGEXP %s AND post_id=%d",
128 | sprintf('^%s$',implode( '_[0-9]_' ,$meta_key ) ),
129 | $new_post_id
130 | );
131 | $meta_keys = $wpdb->get_col($query);
132 | $post_watch_meta_keys = array_merge($post_watch_meta_keys,$meta_keys);
133 | } else {
134 | $post_watch_meta_keys[] = $meta_key;
135 | }
136 | }
137 | if ( $new_post_id != $source_post_id ) { // YES, we do this chack!
138 | foreach ( $post_watch_meta_keys as $meta_key ) {
139 | $old_meta_value = $meta_value = maybe_unserialize( get_post_meta( $new_post_id , $meta_key , true ) );
140 |
141 | if ( is_array( $meta_value ) ) {
142 | foreach ( $meta_value as $k => $v )
143 | $meta_value[$k] = pll_get_post( $v , $lang );
144 | } else if ( is_numeric( $meta_value ) ) {
145 |
146 | if ( $post = get_post( $meta_value ) ) {
147 | if ( pll_is_translated_post_type( $post->post_type ) ) {
148 | $meta_value = pll_get_post( $meta_value , $lang );
149 | } else {
150 | $force_update = true;
151 | }
152 | }
153 | }
154 |
155 | if ( $force_update || ( $old_meta_value != $meta_value ) ) {
156 | update_post_meta( $new_post_id , $meta_key , $meta_value );
157 | }
158 | }
159 | }
160 | }
161 | }
162 |
163 | }
164 | endif;
165 |
166 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | polylang plugin.
7 | Author: Jörn Lund
8 | Author URI: https://github.com/mcguffin
9 | Version: 0.0.5
10 | License: GPLv3
11 | */
12 |
13 | if ( ! class_exists( 'PolylangPostCloner' ) ) :
14 | class PolylangPostCloner {
15 | /**
16 | * Holding the singleton instance
17 | */
18 | private static $_instance = null;
19 |
20 | /**
21 | * @return WP_reCaptcha
22 | */
23 | public static function instance(){
24 | if ( is_null( self::$_instance ) )
25 | self::$_instance = new self();
26 | return self::$_instance;
27 | }
28 |
29 | /**
30 | * Prevent from creating more instances
31 | */
32 | private function __clone() { }
33 |
34 | /**
35 | * Prevent from creating more than one instance
36 | */
37 | private function __construct() {
38 | add_action( 'plugins_loaded' , array( &$this , 'plugins_loaded') );
39 | add_option( 'polylang_clone_attachments' , true );
40 | }
41 |
42 | /**
43 | * Setup plugin
44 | *
45 | * @action 'plugins_loaded'
46 | */
47 | function plugins_loaded() {
48 | load_plugin_textdomain( 'polylang-fix-relationships' , false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
49 | if ( is_admin() && class_exists( 'Polylang' ) && function_exists( 'PLL' ) ) {
50 | PolylangPostClonerAdmin::instance();
51 | PolylangPostClonerWatchMeta::instance();
52 | }
53 | }
54 | }
55 | endif;
56 |
57 |
58 | /**
59 | * Autoload Classes
60 | *
61 | * @param string $classname
62 | */
63 | function polylang_postcloner_autoload( $classname ) {
64 | $class_path = dirname(__FILE__). sprintf('/include/class-%s.php' , $classname ) ;
65 | if ( file_exists($class_path) )
66 | require_once $class_path;
67 | }
68 | spl_autoload_register( 'polylang_postcloner_autoload' );
69 |
70 | // init plugin
71 | PolylangPostCloner::instance();
--------------------------------------------------------------------------------
/languages/polylang-fix-relationships-de_DE.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcguffin/polylang-fix-relationships/d81a5eb43d843408e0e516291c6da819727116f7/languages/polylang-fix-relationships-de_DE.mo
--------------------------------------------------------------------------------
/languages/polylang-fix-relationships-de_DE.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Polylang Fix Relationships v0.0.1\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2015-05-23 13:49+0100\n"
6 | "PO-Revision-Date: 2015-05-23 13:49+0100\n"
7 | "Last-Translator: joern-lund \n"
8 | "Language-Team: \n"
9 | "Language: de_DE\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=n != 1;\n"
14 | "X-Generator: Poedit 1.8\n"
15 | "X-Poedit-SourceCharset: UTF-8\n"
16 | "X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
17 | "_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2\n"
18 | "X-Poedit-Basepath: ../\n"
19 | "X-Textdomain-Support: yes\n"
20 | "X-Poedit-SearchPath-0: .\n"
21 |
22 | # @ polylang-fix-relationships
23 | #: include/class-PolylangPostClonerAdmin.php:84
24 | msgid "Create Translation"
25 | msgid_plural "Create Translations"
26 | msgstr[0] "Übersetzung erstellen"
27 | msgstr[1] "Übersetzungen erstellen"
28 |
29 | # @ polylang-fix-relationships
30 | #: include/class-PolylangPostClonerAdmin.php:96
31 | msgid "Fix Relations"
32 | msgstr "Beziehungen reparieren"
33 |
34 | # @ polylang-fix-relationships
35 | #: include/class-PolylangPostClonerAdmin.php:210
36 | msgid "Source language is target language"
37 | msgstr "Ausgangssprache entspricht der Zielsprache"
38 |
39 | # @ polylang-fix-relationships
40 | #: include/class-PolylangPostClonerAdmin.php:247
41 | msgid "No such Language"
42 | msgstr "Keine solche Sprache"
43 |
--------------------------------------------------------------------------------