PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.8
Advanced Custom Fields (ACF®) v6.8.8
6.8.9 6.8.8 6.8.7 6.8.6 6.8.5 6.8.4 6.8.3 6.8.2 6.8.1 5.8.5 5.8.6 5.8.7 5.8.8 5.8.9 5.9.0 5.9.1 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 5.9.9 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.6.1 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10.2 6.3.11 6.3.12 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.6.1 6.4.0 6.4.0.1 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.8.0 trunk 1.0.0 1.0.2 1.0.3 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.1 2.1.3 2.1.4 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.6 3.0.7 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.8 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.8 4.3.9 4.4.0 4.4.1 4.4.10 4.4.11 4.4.12 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 5.10 5.10.1 5.10.2 5.11 5.11.1 5.11.2 5.11.3 5.11.4 5.12 5.12.1 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.6.10 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 5.6.7 5.6.8 5.6.9 5.7.0 5.7.1 5.7.10 5.7.12 5.7.13 5.7.2 5.7.3 5.7.4 5.7.5 5.7.6 5.7.7 5.7.8 5.7.9 5.8.0 5.8.1 5.8.10 5.8.11 5.8.12 5.8.13 5.8.14 5.8.2 5.8.3 5.8.4
advanced-custom-fields / includes / revisions.php
advanced-custom-fields / includes Last commit date
admin 1 month ago ajax 3 months ago api 2 weeks ago fields 4 weeks ago forms 4 weeks ago legacy 5 months ago locations 1 month ago post-types 5 months ago rest-api 4 weeks ago walkers 5 months ago acf-bidirectional-functions.php 5 months ago acf-field-functions.php 5 months ago acf-field-group-functions.php 5 months ago acf-form-functions.php 5 months ago acf-helper-functions.php 5 months ago acf-hook-functions.php 5 months ago acf-input-functions.php 5 months ago acf-internal-post-type-functions.php 5 months ago acf-meta-functions.php 2 months ago acf-post-functions.php 5 months ago acf-post-type-functions.php 5 months ago acf-taxonomy-functions.php 5 months ago acf-user-functions.php 5 months ago acf-utility-functions.php 5 months ago acf-value-functions.php 5 months ago acf-wp-functions.php 5 months ago assets.php 5 months ago class-acf-data.php 5 months ago class-acf-internal-post-type.php 5 months ago compatibility.php 5 months ago deprecated.php 5 months ago fields.php 5 months ago index.php 2 years ago l10n.php 5 months ago local-fields.php 5 months ago local-json.php 3 months ago local-meta.php 5 months ago locations.php 5 months ago loop.php 5 months ago media.php 5 months ago rest-api.php 5 months ago revisions.php 3 months ago third-party.php 5 months ago upgrades.php 2 months ago validation.php 5 months ago wpml.php 5 months ago
revisions.php
484 lines
1 <?php
2 /**
3 * @package ACF
4 * @author WP Engine
5 *
6 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
7 * "ACF" is a trademark of WP Engine.
8 * Licensed under the GNU General Public License v2 or later.
9 * https://www.gnu.org/licenses/gpl-2.0.html
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly
14 }
15
16 if ( ! class_exists( 'acf_revisions' ) ) :
17 class acf_revisions {
18
19 /**
20 * An array to cache post IDs for revisions.
21 * @var array
22 */
23 public $cache = array();
24
25 /**
26 * Constructs the acf_revisions class.
27 */
28 public function __construct() {
29 add_action( 'wp_restore_post_revision', array( $this, 'wp_restore_post_revision' ), 10, 2 );
30 add_filter( '_wp_post_revision_fields', array( $this, 'wp_preview_post_fields' ), 10, 2 );
31 add_filter( '_wp_post_revision_fields', array( $this, 'wp_post_revision_fields' ), 10, 2 );
32 add_filter( 'acf/validate_post_id', array( $this, 'acf_validate_post_id' ), 10, 2 );
33
34 // WP 6.4+ handles things differently.
35 if ( version_compare( get_bloginfo( 'version' ), '6.4', '>=' ) ) {
36 add_action( '_wp_put_post_revision', array( $this, 'maybe_save_revision' ), 10, 2 );
37 add_filter( 'wp_save_post_revision_post_has_changed', array( $this, 'check_acf_fields_have_changed' ), 9, 3 );
38 add_filter( 'wp_post_revision_meta_keys', array( $this, 'wp_post_revision_meta_keys' ) );
39
40 $this->register_meta();
41 } else {
42 add_filter( 'wp_save_post_revision_check_for_changes', array( $this, 'wp_save_post_revision_check_for_changes' ), 10, 3 );
43 }
44 }
45
46 /**
47 * Registers any ACF meta that should be sent the REST/Gutenberg request.
48 * For now, this is just our "_acf_changed" key that we use to detect if ACF fields have changed.
49 *
50 * @since 6.2.6
51 */
52 public function register_meta() {
53 register_meta(
54 'post',
55 '_acf_changed',
56 array(
57 'type' => 'boolean',
58 'single' => true,
59 'show_in_rest' => true,
60 'revisions_enabled' => true,
61 'auth_callback' => '__return_true',
62 )
63 );
64 }
65
66 /**
67 * Lets WordPress know which meta keys to include in revisions.
68 * For now, this is just our "_acf_changed" key, as we still handle revisions ourselves.
69 *
70 * @since 6.2.6
71 *
72 * @param array $keys The meta keys that should be revisioned.
73 * @return array
74 */
75 public function wp_post_revision_meta_keys( $keys ) {
76 $keys[] = '_acf_changed';
77 return $keys;
78 }
79
80 /**
81 * Helps WordPress determine if fields have changed, and if in a legacy
82 * metabox AJAX request, copies the metadata to the new revision.
83 *
84 * @since 6.2.6
85 *
86 * @param boolean $post_has_changed True if the post has changed, false if not.
87 * @param WP_Post $last_revision The WP_Post object for the latest revision.
88 * @param WP_Post $post The WP_Post object for the parent post.
89 * @return boolean
90 */
91 public function check_acf_fields_have_changed( $post_has_changed, $last_revision, $post ) {
92 if ( apply_filters( 'acf/revisions/skip_legacy_metabox_handling', false ) ) {
93 return $post_has_changed;
94 }
95
96 if ( acf_maybe_get_GET( 'meta-box-loader', false ) ) {
97 // We're in a legacy AJAX request, so we copy fields over to the latest revision.
98 $this->maybe_save_revision( $last_revision->ID, $post->ID );
99 } elseif ( acf_maybe_get_POST( '_acf_changed', false ) ) {
100 // We're in a classic editor save request, so notify WP that fields have changed.
101 $post_has_changed = true;
102 }
103
104 // Let WordPress decide for REST/block editor requests.
105 return $post_has_changed;
106 }
107
108 /**
109 * Copies ACF field data to the latest revision.
110 *
111 * @since 6.2.6
112 *
113 * @param integer $revision_id The ID of the revision that was just created.
114 * @param integer $post_id The ID of the post being updated.
115 * @return void
116 */
117 public function maybe_save_revision( $revision_id, $post_id ) {
118 if ( apply_filters( 'acf/revisions/skip_legacy_metabox_handling', false ) ) {
119 return;
120 }
121
122 // We don't have anything to copy over yet.
123 if ( ! did_action( 'acf/save_post' ) ) {
124 delete_metadata( 'post', $post_id, '_acf_changed' );
125 delete_metadata( 'post', $revision_id, '_acf_changed' );
126 return;
127 }
128
129 // Bail if this is an autosave in Classic Editor, it already has the field values.
130 if ( acf_maybe_get_POST( '_acf_changed' ) && wp_is_post_autosave( $revision_id ) ) {
131 return;
132 }
133
134 // Copy the saved meta from the main post to the latest revision.
135 acf_save_post_revision( $post_id );
136 }
137
138 /**
139 * This function is used to trick WP into thinking that one of the $post's fields has changed and
140 * will allow an autosave to be updated.
141 * Fixes an odd bug causing the preview page to render the non autosave post data on every odd attempt
142 *
143 * @type function
144 * @date 21/10/2014
145 * @since 5.1.0
146 *
147 * @param $fields (array)
148 * @return $fields
149 */
150 function wp_preview_post_fields( $fields ) {
151
152 // bail early if not previewing a post
153 if ( acf_maybe_get_POST( 'wp-preview' ) !== 'dopreview' ) {
154 return $fields;
155 }
156
157 // add to fields if ACF has changed
158 if ( acf_maybe_get_POST( '_acf_changed' ) ) {
159 $fields['_acf_changed'] = 'different than 1';
160 }
161
162 // return
163 return $fields;
164 }
165
166
167 /**
168 * This filter will return false and force WP to save a revision. This is required due to
169 * WP checking only post_title, post_excerpt and post_content values, not custom fields.
170 *
171 * @type filter
172 * @date 19/09/13
173 *
174 * @param boolean $return defaults to true
175 * @param object $last_revision the last revision that WP will compare against
176 * @param object $post the $post object that WP will compare against
177 * @return boolean $return
178 */
179 function wp_save_post_revision_check_for_changes( $return, $last_revision, $post ) {
180
181 // if acf has changed, return false and prevent WP from performing 'compare' logic
182 if ( acf_maybe_get_POST( '_acf_changed' ) ) {
183 return false;
184 }
185
186 // return
187 return $return;
188 }
189
190
191 /**
192 * This filter will add the ACF fields to the returned array
193 * Versions 3.5 and 3.6 of WP feature different uses of the revisions filters, so there are
194 * some hacks to allow both versions to work correctly
195 *
196 * @type filter
197 * @date 11/08/13
198 *
199 * @param $post_id (int)
200 * @return $post_id (int)
201 */
202 function wp_post_revision_fields( $fields, $post = null ) {
203
204 // validate page
205 if ( acf_is_screen( 'revision' ) || acf_is_ajax( 'get-revision-diffs' ) ) {
206
207 // bail early if is restoring
208 if ( acf_maybe_get_GET( 'action' ) === 'restore' ) {
209 return $fields;
210 }
211
212 // allow
213 } else {
214
215 // bail early (most likely saving a post)
216 return $fields;
217 }
218
219 // vars
220 $append = array();
221 $order = array();
222 $post_id = acf_maybe_get( $post, 'ID' );
223
224 // compatibility with WP < 4.5 (test)
225 if ( ! $post_id ) {
226 global $post;
227 $post_id = $post->ID;
228 }
229
230 // get all postmeta
231 $meta = get_post_meta( $post_id );
232
233 // bail early if no meta
234 if ( ! $meta ) {
235 return $fields;
236 }
237
238 // loop
239 foreach ( $meta as $name => $value ) {
240
241 // attempt to find key value
242 $key = acf_maybe_get( $meta, '_' . $name );
243
244 // bail early if no key
245 if ( ! $key ) {
246 continue;
247 }
248
249 // update vars
250 $value = $value[0];
251 $key = $key[0];
252
253 // Load field.
254 $field = acf_get_field( $key );
255 if ( ! $field ) {
256 continue;
257 }
258
259 // get field
260 $field_title = $field['label'] . ' (' . $name . ')';
261 $field_order = $field['menu_order'];
262 $ancestors = acf_get_field_ancestors( $field );
263
264 // ancestors
265 if ( ! empty( $ancestors ) ) {
266
267 // vars
268 $count = count( $ancestors );
269 $oldest = acf_get_field( $ancestors[ $count - 1 ] );
270
271 // update vars
272 $field_title = str_repeat( '- ', $count ) . $field_title;
273 $field_order = $oldest['menu_order'] . '.1';
274 }
275
276 // append
277 $append[ $name ] = $field_title;
278 $order[ $name ] = $field_order;
279
280 // hook into specific revision field filter and return local value
281 add_filter( "_wp_post_revision_field_{$name}", array( $this, 'wp_post_revision_field' ), 10, 4 );
282 }
283
284 // append
285 if ( ! empty( $append ) ) {
286
287 // vars
288 $prefix = '_';
289
290 // add prefix
291 $append = acf_add_array_key_prefix( $append, $prefix );
292 $order = acf_add_array_key_prefix( $order, $prefix );
293
294 // sort by name (orders sub field values correctly)
295 array_multisort( $order, $append );
296
297 // remove prefix
298 $append = acf_remove_array_key_prefix( $append, $prefix );
299
300 // append
301 $fields = $fields + $append;
302 }
303
304 // return
305 return $fields;
306 }
307
308 /**
309 * Load the value for the given field and return it for rendering.
310 *
311 * @param mixed $value Should be false as it has not yet been loaded.
312 * @param string $field_name The name of the field
313 * @param mixed $post Holds the $post object to load from - in WP 3.5, this is not passed!
314 * @param string $direction To / from - not used.
315 * @return string $value
316 */
317 public function wp_post_revision_field( $value, $field_name, $post = null, $direction = false ) {
318 // Bail early if is empty.
319 if ( empty( $value ) ) {
320 return '';
321 }
322
323 $value = acf_maybe_unserialize( $value );
324 $post_id = $post->ID;
325
326 // load field.
327 $field = acf_maybe_get_field( $field_name, $post_id );
328
329 // default formatting.
330 if ( is_array( $value ) ) {
331 $value = implode( ', ', $value );
332 } elseif ( is_object( $value ) ) {
333 $value = serialize( $value );
334 }
335
336 // image.
337 if ( is_array( $field ) && isset( $field['type'] ) && ( $field['type'] === 'image' || $field['type'] === 'file' ) ) {
338 $url = wp_get_attachment_url( $value );
339 $value = $value . ' (' . $url . ')';
340 }
341
342 return $value;
343 }
344
345 /**
346 * This action will copy and paste the metadata from a revision to the post
347 *
348 * @type action
349 * @date 11/08/13
350 *
351 * @param $parent_id (int) the destination post
352 * @return $revision_id (int) the source post
353 */
354 function wp_restore_post_revision( $post_id, $revision_id ) {
355
356 // copy postmeta from revision to post (restore from revision)
357 acf_copy_postmeta( $revision_id, $post_id );
358
359 // Make sure the latest revision is also updated to match the new $post data
360 // get latest revision
361 $revision = acf_get_post_latest_revision( $post_id );
362
363 // save
364 if ( $revision ) {
365
366 // copy postmeta from revision to latest revision (potentialy may be the same, but most likely are different)
367 acf_copy_postmeta( $revision_id, $revision->ID );
368 }
369 }
370
371
372 /**
373 * This function will modify the $post_id and allow loading values from a revision
374 *
375 * @type function
376 * @date 6/3/17
377 * @since 5.5.10
378 *
379 * @param $post_id (int)
380 * @param $_post_id (int)
381 * @return $post_id (int)
382 */
383 function acf_validate_post_id( $post_id, $_post_id ) {
384
385 // phpcs:disable WordPress.Security.NonceVerification.Recommended
386 // bail early if no preview in URL
387 if ( ! isset( $_GET['preview'] ) ) {
388 return $post_id;
389 }
390
391 // bail early if $post_id is not numeric
392 if ( ! is_numeric( $post_id ) ) {
393 return $post_id;
394 }
395
396 // vars
397 $k = $post_id;
398 $preview_id = 0;
399
400 // check cache
401 if ( isset( $this->cache[ $k ] ) ) {
402 return $this->cache[ $k ];
403 }
404
405 // validate
406 if ( isset( $_GET['preview_id'] ) ) {
407 $preview_id = (int) $_GET['preview_id'];
408 } elseif ( isset( $_GET['p'] ) ) {
409 $preview_id = (int) $_GET['p'];
410 } elseif ( isset( $_GET['page_id'] ) ) {
411 $preview_id = (int) $_GET['page_id'];
412 }
413 // phpcs:enable WordPress.Security.NonceVerification.Recommended
414
415 // bail early id $preview_id does not match $post_id
416 if ( $preview_id != $post_id ) {
417 return $post_id;
418 }
419
420 // attempt find revision
421 $revision = acf_get_post_latest_revision( $post_id );
422
423 // save
424 if ( $revision && $revision->post_parent == $post_id ) {
425 $post_id = (int) $revision->ID;
426 }
427
428 // set cache
429 $this->cache[ $k ] = $post_id;
430
431 // return
432 return $post_id;
433 }
434 }
435
436 // initialize
437 acf()->revisions = new acf_revisions();
438 endif; // class_exists check
439
440
441 /**
442 * This function will copy meta from a post to it's latest revision
443 *
444 * @type function
445 * @date 26/09/2016
446 * @since 5.4.0
447 *
448 * @param $post_id (int)
449 * @return n/a
450 */
451 function acf_save_post_revision( $post_id = 0 ) {
452
453 // get latest revision
454 $revision = acf_get_post_latest_revision( $post_id );
455
456 // save
457 if ( $revision ) {
458 acf_copy_postmeta( $post_id, $revision->ID );
459 }
460 }
461
462
463 /**
464 * This function will return the latest revision for a given post
465 *
466 * @type function
467 * @date 25/06/2016
468 * @since 5.3.8
469 *
470 * @param $post_id (int)
471 * @return $post_id (int)
472 */
473 function acf_get_post_latest_revision( $post_id ) {
474
475 // vars
476 $revisions = wp_get_post_revisions( $post_id );
477
478 // shift off and return first revision (will return null if no revisions)
479 $revision = array_shift( $revisions );
480
481 // return
482 return $revision;
483 }
484