PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.2
Secure Custom Fields v6.4.2
6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / includes / revisions.php
secure-custom-fields / includes Last commit date
Blocks 1 year ago admin 1 year ago ajax 1 year ago api 1 year ago fields 1 year ago forms 1 year ago legacy 1 year ago locations 1 year ago post-types 1 year ago rest-api 1 year ago walkers 1 year ago acf-bidirectional-functions.php 1 year ago acf-field-functions.php 1 year ago acf-field-group-functions.php 1 year ago acf-form-functions.php 1 year ago acf-helper-functions.php 1 year ago acf-hook-functions.php 1 year ago acf-input-functions.php 1 year ago acf-internal-post-type-functions.php 1 year ago acf-meta-functions.php 1 year ago acf-post-functions.php 1 year ago acf-post-type-functions.php 1 year ago acf-taxonomy-functions.php 1 year ago acf-user-functions.php 1 year ago acf-utility-functions.php 1 year ago acf-value-functions.php 1 year ago acf-wp-functions.php 1 year ago assets.php 1 year ago blocks.php 1 year ago class-acf-data.php 1 year ago class-acf-internal-post-type.php 1 year ago class-acf-options-page.php 1 year ago class-acf-site-health.php 1 year ago compatibility.php 1 year ago deprecated.php 1 year ago fields.php 1 year ago index.php 1 year ago l10n.php 1 year ago local-fields.php 1 year ago local-json.php 1 year ago local-meta.php 1 year ago locations.php 1 year ago loop.php 1 year ago media.php 1 year ago rest-api.php 1 year ago revisions.php 1 year ago scf-ui-options-page-functions.php 1 year ago third-party.php 1 year ago upgrades.php 1 year ago validation.php 1 year ago wpml.php 1 year ago
revisions.php
465 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit; // Exit if accessed directly
5 }
6
7 if ( ! class_exists( 'acf_revisions' ) ) :
8 #[AllowDynamicProperties]
9 class acf_revisions {
10
11 // vars
12 var $cache = array();
13
14 /**
15 * Constructs the acf_revisions class.
16 */
17 public function __construct() {
18 add_action( 'wp_restore_post_revision', array( $this, 'wp_restore_post_revision' ), 10, 2 );
19 add_filter( '_wp_post_revision_fields', array( $this, 'wp_preview_post_fields' ), 10, 2 );
20 add_filter( '_wp_post_revision_fields', array( $this, 'wp_post_revision_fields' ), 10, 2 );
21 add_filter( 'acf/validate_post_id', array( $this, 'acf_validate_post_id' ), 10, 2 );
22
23 // WP 6.4+ handles things differently.
24 if ( version_compare( get_bloginfo( 'version' ), '6.4', '>=' ) ) {
25 add_action( '_wp_put_post_revision', array( $this, 'maybe_save_revision' ), 10, 2 );
26 add_filter( 'wp_save_post_revision_post_has_changed', array( $this, 'check_acf_fields_have_changed' ), 9, 3 );
27 add_filter( 'wp_post_revision_meta_keys', array( $this, 'wp_post_revision_meta_keys' ) );
28
29 $this->register_meta();
30 } else {
31 add_filter( 'wp_save_post_revision_check_for_changes', array( $this, 'wp_save_post_revision_check_for_changes' ), 10, 3 );
32 }
33 }
34
35 /**
36 * Registers any ACF meta that should be sent the REST/Gutenberg request.
37 * For now, this is just our "_acf_changed" key that we use to detect if ACF fields have changed.
38 *
39 * @since ACF 6.2.6
40 */
41 public function register_meta() {
42 register_meta(
43 'post',
44 '_acf_changed',
45 array(
46 'type' => 'boolean',
47 'single' => true,
48 'show_in_rest' => true,
49 'revisions_enabled' => true,
50 'auth_callback' => '__return_true',
51 )
52 );
53 }
54
55 /**
56 * Lets WordPress know which meta keys to include in revisions.
57 * For now, this is just our "_acf_changed" key, as we still handle revisions ourselves.
58 *
59 * @since ACF 6.2.6
60 *
61 * @param array $keys The meta keys that should be revisioned.
62 * @return array
63 */
64 public function wp_post_revision_meta_keys( $keys ) {
65 $keys[] = '_acf_changed';
66 return $keys;
67 }
68
69 /**
70 * Helps WordPress determine if fields have changed, and if in a legacy
71 * metabox AJAX request, copies the metadata to the new revision.
72 *
73 * @since ACF 6.2.6
74 *
75 * @param boolean $post_has_changed True if the post has changed, false if not.
76 * @param WP_Post $last_revision The WP_Post object for the latest revision.
77 * @param WP_Post $post The WP_Post object for the parent post.
78 * @return boolean
79 */
80 public function check_acf_fields_have_changed( $post_has_changed, $last_revision, $post ) {
81 if ( acf_maybe_get_GET( 'meta-box-loader', false ) ) {
82 // We're in a legacy AJAX request, so we copy fields over to the latest revision.
83 $this->maybe_save_revision( $last_revision->ID, $post->ID );
84 } elseif ( acf_maybe_get_POST( '_acf_changed', false ) ) {
85 // We're in a classic editor save request, so notify WP that fields have changed.
86 $post_has_changed = true;
87 }
88
89 // Let WordPress decide for REST/block editor requests.
90 return $post_has_changed;
91 }
92
93 /**
94 * Copies ACF field data to the latest revision.
95 *
96 * @since ACF 6.2.6
97 *
98 * @param integer $revision_id The ID of the revision that was just created.
99 * @param integer $post_id The ID of the post being updated.
100 * @return void
101 */
102 public function maybe_save_revision( $revision_id, $post_id ) {
103 // We don't have anything to copy over yet.
104 if ( ! did_action( 'acf/save_post' ) ) {
105 delete_metadata( 'post', $post_id, '_acf_changed' );
106 delete_metadata( 'post', $revision_id, '_acf_changed' );
107 return;
108 }
109
110 // Bail if this is an autosave in Classic Editor, it already has the field values.
111 if ( acf_maybe_get_POST( '_acf_changed' ) && wp_is_post_autosave( $revision_id ) ) {
112 return;
113 }
114
115 // Copy the saved meta from the main post to the latest revision.
116 acf_save_post_revision( $post_id );
117 }
118
119 /**
120 * This function is used to trick WP into thinking that one of the $post's fields has changed and
121 * will allow an autosave to be updated.
122 * Fixes an odd bug causing the preview page to render the non autosave post data on every odd attempt
123 *
124 * @type function
125 * @date 21/10/2014
126 * @since ACF 5.1.0
127 *
128 * @param $fields (array)
129 * @return $fields
130 */
131 function wp_preview_post_fields( $fields ) {
132
133 // bail early if not previewing a post
134 if ( acf_maybe_get_POST( 'wp-preview' ) !== 'dopreview' ) {
135 return $fields;
136 }
137
138 // add to fields if ACF has changed
139 if ( acf_maybe_get_POST( '_acf_changed' ) ) {
140 $fields['_acf_changed'] = 'different than 1';
141 }
142
143 // return
144 return $fields;
145 }
146
147
148 /**
149 * This filter will return false and force WP to save a revision. This is required due to
150 * WP checking only post_title, post_excerpt and post_content values, not custom fields.
151 *
152 * @type filter
153 * @date 19/09/13
154 *
155 * @param boolean $return defaults to true
156 * @param object $last_revision the last revision that WP will compare against
157 * @param object $post the $post object that WP will compare against
158 * @return boolean $return
159 */
160 function wp_save_post_revision_check_for_changes( $return, $last_revision, $post ) {
161
162 // if acf has changed, return false and prevent WP from performing 'compare' logic
163 if ( acf_maybe_get_POST( '_acf_changed' ) ) {
164 return false;
165 }
166
167 // return
168 return $return;
169 }
170
171
172 /**
173 * This filter will add the ACF fields to the returned array
174 * Versions 3.5 and 3.6 of WP feature different uses of the revisions filters, so there are
175 * some hacks to allow both versions to work correctly
176 *
177 * @type filter
178 * @date 11/08/13
179 *
180 * @param $post_id (int)
181 * @return $post_id (int)
182 */
183 function wp_post_revision_fields( $fields, $post = null ) {
184
185 // validate page
186 if ( acf_is_screen( 'revision' ) || acf_is_ajax( 'get-revision-diffs' ) ) {
187
188 // bail early if is restoring
189 if ( acf_maybe_get_GET( 'action' ) === 'restore' ) {
190 return $fields;
191 }
192
193 // allow
194 } else {
195
196 // bail early (most likely saving a post)
197 return $fields;
198 }
199
200 // vars
201 $append = array();
202 $order = array();
203 $post_id = acf_maybe_get( $post, 'ID' );
204
205 // compatibility with WP < 4.5 (test)
206 if ( ! $post_id ) {
207 global $post;
208 $post_id = $post->ID;
209 }
210
211 // get all postmeta
212 $meta = get_post_meta( $post_id );
213
214 // bail early if no meta
215 if ( ! $meta ) {
216 return $fields;
217 }
218
219 // loop
220 foreach ( $meta as $name => $value ) {
221
222 // attempt to find key value
223 $key = acf_maybe_get( $meta, '_' . $name );
224
225 // bail early if no key
226 if ( ! $key ) {
227 continue;
228 }
229
230 // update vars
231 $value = $value[0];
232 $key = $key[0];
233
234 // Load field.
235 $field = acf_get_field( $key );
236 if ( ! $field ) {
237 continue;
238 }
239
240 // get field
241 $field_title = $field['label'] . ' (' . $name . ')';
242 $field_order = $field['menu_order'];
243 $ancestors = acf_get_field_ancestors( $field );
244
245 // ancestors
246 if ( ! empty( $ancestors ) ) {
247
248 // vars
249 $count = count( $ancestors );
250 $oldest = acf_get_field( $ancestors[ $count - 1 ] );
251
252 // update vars
253 $field_title = str_repeat( '- ', $count ) . $field_title;
254 $field_order = $oldest['menu_order'] . '.1';
255 }
256
257 // append
258 $append[ $name ] = $field_title;
259 $order[ $name ] = $field_order;
260
261 // hook into specific revision field filter and return local value
262 add_filter( "_wp_post_revision_field_{$name}", array( $this, 'wp_post_revision_field' ), 10, 4 );
263 }
264
265 // append
266 if ( ! empty( $append ) ) {
267
268 // vars
269 $prefix = '_';
270
271 // add prefix
272 $append = acf_add_array_key_prefix( $append, $prefix );
273 $order = acf_add_array_key_prefix( $order, $prefix );
274
275 // sort by name (orders sub field values correctly)
276 array_multisort( $order, $append );
277
278 // remove prefix
279 $append = acf_remove_array_key_prefix( $append, $prefix );
280
281 // append
282 $fields = $fields + $append;
283 }
284
285 // return
286 return $fields;
287 }
288
289 /**
290 * Load the value for the given field and return it for rendering.
291 *
292 * @param mixed $value Should be false as it has not yet been loaded.
293 * @param string $field_name The name of the field
294 * @param mixed $post Holds the $post object to load from - in WP 3.5, this is not passed!
295 * @param string $direction To / from - not used.
296 * @return string $value
297 */
298 public function wp_post_revision_field( $value, $field_name, $post = null, $direction = false ) {
299 // Bail early if is empty.
300 if ( empty( $value ) ) {
301 return '';
302 }
303
304 $value = acf_maybe_unserialize( $value );
305 $post_id = $post->ID;
306
307 // load field.
308 $field = acf_maybe_get_field( $field_name, $post_id );
309
310 // default formatting.
311 if ( is_array( $value ) ) {
312 $value = implode( ', ', $value );
313 } elseif ( is_object( $value ) ) {
314 $value = serialize( $value );
315 }
316
317 // image.
318 if ( is_array( $field ) && isset( $field['type'] ) && ( $field['type'] === 'image' || $field['type'] === 'file' ) ) {
319 $url = wp_get_attachment_url( $value );
320 $value = $value . ' (' . $url . ')';
321 }
322
323 return $value;
324 }
325
326 /**
327 * This action will copy and paste the metadata from a revision to the post
328 *
329 * @type action
330 * @date 11/08/13
331 *
332 * @param $parent_id (int) the destination post
333 * @return $revision_id (int) the source post
334 */
335 function wp_restore_post_revision( $post_id, $revision_id ) {
336
337 // copy postmeta from revision to post (restore from revision)
338 acf_copy_postmeta( $revision_id, $post_id );
339
340 // Make sure the latest revision is also updated to match the new $post data
341 // get latest revision
342 $revision = acf_get_post_latest_revision( $post_id );
343
344 // save
345 if ( $revision ) {
346
347 // copy postmeta from revision to latest revision (potentialy may be the same, but most likely are different)
348 acf_copy_postmeta( $revision_id, $revision->ID );
349 }
350 }
351
352
353 /**
354 * This function will modify the $post_id and allow loading values from a revision
355 *
356 * @type function
357 * @date 6/3/17
358 * @since ACF 5.5.10
359 *
360 * @param $post_id (int)
361 * @param $_post_id (int)
362 * @return $post_id (int)
363 */
364 function acf_validate_post_id( $post_id, $_post_id ) {
365
366 // phpcs:disable WordPress.Security.NonceVerification.Recommended
367 // bail early if no preview in URL
368 if ( ! isset( $_GET['preview'] ) ) {
369 return $post_id;
370 }
371
372 // bail early if $post_id is not numeric
373 if ( ! is_numeric( $post_id ) ) {
374 return $post_id;
375 }
376
377 // vars
378 $k = $post_id;
379 $preview_id = 0;
380
381 // check cache
382 if ( isset( $this->cache[ $k ] ) ) {
383 return $this->cache[ $k ];
384 }
385
386 // validate
387 if ( isset( $_GET['preview_id'] ) ) {
388 $preview_id = (int) $_GET['preview_id'];
389 } elseif ( isset( $_GET['p'] ) ) {
390 $preview_id = (int) $_GET['p'];
391 } elseif ( isset( $_GET['page_id'] ) ) {
392 $preview_id = (int) $_GET['page_id'];
393 }
394 // phpcs:enable WordPress.Security.NonceVerification.Recommended
395
396 // bail early id $preview_id does not match $post_id
397 if ( $preview_id != $post_id ) {
398 return $post_id;
399 }
400
401 // attempt find revision
402 $revision = acf_get_post_latest_revision( $post_id );
403
404 // save
405 if ( $revision && $revision->post_parent == $post_id ) {
406 $post_id = (int) $revision->ID;
407 }
408
409 // set cache
410 $this->cache[ $k ] = $post_id;
411
412 // return
413 return $post_id;
414 }
415 }
416
417 // initialize
418 acf()->revisions = new acf_revisions();
419 endif; // class_exists check
420
421
422 /**
423 * This function will copy meta from a post to it's latest revision
424 *
425 * @type function
426 * @date 26/09/2016
427 * @since ACF 5.4.0
428 *
429 * @param $post_id (int)
430 * @return n/a
431 */
432 function acf_save_post_revision( $post_id = 0 ) {
433
434 // get latest revision
435 $revision = acf_get_post_latest_revision( $post_id );
436
437 // save
438 if ( $revision ) {
439 acf_copy_postmeta( $post_id, $revision->ID );
440 }
441 }
442
443
444 /**
445 * This function will return the latest revision for a given post
446 *
447 * @type function
448 * @date 25/06/2016
449 * @since ACF 5.3.8
450 *
451 * @param $post_id (int)
452 * @return $post_id (int)
453 */
454 function acf_get_post_latest_revision( $post_id ) {
455
456 // vars
457 $revisions = wp_get_post_revisions( $post_id );
458
459 // shift off and return first revision (will return null if no revisions)
460 $revision = array_shift( $revisions );
461
462 // return
463 return $revision;
464 }
465