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