Revisions_Service   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 302
rs 3.36
c 0
b 0
f 0
wmc 63
lcom 1
cbo 4

18 Methods

Rating   Name   Duplication   Size   Complexity  
A check_for_changes() 0 7 2
B update_post_id_on_preview() 0 27 8
A enabled() 0 8 1
A disabled() 0 8 1
A maybe_copy_meta_to_revision() 0 12 4
A maybe_save_revision() 0 14 4
C add_fields_to_revision() 0 38 13
A update_revision_field_value() 0 11 3
A restore_post_revision() 0 8 2
A get_revisioned_fields() 0 17 3
A get_latest_post_revision() 0 6 1
A get_preview_id() 0 15 4
A copy_meta() 0 26 4
A meta_key_matches_names() 0 9 3
A filter_meta_by_keys() 0 12 3
A delete_old_meta() 0 14 2
A insert_new_meta() 0 25 4
A get_keys_for_mysql() 0 12 1

How to fix   Complexity   

Complex Class

Complex classes like Revisions_Service often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Revisions_Service, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Carbon_Fields\Service;
4
5
use Carbon_Fields\Carbon_Fields;
6
7
class Revisions_Service extends Service {
8
	const CHANGE_KEY = 'carbon_fields_changed';
9
10
	protected function enabled() {
11
		add_filter( 'carbon_get_post_meta_post_id', array( $this, 'update_post_id_on_preview' ), 10, 3 );
12
		add_action( 'carbon_fields_post_meta_container_saved', array( $this, 'maybe_copy_meta_to_revision' ), 10, 2 );
13
		add_filter('_wp_post_revision_fields', array( $this, 'maybe_save_revision' ), 10, 2 );
14
		add_filter('_wp_post_revision_fields', array( $this, 'add_fields_to_revision' ), 10, 2 );
15
		add_action( 'wp_restore_post_revision', array( $this, 'restore_post_revision' ), 10, 2 );
16
		add_filter( 'wp_save_post_revision_check_for_changes', array( $this, 'check_for_changes' ), 10, 3 );
17
	}
18
19
	protected function disabled() {
20
		remove_filter( 'carbon_get_post_meta_post_id', array( $this, 'update_post_id_on_preview' ), 10 );
21
		remove_action( 'carbon_fields_post_meta_container_saved', array( $this, 'maybe_copy_meta_to_revision' ), 10 );
22
		remove_filter('_wp_post_revision_fields', array( $this, 'maybe_save_revision' ), 10 );
23
		remove_filter('_wp_post_revision_fields', array( $this, 'add_fields_to_revision' ), 10 );
24
		remove_action( 'wp_restore_post_revision', array( $this, 'restore_post_revision' ), 10 );
25
		remove_filter( 'wp_save_post_revision_check_for_changes', array( $this, 'check_for_changes' ), 10 );
26
	}
27
28
	public function check_for_changes( $return, $last_revision, $post ) {
0 ignored issues
show
Unused Code introduced by
The parameter $last_revision is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $post is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
29
		if ( empty( $_POST[ self::CHANGE_KEY ] ) ) {
30
			return $return;
31
		}
32
33
		return false;
34
	}
35
36
	public function update_post_id_on_preview( $id, $name, $container_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $container_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
37
		if ( empty( $_GET['preview'] ) ) {
38
			return $id;
39
		}
40
41
		$post_type = get_post_type( $id );
42
		if ( $post_type === 'revision' ) {
43
			return $id;
44
		}
45
46
		$preview_id = $this->get_preview_id();
47
		if ( ! $preview_id || $preview_id !== intval( $id ) ) {
48
			return $id;
49
		}
50
51
		$nonce = ( ! empty( $_GET['preview_nonce'] ) ) ? $_GET['preview_nonce'] : '';
52
		if ( ! wp_verify_nonce( $nonce, 'post_preview_' . $preview_id ) ) {
53
			return $id;
54
		}
55
56
		$revision = $this->get_latest_post_revision( $id );
57
		if ( empty( $revision ) ) {
58
			return $id;
59
		}
60
61
		return $revision->ID;
62
	}
63
64
	/**
65
	 * @param int $post_id
66
	 * @param \Carbon_Fields\Container\Post_Meta_Container $container
67
	 */
68
	public function maybe_copy_meta_to_revision( $post_id, $container ) {
69
		if ( ! $container || $container->get_revisions_disabled() ) {
70
			return;
71
		}
72
73
		$revision = $this->get_latest_post_revision( $post_id );
74
		if ( empty( $revision ) ) {
75
			return;
76
		}
77
78
		$this->copy_meta( $post_id, $revision->ID );
79
	}
80
81
	public function maybe_save_revision( $fields, $post = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $post is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
82
		$wp_preview = ( ! empty( $_POST['wp-preview'] ) ) ? $_POST['wp-preview'] : '';
83
		$in_preview = $wp_preview === 'dopreview';
84
85
		if ( ! $in_preview ) {
86
			return $fields;
87
		}
88
89
		if ( ! empty( $_POST[ self::CHANGE_KEY ] ) ) {
90
			$fields[ self::CHANGE_KEY ] = 2;
91
		}
92
93
		return $fields;
94
	}
95
96
	public function add_fields_to_revision( $fields, $post = null ) {
97
		// When Gutenberg is saving the post
98
		// this function isn't defined yet.
99
		// Also we don't need it at all because the metaboxes
100
		// are saved by separate request.
101
		if ( ! function_exists( 'get_current_screen' ) ) {
102
			return $fields;
103
		}
104
105
		$current_screen = get_current_screen();
106
107
		$is_revision_screen = $current_screen && $current_screen->id === 'revision';
108
		$is_doing_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
109
		$is_revision_action = ! empty( $_POST['action'] ) && $_POST['action'] === 'get-revision-diffs';
110
		$is_revision_diff_ajax = $is_doing_ajax && $is_revision_action;
111
		$is_restoring = ! empty( $_GET['action'] ) && $_GET['action'] === 'restore';
112
113
		// exit early if not on the revision screens or if a restore revision is in progress
114
		if ( $is_restoring || ( ! $is_revision_screen && ! $is_revision_diff_ajax ) ) {
115
			return $fields;
116
		}
117
118
		if ( $post && ! empty( $post['ID'] ) ) {
119
			$post_id = $post['ID'];
0 ignored issues
show
Unused Code introduced by
$post_id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
120
		} else {
121
			global $post;
122
			$post_id = $post->ID;
0 ignored issues
show
Unused Code introduced by
$post_id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
123
		}
124
125
		$revisioned_fields = $this->get_revisioned_fields();
126
		$fields = array_merge( $fields, $revisioned_fields );
127
		// this hook is used when displaying the field values
128
		foreach ( $revisioned_fields as $name => $label ) {
129
			add_filter( "_wp_post_revision_field_{$name}", array( $this, 'update_revision_field_value' ), 10, 4 );
130
		}
131
132
		return $fields;
133
	}
134
135
	/**
136
	 * @param mixed $value
137
	 * @param string $field_name
138
	 * @param \WP_Post $post
139
	 * @param bool $direction
140
	 *
141
	 * @return int|mixed
142
	 */
143
	public function update_revision_field_value( $value, $field_name, $post = null, $direction = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $direction is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
144
		if ( empty( $post ) ) {
145
			return $value;
146
		}
147
148
		$value = carbon_get_post_meta( $post->ID, substr( $field_name, 1 ) );
149
		if ( is_array( $value ) ) {
150
			$value = count( $value );
151
		}
152
		return $value;
153
	}
154
155
	public function restore_post_revision( $post_id, $revision_id ) {
156
		$this->copy_meta( $revision_id, $post_id );
157
158
		$revision = $this->get_latest_post_revision( $post_id );
159
		if ( $revision ) {
160
			$this->copy_meta( $revision_id, $revision->ID );
161
		}
162
	}
163
164
	protected function get_revisioned_fields() {
165
		$repository = Carbon_Fields::resolve( 'container_repository' );
166
		$containers = $repository->get_containers( 'post_meta' );
167
		$containers = array_filter( $containers, function( $container ) {
168
			/** @var \Carbon_Fields\Container\Post_Meta_Container $container */
169
			return !$container->get_revisions_disabled();
170
		} );
171
		$fields = array();
172
		foreach ( $containers as $container ) {
173
			/** @var \Carbon_Fields\Container\Post_Meta_Container $container */
174
			foreach ( $container->get_fields() as $field ) {
175
				$fields[ $field->get_name() ] = $field->get_label();
176
			}
177
		}
178
179
		return $fields;
180
	}
181
182
	protected function get_latest_post_revision( $post_id ) {
183
		$revisions = wp_get_post_revisions( $post_id );
184
		$revision = array_shift($revisions);
185
186
		return $revision;
187
	}
188
189
	protected function get_preview_id() {
190
		if( isset( $_GET['preview_id'] ) ) {
191
			return intval( $_GET['preview_id'] );
192
		}
193
194
		if( isset( $_GET['p'] ) ) {
195
			return intval( $_GET['p'] );
196
		}
197
198
		if( isset( $_GET['page_id'] ) ) {
199
			return intval( $_GET['page_id'] );
200
		}
201
202
		return 0;
203
	}
204
205
	protected function copy_meta( $from_id, $to_id ) {
206
	    $repository = Carbon_Fields::resolve( 'container_repository' );
207
	    $containers = $repository->get_containers( 'post_meta' );
208
	    $containers = array_filter( $containers, function( $container ) {
209
		    /** @var \Carbon_Fields\Container\Post_Meta_Container $container */
210
	        return !$container->get_revisions_disabled();
211
	    } );
212
213
	    $field_keys = array();
214
	    foreach ( $containers as $container ) {
215
		    /** @var \Carbon_Fields\Container\Post_Meta_Container $container */
216
	        foreach ( $container->get_fields() as $field ) {
217
	            $field_keys[] = $field->get_name();
218
	        }
219
	    }
220
221
	    $meta = get_post_meta( $from_id );
222
	    $meta_to_copy = $this->filter_meta_by_keys( $meta, $field_keys );
223
224
	    if ( ! $meta_to_copy ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $meta_to_copy of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
225
	    	return;
226
	    }
227
228
	    $this->delete_old_meta( $to_id, $meta_to_copy );
229
	    $this->insert_new_meta( $to_id, $meta_to_copy );
230
	}
231
232
	protected function meta_key_matches_names( $meta_key, $names ) {
233
		foreach ( $names as $name ) {
234
			if ( strpos( $meta_key, $name ) === 0 ) {
235
				return true;
236
			}
237
		}
238
239
		return false;
240
	}
241
242
	protected function filter_meta_by_keys( $meta, $field_keys ) {
243
		$filtered_meta = array();
244
		foreach ( $meta as $meta_key => $meta_value ) {
245
			if ( ! $this->meta_key_matches_names( $meta_key, $field_keys ) ) {
246
				continue;
247
			}
248
249
			$filtered_meta[ $meta_key ] = $meta_value;
250
		}
251
252
		return $filtered_meta;
253
	}
254
255
	protected function delete_old_meta( $to_id, $meta_to_copy ) {
256
		global $wpdb;
257
258
		if ( ! $to_id ) {
259
			return;
260
		}
261
262
		$keys = $this->get_keys_for_mysql( $meta_to_copy );
263
		$delete_query = $wpdb->prepare(
264
			"DELETE FROM `{$wpdb->postmeta}` WHERE `meta_key` IN ({$keys}) AND `post_id` = %d",
265
			$to_id
266
		);
267
		$wpdb->query( $delete_query );
268
	}
269
270
	protected function insert_new_meta( $to_id, $meta_to_copy ) {
271
		global $wpdb;
272
273
		if ( ! $to_id ) {
274
			return;
275
		}
276
277
		$values = array();
278
		foreach ( $meta_to_copy as $meta_key => $meta_value ) {
279
			$meta_value_string = ( is_array( $meta_value ) ) ? $meta_value[0] : $meta_value;
280
			$value = '(';
281
			$value .= intval( $to_id );
282
			$value .= ", '" . esc_sql( $meta_key ) . "'";
283
			$value .= ", '" . esc_sql( $meta_value_string ) . "'";
284
			$value .= ')';
285
286
			$values[] = $value;
287
		}
288
289
		$values_string = implode( ',', $values );
290
291
		$sql = "INSERT INTO `{$wpdb->postmeta}`(post_id, meta_key, meta_value) VALUES " . $values_string;
292
293
	    $wpdb->query( $sql );
294
	}
295
296
	protected function get_keys_for_mysql( $meta_to_copy ) {
297
		$keys = array_keys( $meta_to_copy );
298
		$keys = array_map(
299
			function( $item ) {
300
				$item = "'" . esc_sql( $item ) . "'";
301
				return $item;
302
			},
303
			$keys
304
		);
305
306
		return implode( ',', $keys );
307
	}
308
}
309