Passed
Push — ci ( 38d7bc...9f72b5 )
by litefeel
03:33
created

lib/import.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * GitHub Import Manager
4
 *
5
 * @package Writing_On_GitHub
6
 */
7
8
/**
9
 * Class Writing_On_GitHub_Import
10
 */
11
class Writing_On_GitHub_Import {
12
13
	/**
14
	 * Application container.
15
	 *
16
	 * @var Writing_On_GitHub
17
	 */
18
	protected $app;
19
20
	/**
21
	 * Initializes a new import manager.
22
	 *
23
	 * @param Writing_On_GitHub $app Application container.
24
	 */
25
	public function __construct( Writing_On_GitHub $app ) {
26
		$this->app = $app;
27
	}
28
29
    /**
30
     * Imports a payload.
31
     * @param  Writing_On_GitHub_Payload $payload
32
     *
33
     * @return string|WP_Error
34
     */
35 View Code Duplication
	public function payload( Writing_On_GitHub_Payload $payload ) {
36
37
		$result = $this->app->api()->fetch()->compare( $payload->get_before_commit_id() );
38
39
		if ( is_wp_error( $result ) ) {
40
            /* @var WP_Error $result */
41
			return $result;
42
		}
43
44
		$result = $this->import_files( $result );
45
46
		if ( is_wp_error( $result ) ) {
47
			return $files;
48
		}
49
50
		return __( 'Payload processed', 'writing-on-github' );
51
	}
52
53
	/**
54
	 * import blob by files
55
	 * @param  Writing_On_GitHub_File_Info[] $files
56
     *
57
	 * @return string|WP_Error
0 ignored issues
show
Should the return type not be WP_Error|string|array|false? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
58
	 */
59
	protected function import_files( $files ) {
60
61
		$error 		= false;
62
		$delete_ids = false;
63
64
		$result = $this->compare( $files, $delete_ids );
65
66
		if ( is_wp_error( $result ) ) {
67
			return $result;
68
		}
69
70
		if ( $delete_ids ) {
71 View Code Duplication
			foreach ($delete_ids as $id) {
72
				$result = $this->app->database()->delete_post( $id );
73
				if ( is_wp_error( $result ) ) {
74
					if ( $error ) {
75
						$error->add( $result->get_error_code(), $result->get_error_message() );
76
					} else {
77
						$error = $result;
78
					}
79
				}
80
			}
81
		}
82
83
		return $error;
84
	}
85
86
	/**
87
	 * Imports the latest commit on the master branch.
88
	 *
89
	 * @return string|WP_Error
90
	 */
91 View Code Duplication
	public function master() {
92
		$result = $this->app->api()->fetch()->tree_recursive();
93
94
		if ( is_wp_error( $result ) ) {
95
            /* @var WP_Error $result */
96
			return $result;
97
		}
98
99
		$result = $this->import_files( $result );
100
101
		if ( is_wp_error( $result ) ) {
102
			return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (WP_Error|string|array|false) is incompatible with the return type documented by Writing_On_GitHub_Import::master 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...
103
		}
104
105
		return __( 'Payload processed', 'writing-on-github' );
106
	}
107
108
    /**
109
     * Do compare
110
     * @param  Writing_On_GitHub_File_Info[]|WP_Error $files
111
     * @param  int[] &$delete_ids
112
     *
113
     * @return string|WP_Error
0 ignored issues
show
Should the return type not be WP_Error|string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
114
     */
115
	protected function compare( $files, &$delete_ids ) {
116
		if ( is_wp_error( $files ) ) {
117
            /* @var WP_Error $files */
118
			return $files;
119
		}
120
121
		$posts = array();
122
		$new   = array();
123
124
		$idsmap = array();
125
126
		foreach ( $files as $file ) {
127
			if ( ! $this->importable_file( $file ) ) {
128
				continue;
129
			}
130
131
			$blob = $this->app->api()->fetch()->blob( $file );
132
			// network error ?
133
			if ( ! $blob instanceof Writing_On_GitHub_Blob ) {
134
				continue;
135
			}
136
137
			if ( $this->importable_raw_file( $blob ) ) {
138
				$this->import_raw_file( $blob, $file->status == 'removed' );
139
				continue;
140
			}
141
142
			if ( ! $this->importable_blob( $blob ) ) {
143
				continue;
144
			}
145
146
			$post = $this->blob_to_post( $blob );
147
148
			if ( $file->status == 'removed' ) {
149
				if ( $blob->id() ) {
150
					$idsmap[$blob->id()] = true;
151
				}
152
			} elseif ( $post != false ) {
153
				$posts[] = $post;
154
				if ( $post->is_new() ) {
155
					$new[] = $post;
156
				}
157
			}
158
		}
159
160
		foreach ( $posts as $post ) {
161
			if ( $post->id() && isset( $idsmap[ $post->id() ] ) ) {
162
				unset( $idsmap[ $post->id() ] );
163
			}
164
		}
165
		$delete_ids = array();
166
		foreach ( $idsmap as $id => $value ) {
167
			$delete_ids[] = $id;
168
		}
169
170
		// $this->app->database()->save_posts( $posts, $commit->author_email() );
171
172
		$result = $this->app->database()->save_posts( $posts );
173
174
		if ( is_wp_error( $result ) ) {
175
			return $result;
176
		}
177
178
		if ( ! empty( $new ) ) {
179
			$result = $this->app->export()->new_posts( $new );
180
181
			if ( is_wp_error( $result ) ) {
182
				return $result;
183
			}
184
		}
185
186
		return $posts;
187
	}
188
189
	/**
190
	 * Checks whether the provided blob should be imported.
191
	 *
192
	 * @param Writing_On_GitHub_File_Info $file
193
	 *
194
	 * @return bool
195
	 */
196
	protected function importable_file( Writing_On_GitHub_File_Info $file ) {
197
198
		// only _pages and _posts
199
		if ( strncasecmp($file->path, '_pages/', strlen('_pages/') ) != 0 &&
200
			 strncasecmp($file->path, '_posts/', strlen('_posts/') ) != 0 &&
201
			 strncasecmp($file->path, 'images/', strlen('images/') ) != 0 ) {
202
			return false;
203
		}
204
205
206
		// if ( ! $file->has_frontmatter() ) {
207
		// 	return false;
208
		// }
209
210
		return true;
211
	}
212
213
	/**
214
	 * Checks whether the provided blob should be imported.
215
	 *
216
	 * @param Writing_On_GitHub_Blob $blob Blob to validate.
217
	 *
218
	 * @return bool
219
	 */
220
	protected function importable_blob( Writing_On_GitHub_Blob $blob ) {
221
		// global $wpdb;
222
223
		// // Skip the repo's readme.
224
		// if ( 'readme' === strtolower( substr( $blob->path(), 0, 6 ) ) ) {
225
		// 	return false;
226
		// }
227
228
		// // If the blob sha already matches a post, then move on.
229
		// if ( ! is_wp_error( $this->app->database()->fetch_by_sha( $blob->sha() ) ) ) {
230
		// 	return false;
231
		// }
232
233
		if ( ! $blob->has_frontmatter() ) {
234
			return false;
235
		}
236
237
		return true;
238
	}
239
240
	protected function importable_raw_file( Writing_On_GitHub_Blob $blob ) {
241
		if ( $blob->has_frontmatter() ) {
242
			return false;
243
		}
244
245
		// only images
246
		if ( strncasecmp($blob->path(), 'images/', strlen('images/') ) != 0) {
247
			return false;
248
		}
249
250
		return true;
251
	}
252
253
	/**
254
	 * Imports a raw file content into file system.
255
	 * @param  Writing_On_GitHub_Blob $blob
256
	 * @param  bool                   $is_remove
257
	 */
258
	protected function import_raw_file( Writing_On_GitHub_Blob $blob, $is_remove ) {
259
		$arr = wp_upload_dir();
260
		$path = $arr['basedir'] . '/writing-on-github/' . $blob->path();
261
		if ( $is_remove ) {
262
			if ( file_exists($path) ) {
263
				unlink($path);
264
			}
265
		} else {
266
			$dirname = dirname($path);
267
			if ( ! file_exists($dirname) ) {
268
				wp_mkdir_p($dirname);
269
			}
270
271
			file_put_contents($path, $blob->content());
272
		}
273
	}
274
275
	/**
276
	 * Imports a single blob content into matching post.
277
	 *
278
	 * @param Writing_On_GitHub_Blob $blob Blob to transform into a Post.
279
	 *
280
	 * @return Writing_On_GitHub_Post|false
281
	 */
282
	protected function blob_to_post( Writing_On_GitHub_Blob $blob ) {
283
		$args = array( 'post_content' => $blob->content_import() );
284
		$meta = $blob->meta();
285
286
		$id = false;
287
288
		if ( ! empty( $meta ) ) {
289
			if ( array_key_exists( 'layout', $meta ) ) {
290
				$args['post_type'] = $meta['layout'];
291
				unset( $meta['layout'] );
292
			}
293
294
			if ( array_key_exists( 'published', $meta ) ) {
295
				$args['post_status'] = true === $meta['published'] ? 'publish' : 'draft';
296
				unset( $meta['published'] );
297
			}
298
299
			if ( array_key_exists( 'post_title', $meta ) ) {
300
				$args['post_title'] = $meta['post_title'];
301
				unset( $meta['post_title'] );
302
			}
303
304
			if ( array_key_exists( 'post_name', $meta ) ) {
305
				$args['post_name'] = $meta['post_name'];
306
				unset( $meta['post_name'] );
307
			}
308
309
			if ( array_key_exists( 'ID', $meta ) ) {
310
				$id = $args['ID'] = $meta['ID'];
311
				$blob->set_id($id);
312
				unset( $meta['ID'] );
313
			}
314
		}
315
316
		$meta['_wogh_sha'] = $blob->sha();
317
318
		if ( $id ) {
319
			$old_sha = get_post_meta( $id, '_wogh_sha', true );
320
			$old_github_path = get_post_meta( $id, '_wogh_github_path', true );
321
322
			// dont save post when has same sha
323
			if ( $old_sha  && $old_sha == $meta['_wogh_sha'] &&
324
				 $old_github_path && $old_github_path == $blob->path() ) {
325
				return false;
326
			}
327
		}
328
329
		$post = new Writing_On_GitHub_Post( $args, $this->app->api() );
330
		$post->set_old_github_path( $blob->path() );
331
		$post->set_meta( $meta );
332
		$blob->set_id( $post->id() );
333
334
		return $post;
335
	}
336
}
337