Completed
Branch development (882501)
by Kenny
24:05
created

WordPress_GitHub_Sync_Import   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 17
Bugs 3 Features 3
Metric Value
wmc 46
c 17
b 3
f 3
lcom 1
cbo 8
dl 0
loc 325
rs 8.3999

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
C payload() 0 36 7
A master() 0 3 1
D commit() 0 93 16
A get_blob_processor() 0 12 4
A is_excluded_path() 0 11 2
A is_excluded_mime_type() 0 7 1
A is_excluded_file_extension() 0 8 1
A importable_blob() 0 13 4
A blob_changed() 0 13 2
C blob_to_post() 0 37 7

How to fix   Complexity   

Complex Class

Complex classes like WordPress_GitHub_Sync_Import 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 WordPress_GitHub_Sync_Import, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * GitHub Import Manager
4
 *
5
 * @package WordPress_GitHub_Sync
6
 */
7
8
/**
9
 * Class WordPress_GitHub_Sync_Import
10
 */
11
class WordPress_GitHub_Sync_Import {
12
13
	/**
14
	 * Application container.
15
	 *
16
	 * @var WordPress_GitHub_Sync
17
	 */
18
	protected $app;
19
20
	/**
21
	 * Initializes a new import manager.
22
	 *
23
	 * @param WordPress_GitHub_Sync $app Application container.
24
	 */
25
	public function __construct( WordPress_GitHub_Sync $app ) {
26
		$this->app = $app;
27
	}
28
29
	/**
30
	 * Imports a payload.
31
	 *
32
	 * @param WordPress_GitHub_Sync_Payload $payload GitHub payload object.
33
	 *
34
	 * @return string|WP_Error
35
	 */
36
	public function payload( WordPress_GitHub_Sync_Payload $payload ) {
37
		/**
38
		 * Whether there's an error during import.
39
		 *
40
		 * @var false|WP_Error $error
41
		 */
42
		$error = false;
43
44
		$result = $this->commit( $this->app->api()->fetch()->commit( $payload->get_commit_id() ) );
0 ignored issues
show
Bug introduced by
It seems like $this->app->api()->fetch...yload->get_commit_id()) targeting WordPress_GitHub_Sync_Fetch_Client::commit() can also be of type boolean or object<WordPress_GitHub_Sync_Tree> or object<stdClass>; however, WordPress_GitHub_Sync_Import::commit() does only seem to accept object<WordPress_GitHub_...ommit>|object<WP_Error>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
45
46
		if ( is_wp_error( $result ) ) {
47
			$error = $result;
48
		}
49
50
		$removed = array();
51
		foreach ( $payload->get_commits() as $commit ) {
52
			$removed = array_merge( $removed, $commit->removed );
53
		}
54
		foreach ( array_unique( $removed ) as $path ) {
55
			$result = $this->app->database()->delete_post_by_path( $path );
56
57
			if ( is_wp_error( $result ) ) {
58
				if ( $error ) {
59
					$error->add( $result->get_error_code(), $result->get_error_message() );
60
				} else {
61
					$error = $result;
62
				}
63
			}
64
		}
65
66
		if ( $error ) {
67
			return $error;
68
		}
69
70
		return __( 'Payload processed', 'wordpress-github-sync' );
71
	}
72
73
	/**
74
	 * Imports the latest commit on the master branch.
75
	 *
76
	 * @return string|WP_Error
77
	 */
78
	public function master() {
79
		return $this->commit( $this->app->api()->fetch()->master() );
0 ignored issues
show
Bug introduced by
It seems like $this->app->api()->fetch()->master() targeting WordPress_GitHub_Sync_Fetch_Client::master() can also be of type boolean or object<WordPress_GitHub_Sync_Tree> or object<stdClass>; however, WordPress_GitHub_Sync_Import::commit() does only seem to accept object<WordPress_GitHub_...ommit>|object<WP_Error>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
80
	}
81
82
	/**
83
	 * Imports a provided commit into the database.
84
	 *
85
	 * @param WordPress_GitHub_Sync_Commit|WP_Error $commit Commit to import.
86
	 *
87
	 * @return string|WP_Error
88
	 */
