Completed
Push — add/autosave-revisions-module ( bd8d9c )
by
unknown
07:15
created

autosave-revisions.php ➔ jetpack_post_has_changed_since_last_revision()   B

Complexity

Conditions 8
Paths 16

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 16
nop 2
dl 0
loc 36
rs 8.0995
c 0
b 0
f 0
1
<?php
2
/**
3
 * Autosave Revisions
4
 * More reliable autosaves that save a permanent revision before large changes
5
 */
6
7
/**
8
 * Compare the proposed update with the last stored revision verifying that
9
 * they are different, unless a plugin tells us to always save regardless.
10
 *
11
 * TODO: this function is extracted from Core's wp_save_post_revision function. Submit it as
12
 *       a Core patch and then use it here after it's merged.
13
 *
14
 * @param int $post_id The ID of the post to save as a revision.
15
 * @param WP_Post $post The proposed post update.
16
 * @return bool Whether the proposed update is different from the last saved revision.
17
 */
18
function jetpack_post_has_changed_since_last_revision( $post_id, $post ) {
19
	$revisions = wp_get_post_revisions( $post_id );
20
	// If no previous revisions, save one
21
	if ( ! $revisions ) {
22
		return true;
23
	}
24
25
	// grab the last revision, but not an autosave
26
	foreach ( $revisions as $revision ) {
27
		if ( false !== strpos( $revision->post_name, "{$revision->post_parent}-revision" ) ) {
28
			$last_revision = $revision;
29
			break;
30
		}
31
	}
32
33
	if ( ! isset( $last_revision ) ) {
34
		return true;
35
	}
36
37
	if ( ! apply_filters( 'wp_save_post_revision_check_for_changes', $check_for_changes = true, $last_revision, $post ) ) {
38
		return true;
39
	}
40
41
	$post_has_changed = false;
42
43
	foreach ( array_keys( _wp_post_revision_fields( $post ) ) as $field ) {
44
		if ( normalize_whitespace( $post->$field ) != normalize_whitespace( $last_revision->$field ) ) {
45
			$post_has_changed = true;
46
			break;
47
		}
48
	}
49
50
	$post_has_changed = (bool) apply_filters( 'wp_save_post_revision_post_has_changed', $post_has_changed, $last_revision, $post );
51
52
	return $post_has_changed;
53
}
54
55
/**
56
 * Determine if the two versions (autosave revisions) of a post are different enough to warrant
57
 * saving the old autosave as a separate post revision.
58
 */
59
function jetpack_is_big_edit( $post_before, $post_after ) {
60
	// TODO: make the criteria more reasonable, maybe even make a text diff and look at its +-.
61
	$before_len = strlen( $post_before->post_content );
62
	$after_len = strlen( $post_after->post_content );
63
	$size_diff = absint( $after_len - $before_len );
64
	// depends on size: starts at 50 chars (approx one line) for smallest posts, and ends up
65
	// being at least 250 chars for 1000 chars posts and bigger.
66
	$size_threshold = 50 + min( $before_len, 1000 ) / 5;
67
	return $size_diff > $size_threshold;
68
}
69
70
/**
71
 * When a post is autosaved, we don't create a post revision for that save. On multiple
72
 * consecutive autosaves, we overwrite the old autosave (i.e., the post itself in case of drafts or
73
 * an autosave revision in case of published posts) and its content is lost forever.
74
 *
75
 * This function will compare the old and new autosave, determine if they are significantly
76
 * from each other and if they are, saves the old autosave as a separate post revision.
77
 * This prevents losing valuable unsaved content in case an autosave goes awry, e.g., it empties
78
 * the post content due to an editor bug or unwanted edit.
79
 */
80
function jetpack_create_autosave_revision( $post_ID, $post_after, $post_before ) {
81
	// we are only interested in post changes done during autosave
82
	if ( ! defined( 'DOING_AUTOSAVE' ) || ! DOING_AUTOSAVE ) {
83
		return;
84
	}
85
86
	// get the actual post whose revision is to be saved: we might need to reach out for the parent
87
	// post if the update is for autosave revision. It's simply the $post_before for draft autosave.
88
	$revision_post = $post_before;
89
90
	if ( $post_before->post_type === 'revision' ) {
91
		if ( strpos( $post_before->post_name, "{$post_before->post_parent}-autosave" ) === false ) {
92
			// update of a non-autosave revision: we're not interested in this kind of update
93
			return;
94
		}
95
96
		// it's an update of autosave revision, retrieve the parent post
97
		$revision_post = get_post( $post_before->post_parent );
98
	}
99
100
	// bail out if the post type doesn't support revisions
101
	if ( ! wp_revisions_enabled( $revision_post ) ) {
102
		return;
103
	}
104
105
	// the autosave revision can either have fresh content, if it's newer than the saved post, or
106
	// be stale: we don't delete the autosave revision when saving the post. We'll reuse it later
107
	// on the next autosave instead of creating a new one.
108
	// If the autosave is indeed fresh, update the parent post with its content and timestamp before
109
	// saving it as revision.
110
	if ( $post_before->post_modified > $revision_post->post_modified) {
111
		foreach ( array_keys( _wp_post_revision_fields( $revision_post ) ) as $field ) {
112
			$revision_post->$field = $post_before->$field;
113
		}
114
		$revision_post->post_modified = $post_before->post_modified;
115
		$revision_post->post_modified_gmt = $post_before->post_modified_gmt;
116
	}
117
118
	// don't save a revision if it would be identical to the last saved revision
119
	if ( ! jetpack_post_has_changed_since_last_revision( $revision_post->ID, $revision_post ) ) {
120
		return;
121
	}
122
123
	// we'll save a post revision only if the difference between the old and new autosave is big.
124
	// then the old autosave is worth preserving: it would be overwritten and lost otherwise.
125
	if ( ! jetpack_is_big_edit( $revision_post, $post_after ) ) {
126
		return;
127
	}
128
129
	_wp_put_post_revision( $revision_post );
130
131
	// record stats about count and size of created autosave revisions
132
	$revision_content_length = strlen( $revision_post->post_content );
133
	do_action( 'jetpack_stats_statsd', 'autosave_revision', "1|c" );
134
	do_action( 'jetpack_stats_statsd', 'autosave_revision', "{$revision_content_length}|g" );
135
}
136
137
add_action( 'post_updated', 'jetpack_create_autosave_revision', 10, 3 );
138