PluginProbe
Polylang / 2.7
Polylang v2.7
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 / sync-metas.php

sync-metas.php in Polylang 2.7, at modules/sync/sync-metas.php

369 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Abstract class to manage the copy and synchronization of metas
5 *
6 * @since 2.3
7 */
8 abstract class PLL_Sync_Metas {
9 public $model;
10 protected $meta_type, $prev_value, $to_copy;
11
12 /**
13 * Constructor
14 *
15 * @since 2.3
16 *
17 * @param object $polylang
18 */
19 public function __construct( &$polylang ) {
20 $this->model = &$polylang->model;
21
22 add_filter( "add_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
23 add_filter( "update_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
24 add_filter( "delete_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
25
26 $this->add_all_meta_actions();
27
28 add_action( "pll_save_{$this->meta_type}", array( $this, 'save_object' ), 10, 3 );
29 }
30
31 /**
32 * Removes "added_{$this->meta_type}_meta" action
33 *
34 * @since 2.3
35 */
36 protected function remove_add_meta_action() {
37 remove_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 );
38 }
39
40 /**
41 * Removes all meta synchronization actions and filters
42 *
43 * @since 2.3
44 */
45 protected function remove_all_meta_actions() {
46 $this->remove_add_meta_action();
47
48 remove_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 );
49 remove_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 );
50
51 remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
52 remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
53 }
54
55 /**
56 * Adds "added_{$this->meta_type}_meta" action
57 *
58 * @since 2.3
59 */
60 protected function restore_add_meta_action() {
61 add_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 );
62 }
63
64 /**
65 * Adds meta synchronization actions and filters
66 *
67 * @since 2.3
68 */
69 protected function add_all_meta_actions() {
70 $this->restore_add_meta_action();
71
72 add_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 ); // Very late in case a filter prevents the meta to be updated
73 add_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 );
74
75 add_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
76 add_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
77 }
78
79 /**
80 * Maybe modify ("translate") a meta value when it is copied or synchronized
81 *
82 * @since 2.3
83 *
84 * @param mixed $value Meta value
85 * @param string $key Meta key
86 * @param int $from Id of the source
87 * @param int $to Id of the target
88 * @param string $lang Language of target
89 * @return mixed
90 */
91 protected function maybe_translate_value( $value, $key, $from, $to, $lang ) {
92 /**
93 * Filter a meta value before is copied or synchronized
94 *
95 * @since 2.3
96 *
97 * @param mixed $value Meta value
98 * @param string $key Meta key
99 * @param string $lang Language of target
100 * @param int $from Id of the source
101 * @param int $to Id of the target
102 */
103 return apply_filters( "pll_translate_{$this->meta_type}_meta", maybe_unserialize( $value ), $key, $lang, $from, $to );
104 }
105
106 /**
107 * Get the custom fields to copy or synchronize
108 *
109 * @since 2.3
110 *
111 * @param int $from Id of the post from which we copy informations
112 * @param int $to Id of the post to which we paste informations
113 * @param string $lang Language slug
114 * @param bool $sync True if it is synchronization, false if it is a copy
115 * @return array List of meta keys
116 */
117 protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) {
118 /**
119 * Filter the custom fields to copy or synchronize
120 *
121 * @since 0.6
122 * @since 1.9.2 The `$from`, `$to`, `$lang` parameters were added.
123 *
124 * @param array $keys List of custom fields names
125 * @param bool $sync True if it is synchronization, false if it is a copy
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 */
130 return array_unique( apply_filters( "pll_copy_{$this->meta_type}_metas", array(), $sync, $from, $to, $lang ) );
131 }
132
133 /**
134 * Disallow modifying synchronized meta if the current user can not modify translations
135 *
136 * @since 2.6
137 *
138 * @param null|bool $check Whether to allow adding/updating/deleting metadata.
139 * @param int $id Object ID.
140 * @param string $meta_key Meta key.
141 * @return null|bool
142 */
143 public function can_synchronize_metadata( $check, $id, $meta_key ) {
144 if ( ! $this->model->{$this->meta_type}->current_user_can_synchronize( $id ) ) {
145 $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
146
147 foreach ( $tr_ids as $lang => $tr_id ) {
148 if ( $tr_id != $id ) {
149 $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
150 if ( in_array( $meta_key, $to_copy ) ) {
151 return false;
152 }
153 }
154 }
155 }
156 return $check;
157 }
158
159 /**
160 * Synchronize added metas across translations
161 *
162 * @since 2.3
163 *
164 * @param int $mid Meta id.
165 * @param int $id Object ID.
166 * @param string $meta_key Meta key.
167 * @param mixed $meta_value Meta value. Must be serializable if non-scalar.
168 */
169 public function add_meta( $mid, $id, $meta_key, $meta_value ) {
170 static $avoid_recursion = false;
171
172 if ( ! $avoid_recursion ) {
173 $avoid_recursion = true;
174 $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
175
176 foreach ( $tr_ids as $lang => $tr_id ) {
177 if ( $tr_id != $id ) {
178 $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
179 if ( in_array( $meta_key, $to_copy ) ) {
180 $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
181 add_metadata( $this->meta_type, $tr_id, $meta_key, is_string( $meta_value ) ? wp_slash( $meta_value ) : $meta_value );
182 }
183 }
184 }
185
186 $avoid_recursion = false;
187 }
188 }
189
190 /**
191 * Stores the previous value when updating metas
192 *
193 * @since 2.3
194 *
195 * @param null|bool $r Not used
196 * @param int $id Object ID.
197 * @param string $meta_key Meta key.
198 * @param mixed $meta_value Meta value. Must be serializable if non-scalar.
199 * @param mixed $prev_value If specified, only update existing metadata entries with the specified value.
200 * @return null|bool Unchanged
201 */
202 public function update_metadata( $r, $id, $meta_key, $meta_value, $prev_value ) {
203 if ( null === $r ) {
204 $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) );
205 $this->prev_value[ $hash ] = $prev_value;
206 }
207 return $r;
208 }
209
210 /**
211 * Synchronize updated metas across translations
212 *
213 * @since 2.3
214 *
215 * @param int $mid Meta id.
216 * @param int $id Object ID.
217 * @param string $meta_key Meta key.
218 * @param mixed $meta_value Meta value. Must be serializable if non-scalar.
219 */
220 public function update_meta( $mid, $id, $meta_key, $meta_value ) {
221 static $avoid_recursion = false;
222
223 if ( ! $avoid_recursion ) {
224 $avoid_recursion = true;
225 $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) );
226
227 $prev_meta = get_metadata_by_mid( $this->meta_type, $mid );
228
229 if ( $prev_meta ) {
230 $this->remove_add_meta_action(); // We don't want to sync back the new metas
231 $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
232
233 foreach ( $tr_ids as $lang => $tr_id ) {
234 if ( $tr_id != $id && in_array( $meta_key, $this->get_metas_to_copy( $id, $tr_id, $lang, true ) ) ) {
235 if ( empty( $this->prev_value[ $hash ] ) || $this->prev_value[ $hash ] === $prev_meta->meta_value ) {
236 $prev_value = $this->maybe_translate_value( $prev_meta->meta_value, $meta_key, $id, $tr_id, $lang );
237 $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
238 update_metadata( $this->meta_type, $tr_id, $meta_key, is_string( $meta_value ) ? wp_slash( $meta_value ) : $meta_value, $prev_value );
239 }
240 }
241 }
242 $this->restore_add_meta_action();
243 }
244
245 unset( $this->prev_value[ $hash ] );
246 $avoid_recursion = false;
247 }
248 }
249
250 /**
251 * Store metas to synchronize before deleting them
252 *
253 * @since 2.3
254 *
255 * @param array $mids Not used
256 * @param int $id Object ID.
257 */
258 public function store_metas_to_sync( $mids, $id ) {
259 $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
260
261 foreach ( $tr_ids as $lang => $tr_id ) {
262 $this->to_copy[ $id ][ $tr_id ] = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
263 }
264 }
265
266 /**
267 * Synchronize deleted meta across translations
268 *
269 * @since 2.3
270 *
271 * @param array $mids Not used
272 * @param int $id Object ID.
273 * @param string $key Meta key.
274 * @param mixed $value Meta value.
275 */
276 public function delete_meta( $mids, $id, $key, $value ) {
277 static $avoid_recursion = false;
278
279 if ( ! $avoid_recursion ) {
280 $avoid_recursion = true;
281
282 $tr_ids = $this->model->{$this->meta_type}->get_translations( $id );
283
284 foreach ( $tr_ids as $lang => $tr_id ) {
285 if ( $tr_id != $id ) {
286 if ( in_array( $key, $this->to_copy[ $id ][ $tr_id ] ) ) {
287 if ( '' !== $value && null !== $value && false !== $value ) { // Same test as WP
288 $value = $this->maybe_translate_value( $value, $key, $id, $tr_id, $lang );
289 }
290 delete_metadata( $this->meta_type, $tr_id, $key, is_string( $value ) ? wp_slash( $value ) : $value );
291 }
292 }
293 }
294 }
295
296 $avoid_recursion = false;
297 }
298
299 /**
300 * Copy or synchronize metas
301 *
302 * @since 2.3
303 *
304 * @param int $from Id of the source object
305 * @param int $to Id of the target object
306 * @param string $lang Language code of the target object
307 * @param bool $sync Optional, defaults to true. True if it is synchronization, false if it is a copy
308 */
309 public function copy( $from, $to, $lang, $sync = false ) {
310 $this->remove_all_meta_actions();
311
312 remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
313 remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
314
315 $to_copy = $this->get_metas_to_copy( $from, $to, $lang, $sync );
316 $metas = get_metadata( $this->meta_type, $from );
317 $tr_metas = get_metadata( $this->meta_type, $to );
318
319 foreach ( $to_copy as $key ) {
320 if ( empty( $metas[ $key ] ) ) {
321 if ( ! empty( $tr_metas[ $key ] ) ) {
322 // If the meta key is not present in the source object, delete all values
323 delete_metadata( $this->meta_type, $to, $key );
324 }
325 } else {
326 if ( ! empty( $tr_metas[ $key ] ) && 1 === count( $metas[ $key ] ) && 1 === count( $tr_metas[ $key ] ) ) {
327 // One custom field to update
328 $value = reset( $metas[ $key ] );
329 $value = maybe_unserialize( $value );
330 $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
331 update_metadata( $this->meta_type, $to, $key, is_string( $to_value ) ? wp_slash( $to_value ) : $to_value );
332 } else {
333 // Multiple custom fields, either in the source or the target
334 if ( ! empty( $tr_metas[ $key ] ) ) {
335 // The synchronization of multiple values custom fields is easier if we delete all metas first
336 delete_metadata( $this->meta_type, $to, $key );
337 }
338
339 foreach ( $metas[ $key ] as $value ) {
340 $value = maybe_unserialize( $value );
341 $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
342 add_metadata( $this->meta_type, $to, $key, is_string( $to_value ) ? wp_slash( $to_value ) : $to_value );
343 }
344 }
345 }
346 }
347
348 $this->add_all_meta_actions();
349 }
350
351 /**
352 * If synchronized custom fields were previously not synchronized, it is expected
353 * that saving a post (or term) will synchronize them.
354 *
355 * @since 2.3
356 *
357 * @param int $object_id Id of the object being asaved
358 * @param object $obj Not used
359 * @param array $translations The list of translations object ids
360 */
361 public function save_object( $object_id, $obj, $translations ) {
362 foreach ( $translations as $tr_lang => $tr_id ) {
363 if ( $tr_id != $object_id ) {
364 $this->copy( $object_id, $tr_id, $tr_lang, true );
365 }
366 }
367 }
368 }
369