|
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
|
|
|
|