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

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