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