PluginProbe
Polylang / 2.8
Polylang v2.8
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.8, at modules/sync/sync-metas.php

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