89
	protected function commit( $commit ) {
90
		if ( is_wp_error( $commit ) ) {
91
			return $commit;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $commit; of type WordPress_GitHub_Sync_Commit|WP_Error is incompatible with the return type documented by WordPress_GitHub_Sync_Import::commit of type string|WP_Error as it can also be of type WordPress_GitHub_Sync_Commit which is not included in this return type.
Loading history...
92
		}
93
94
		if ( $commit->already_synced() ) {
95
			return new WP_Error( 'commit_synced', __( 'Already synced this commit.', 'wordpress-github-sync' ) );
96
		}
97
98
		$posts = array();
99
		$new   = array();
100
101
		$import_results = array();
102
103
		foreach ( $commit->tree()->blobs() as $blob ) {
104
105
			$import_results[] = $import_result = (object) array();
106
			$import_result->path = $blob->path();
107
108
			// is this blob importable?
109
			if ( !$this->importable_blob($blob) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
110
				$import_result->importable = false;
111
				continue;
112
			}
113
114
			// has the blob changed since the last import?
115
			if( !$this->blob_changed($blob) ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
introduced by
Expected 1 space after "!"; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
116
				$import_result->changed = false;
117
				continue;
118
			}
119
120
			// fetch the function that will be used to import the blob
121
			// based on blob properties
122
			if( $processor = $this->get_blob_processor($blob) ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
123
				if( is_callable($processor) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
124
125
					// call the processor to import the blob
126
					$result = call_user_func_array($processor,[$blob,$this]);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
127
128
					// if the result is a Post object ( WordPress_GitHub_Sync_Post )
129
					if( $result && is_a($result,'WordPress_GitHub_Sync_Post')){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
introduced by
No space before closing parenthesis is prohibited
Loading history...
130
131
						// add the post to the array of posts to be
132
						// imported in to the database
133
						$posts[] = $post = $result;
134
135
						if ( $post->is_new() ) {
136
							// keep track of new posts
137
							$new[] = $post;
138
						}
139
140
						$import_result->new = $post->is_new();
141
142
					}else{
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
143
						$import_result->result = $result?$result:false;
144
					}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
145
146
				}else{
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
147
					$import_result->processor = "not callable";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal not callable does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
148
				}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
149
150
			}else{
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
151
				$import_result->processor = "no processor";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal no processor does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
152
			}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
153
154
		}
155
156
		// TODO: Log/output results
157
		// print_r($import_results);
158
159
		if( count($posts) ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
160
161
			$result = $this->app->database()->save_posts( $posts, $commit->author_email() );
162
163
			if ( is_wp_error( $result ) ) {
164
				return $result;
165
			}
166
167
			if ( $new ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new 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...
168
169
				do_action('wpghs_post_new_post_import', $new);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
170
171
				$result = $this->app->export()->new_posts( $new );
172
173
				if ( is_wp_error( $result ) ) {
174
					return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (stdClass|WP_Error|boolea...tHub_Sync_Commit|string) is incompatible with the return type documented by WordPress_GitHub_Sync_Import::commit of type string|WP_Error.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
175
				}
176
			}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
177
178
		}
179
180
		return __( 'No commitable posts', 'wordpress-github-sync' );;
181
	}
182
183
	/**
184
	 * Get the function that will be used to import the blob, hookable
185
	 *
186
	 * @param  WordPress_GitHub_Sync_Blob
187
	 * @return callable
188
	 */
189
	protected function get_blob_processor( WordPress_GitHub_Sync_Blob $blob ) {
190
		$processor = false;
191
192
		// import any plain text .md document in the defaut way
193
		if ( $blob->has_frontmatter()
0 ignored issues
show
introduced by
Found "== "". Use Yoda Condition checks, you must
Loading history...
194
				&& $blob->mimetype() == "text/plain"
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal text/plain does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
195
				&& $blob->file_extension() == "md" ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal md does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
196
			$processor = array($this,'blob_to_post');
0 ignored issues
show
introduced by
No space after opening parenthesis of array is bad style
Loading history...
introduced by
Expected 1 space between comma and "'blob_to_post'"; 0 found
Loading history...
introduced by
No space before closing parenthesis of array is bad style
Loading history...
197
		}
198
199
		return apply_filters('wpghs_get_blob_processor', $processor, $blob);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
200
	}
201
202
	/**
203
	 * Returns true if the path is not importable, hookable
204
	 *
205
	 * @param  string
206
	 * @return boolean
207
	 */
208
	protected function is_excluded_path( $path ) {
209
210
		$excluded = false;
211
212
		// Skip the repo's readme.
213
		if ( 'readme' === strtolower( substr( $path, 0, 6 ) ) ) {
214
			$excluded = true;
215
		}
216
217
		return apply_filters('wpghs_exclude_paths', $excluded, $path);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
218
	}
219
220
	/**
221
	 * Returns true if the mine type is not importable, hookable
222
	 *
223
	 * @param  string
224
	 * @return boolean
225
	 */
226
	protected function is_excluded_mime_type( $mime_type ) {
227
228
		// nothing excuded by default
229
		$excluded = false;
230
231
		return apply_filters('wpghs_exclude_mime_types', $excluded, $mime_type);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
232
	}
233
234
	/**
235
	 * Returns true if the file extension is not importable, hookable
236
	 *
237
	 * @param  string
238
	 * @return boolean
239
	 */
240
	protected function is_excluded_file_extension( $file_extension ) {
241
242
		// nothing excuded by default
243
		$excluded = false;
244
245
		// TODO: Test this hook
246
		return apply_filters('wpghs_exclude_file_extensions', $excluded, $file_extension);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
247
	}
248
249
	/**
250
	 * Checks whether the provided blob should be imported.
251
	 *
252
	 * @param WordPress_GitHub_Sync_Blob $blob Blob to validate.
253
	 *
254
	 * @return bool
255
	 */
256
	protected function importable_blob( WordPress_GitHub_Sync_Blob $blob ) {
257
		global $wpdb;
258
259
		$importable = true;
260
261
		if( $this->is_excluded_path($blob->path()) ||
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
262
			$this->is_excluded_file_extension($blob->file_extension()) ||
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
263
			$this->is_excluded_mime_type($blob->path()) ){
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
264
			$importable = false;
265
		}
266
267
		return apply_filters('wpghs_importable_blob', $importable, $blob);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
268
	}
269
270
	/**
271
	 * Checks whether the provided blob has changed.
272
	 *
273
	 * @param WordPress_GitHub_Sync_Blob $blob Blob to validate.
274
	 *
275
	 * @return bool
276
	 */
277
	protected function blob_changed( WordPress_GitHub_Sync_Blob $blob ) {
278
		global $wpdb;
279
280
		$changed = true;
281
282
		// If the blob sha already matches a post, and the path accociated with the sha
283
		// has not changed then there has been no change
284
		if ( $this->app->database()->sha_exists_with_path( $blob->sha(), $blob->path() ) ){
285
			return false;
286
		}
287
288
		return apply_filters('wpghs_blob_changed', $blob, $changed);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
289
	}
290
291
	/**
292
	 * Imports a single blob content into matching post.
293
	 *
294
	 * @param WordPress_GitHub_Sync_Blob $blob Blob to transform into a Post.
295
	 *
296
	 * @return WordPress_GitHub_Sync_Post
297
	 */
298
	protected function blob_to_post( WordPress_GitHub_Sync_Blob $blob ) {
299
		$args = array( 'post_content' => $blob->content_import() );
300
		$meta = $blob->meta();
301
302
		if ( $meta ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $meta 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...
303
			if ( array_key_exists( 'layout', $meta ) ) {
304
				$args['post_type'] = $meta['layout'];
305
				unset( $meta['layout'] );
306
			}
307
308
			if ( array_key_exists( 'published', $meta ) ) {
309
				$args['post_status'] = true === $meta['published'] ? 'publish' : 'draft';
310
				unset( $meta['published'] );
311
			}
312
313
			if ( array_key_exists( 'post_title', $meta ) ) {
314
				$args['post_title'] = $meta['post_title'];
315
				unset( $meta['post_title'] );
316
			}
317
318
			if ( array_key_exists( 'ID', $meta ) ) {
319
				$args['ID'] = $meta['ID'];
320
				unset( $meta['ID'] );
321
			}
322
		}
323
324
		$meta['_sha'] = $blob->sha();
325
		$meta['_wpghs_github_path']  = $blob->path();
326
		
327
		$meta = apply_filters('wpghs_blob_to_post_meta', $meta, $blob);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
328
		$args = apply_filters('wpghs_blob_to_post_args', $args, $meta, $blob);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
329
330
		$post = new WordPress_GitHub_Sync_Post( $args, $this->app->api() );
331
		$post->set_meta( $meta );
332
333
		return $post;
334
	}
335
}
336