Issues (64)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/database.php (12 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
 * Database interface.
4
 * @package WordPress_GitHub_Sync
5
 */
6
7
/**
8
 * Class WordPress_GitHub_Sync_Database
9
 */
10
class WordPress_GitHub_Sync_Database {
11
12
	/**
13
	 * Application container.
14
	 *
15
	 * @var WordPress_GitHub_Sync
16
	 */
17
	protected $app;
18
19
	/**
20
	 * Currently whitelisted post types.
21
	 *
22
	 * @var array
23
	 */
24
	protected $whitelisted_post_types = array( 'post', 'page' );
25
26
	/**
27
	 * Currently whitelisted post statuses.
28
	 *
29
	 * @var array
30
	 */
31
	protected $whitelisted_post_statuses = array( 'publish' );
32
33
	/**
34
	 * Instantiates a new Database object.
35
	 *
36
	 * @param WordPress_GitHub_Sync $app Application container.
37
	 */
38 22
	public function __construct( WordPress_GitHub_Sync $app ) {
39 22
		$this->app = $app;
40 22
	}
41
42
	/**
43
	 * Queries the database for all of the supported posts.
44
	 *
45
	 * @return WordPress_GitHub_Sync_Post[]|WP_Error
46
	 */
47 3
	public function fetch_all_supported() {
48
		$args  = array(
49 3
			'post_type'   => $this->get_whitelisted_post_types(),
50 3
			'post_status' => $this->get_whitelisted_post_statuses(),
51 3
			'nopaging'    => true,
0 ignored issues
show
Disabling pagination is prohibited in VIP context, do not set nopaging to true ever.
Loading history...
52 3
			'fields'      => 'ids',
53 3
		);
54
55 3
		$query = new WP_Query( apply_filters( 'wpghs_pre_fetch_all_supported', $args ) );
56
57 3
		$post_ids = $query->get_posts();
58
59 3
		if ( ! $post_ids ) {
60 1
			return new WP_Error(
61 1
				'no_results',
62 1
				__( 'Querying for supported posts returned no results.', 'wp-github-sync' )
63 1
			);
64
		}
65
66 2
		$results = array();
67 2
		foreach ( $post_ids as $post_id ) {
68 2
			$results[] = new WordPress_GitHub_Sync_Post( $post_id, $this->app->api() );
69 2
		}
70
71 2
		return $results;
72
	}
73
74
	/**
75
	 * Queries a post and returns it if it's supported.
76
	 *
77
	 * @param int $post_id Post ID to fetch.
78
	 *
79
	 * @return WP_Error|WordPress_GitHub_Sync_Post
80
	 */
81 5
	public function fetch_by_id( $post_id ) {
82 5
		$post = new WordPress_GitHub_Sync_Post( $post_id, $this->app->api() );
83
84 5
		if ( ! $this->is_post_supported( $post ) ) {
85 4
			return new WP_Error(
86 4
				'unsupported_post',
87 4
				sprintf(
88 4
					__(
89 4
						'Post ID %s is not supported by WPGHS. See wiki to find out how to add support.',
90
						'wp-github-sync'
91 4
					),
92
					$post_id
93 4
				)
94 4
			);
95
		}
96
97 1
		return $post;
98
	}
99
100
	/**
101
	 * Queries for a post by provided sha.
102
	 *
103
	 * @param string $sha Post sha to fetch by.
104
	 *
105
	 * @return WordPress_GitHub_Sync_Post|WP_Error
106
	 */
107 2
	public function fetch_by_sha( $sha ) {
108 2
		$query = new WP_Query( array(
109 2
			'meta_key'       => '_sha',
0 ignored issues
show
Detected usage of meta_key, possible slow query.
Loading history...
110 2
			'meta_value'     => $sha,
0 ignored issues
show
Detected usage of meta_value, possible slow query.
Loading history...
111 2
			'meta_compare'   => '=',
112 2
			'posts_per_page' => 1,
113 2
			'fields'         => 'ids',
114 2
		) );
115
116 2
		$post_id = $query->get_posts();
117 2
		$post_id = array_pop( $post_id );
118
119 2
		if ( ! $post_id ) {
120 1
			return new WP_Error(
121 1
				'sha_not_found',
122 1
				sprintf(
123 1
					__(
124 1
						'Post for sha %s not found.',
125
						'wp-github-sync'
126 1
					),
127
					$sha
128 1
				)
129 1
			);
130
		}
131
132 1
		return new WordPress_GitHub_Sync_Post( $post_id, $this->app->api() );
133
	}
134
135
	/**
136
	 * Saves an array of Post objects to the database
137
	 * and associates their author as well as their latest
138
	 *
139
	 * @param WordPress_GitHub_Sync_Post[] $posts Array of Posts to save.
140
	 * @param string                       $email Author email.
141
	 *
142
	 * @return string|WP_Error
143
	 */
144 6
	public function save_posts( array $posts, $email ) {
145 6
		$user    = $this->fetch_commit_user( $email );
146 6
		$user_id = ! is_wp_error( $user ) ? $user->ID : 0;
147
148
		/**
149
		 * Whether an error has occurred.
150
		 *
151
		 * @var WP_Error|false $error
152
		 */
153 6
		$error = false;
154
155 6
		foreach ( $posts as $post ) {
156 6
			$args    = apply_filters( 'wpghs_pre_import_args', $post->get_args(), $post );
157
			
158 6
			remove_filter('content_save_pre', 'wp_filter_post_kses');
0 ignored issues
show
Expected 1 spaces after opening bracket; 0 found
Loading history...
Expected 1 spaces before closing bracket; 0 found
Loading history...
159 6
			$post_id = $post->is_new() ?
160 6
				wp_insert_post( $args, true ) :
161 6
				wp_update_post( $args, true );
162 6
			add_filter('content_save_pre', 'wp_filter_post_kses');
0 ignored issues
show
Expected 1 spaces after opening bracket; 0 found
Loading history...
Expected 1 spaces before closing bracket; 0 found
Loading history...
163
164 6
			if ( is_wp_error( $post_id ) ) {
165
				if ( ! $error ) {
166
					$error = $post_id;
167
				} else {
168
					$error->add( $post_id->get_error_code(), $post_id->get_error_message() );
169
				}
170
171
				// Abort saving if updating the post fails.
172
				continue;
173
			}
174
175 6
			$this->set_revision_author( $post_id, $user_id );
176
177 6
			if ( $post->is_new() ) {
178 3
				$this->set_post_author( $post_id, $user_id );
179 3
			}
180
181 6
			$post->set_post( get_post( $post_id ) );
182
183 6
			$meta = apply_filters( 'wpghs_pre_import_meta', $post->get_meta(), $post );
184
185 6
			foreach ( $meta as $key => $value ) {
186 6
				update_post_meta( $post_id, $key, $value );
187 6
			}
188 6
		}
189
190 6
		if ( $error ) {
191
			return $error;
192
		}
193
194 6
		return __( 'Successfully saved posts.', 'wp-github-sync' );
195
	}
196
197
	/**
198
	 * Deletes a post from the database based on its GitHub path.
199
	 *
200
	 * @param string $path Path of Post to delete.
201
	 *
202
	 * @return string|WP_Error
203
	 */
204 5
	public function delete_post_by_path( $path ) {
205 5
		$query = new WP_Query( array(
206 5
			'meta_key'       => '_wpghs_github_path',
0 ignored issues
show
Detected usage of meta_key, possible slow query.
Loading history...
207 5
			'meta_value'     => $path,
0 ignored issues
show
Detected usage of meta_value, possible slow query.
Loading history...
208 5
			'meta_compare'   => '=',
209 5
			'posts_per_page' => 1,
210 5
			'fields'         => 'ids',
211 5
		) );
212
213 5
		$post_id = $query->get_posts();
214 5
		$post_id = array_pop( $post_id );
215
216 5
		if ( ! $post_id ) {
217 4
			$parts     = explode( '/', $path );
218 4
			$filename  = array_pop( $parts );
219 4
			$directory = $parts ? array_shift( $parts ) : '';
220
221 4 View Code Duplication
			if ( false !== strpos( $directory, 'post' ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222 2
				preg_match( '/([0-9]{4})-([0-9]{2})-([0-9]{2})-(.*)\.md/', $filename, $matches );
223 2
				$title = $matches[4];
224
225 2
				$query = new WP_Query( array(
226 2
					'name'     => $title,
227 2
					'posts_per_page' => 1,
228 2
					'post_type' => $this->get_whitelisted_post_types(),
229 2
					'fields'         => 'ids',
230 2
				) );
231
232 2
				$post_id = $query->get_posts();
233 2
				$post_id = array_pop( $post_id );
234 2
			}
235
236 4 View Code Duplication
			if ( ! $post_id ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237 3
				preg_match( '/(.*)\.md/', $filename, $matches );
238 3
				$title = $matches[1];
239
240 3
				$query = new WP_Query( array(
241 3
					'name'     => $title,
242 3
					'posts_per_page' => 1,
243 3
					'post_type' => $this->get_whitelisted_post_types(),
244 3
					'fields'         => 'ids',
245 3
				) );
246
247 3
				$post_id = $query->get_posts();
248 3
				$post_id = array_pop( $post_id );
249 3
			}
250 4
		}
251
252 5
		if ( ! $post_id ) {
253 1
			return new WP_Error(
254 1
				'path_not_found',
255 1
				sprintf(
256 1
					__( 'Post not found for path %s.', 'wp-github-sync' ),
257
					$path
258 1
				)
259 1
			);
260
		}
261
262 4
		$result = wp_delete_post( $post_id );
263
264
		// If deleting fails...
265 4
		if ( false === $result ) {
266
			$post = get_post( $post_id );
267
268
			// ...and the post both exists and isn't in the trash...
269
			if ( $post && 'trash' !== $post->post_status ) {
270
				// ... then something went wrong.
271
				return new WP_Error(
272
					'db_error',
273
					sprintf(
274
						__( 'Failed to delete post ID %d.', 'wp-github-sync' ),
275
						$post_id
276
					)
277
				);
278
			}
279
		}
280
281 4
		return sprintf(
282 4
			__( 'Successfully deleted post ID %d.', 'wp-github-sync' ),
283
			$post_id
284 4
		);
285
	}
286
287
	/**
288
	 * Returns the list of post type permitted.
289
	 *
290
	 * @return array
291
	 */
292 10
	protected function get_whitelisted_post_types() {
293 10
		return apply_filters( 'wpghs_whitelisted_post_types', $this->whitelisted_post_types );
294
	}
295
296
	/**
297
	 * Returns the list of post status permitted.
298
	 *
299
	 * @return array
300
	 */
301 7
	protected function get_whitelisted_post_statuses() {
302 7
		return apply_filters( 'wpghs_whitelisted_post_statuses', $this->whitelisted_post_statuses );
303
	}
304
305
	/**
306
	 * Formats a whitelist array for a query.
307
	 *
308
	 * @param array $whitelist Whitelisted posts to format into query.
309
	 *
310
	 * @return string Whitelist formatted for query
311
	 */
312
	protected function format_for_query( $whitelist ) {
313
		foreach ( $whitelist as $key => $value ) {
314
			$whitelist[ $key ] = "'$value'";
315
		}
316
317
		return implode( ', ', $whitelist );
318
	}
319
320
	/**
321
	 * Verifies that both the post's status & type
322
	 * are currently whitelisted
323
	 *
324
	 * @param  WordPress_GitHub_Sync_Post $post Post to verify.
325
	 *
326
	 * @return boolean                          True if supported, false if not.
327
	 */
328 5
	protected function is_post_supported( WordPress_GitHub_Sync_Post $post ) {
329 5
		if ( wp_is_post_revision( $post->id ) ) {
330 1
			return false;
331
		}
332
333
		// We need to allow trashed posts to be queried, but they are not whitelisted for export.
334 4
		if ( ! in_array( $post->status(), $this->get_whitelisted_post_statuses() ) && 'trash' !== $post->status() ) {
335 1
			return false;
336
		}
337
338 3
		if ( ! in_array( $post->type(), $this->get_whitelisted_post_types() ) ) {
339 1
			return false;
340
		}
341
342 2
		if ( $post->has_password() ) {
343 1
			return false;
344
		}
345
346 1
		return apply_filters( 'wpghs_is_post_supported', true, $post );
347
	}
348
349
	/**
350
	 * Retrieves the commit user for a provided email address.
351
	 *
352
	 * Searches for a user with provided email address or returns
353
	 * the default user saved in the database.
354
	 *
355
	 * @param string $email User email address to search for.
356
	 *
357
	 * @return WP_Error|WP_User
358
	 */
359 6
	protected function fetch_commit_user( $email ) {
360
		// If we can't find a user and a default hasn't been set,
361
		// we're just going to set the revision author to 0.
362 6
		$user = get_user_by( 'email', $email );
363
364 6
		if ( ! $user ) {
365
			// Use the default user.
366 4
			$user = get_user_by( 'id', (int) get_option( 'wpghs_default_user' ) );
367 4
		}
368
369 6
		if ( ! $user ) {
370 2
			return new WP_Error(
371 2
				'user_not_found',
372 2
				sprintf(
373 2
					__( 'Commit user not found for email %s', 'wp-github-sync' ),
374
					$email
375 2
				)
376 2
			);
377
		}
378
379 4
		return $user;
380
	}
381
382
	/**
383
	 * Sets the author latest revision
384
	 * of the provided post ID to the provided user.
385
	 *
386
	 * @param int $post_id Post ID to update revision author.
387
	 * @param int $user_id User ID for revision author.
388
	 *
389
	 * @return string|WP_Error
390
	 */
391 6
	protected function set_revision_author( $post_id, $user_id ) {
392 6
		$revision = wp_get_post_revisions( $post_id );
393
394 6
		if ( ! $revision ) {
395 3
			$new_revision = wp_save_post_revision( $post_id );
396
397 3
			if ( ! $new_revision || is_wp_error( $new_revision ) ) {
398
				return new WP_Error( 'db_error', 'There was a problem saving a new revision.' );
399
			}
400
401
			// `wp_save_post_revision` returns the ID, whereas `get_post_revision` returns the whole object
402
			// in order to be consistent, let's make sure we have the whole object before continuing.
403 3
			$revision = get_post( $new_revision );
404
405 3
			if ( ! $revision ) {
406
				return new WP_Error( 'db_error', 'There was a problem retrieving the newly recreated revision.' );
407
			}
408 3
		} else {
409 3
			$revision = array_shift( $revision );
410
		}
411
412 6
		return $this->set_post_author( $revision->ID, $user_id );
413
	}
414
415
	/**
416
	 * Updates the user ID for the provided post ID.
417
	 *
418
	 * Bypassing triggering any hooks, including creating new revisions.
419
	 *
420
	 * @param int $post_id Post ID to update.
421
	 * @param int $user_id User ID to update to.
422
	 *
423
	 * @return string|WP_Error
424
	 */
425 6
	protected function set_post_author( $post_id, $user_id ) {
426 6
		global $wpdb;
427
428 6
		$result = $wpdb->update(
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
429 6
			$wpdb->posts,
430
			array(
431 6
				'post_author' => (int) $user_id,
432 6
			),
433
			array(
434 6
				'ID' => (int) $post_id,
435 6
			),
436 6
			array( '%d' ),
437 6
			array( '%d' )
438 6
		);
439
440 6
		if ( false === $result ) {
441
			return new WP_Error( 'db_error', $wpdb->last_error );
442
		}
443
444 6
		if ( 0 === $result ) {
445 2
			return sprintf(
446 2
				__( 'No change for post ID %d.', 'wp-github-sync' ),
447
				$post_id
448 2
			);
449
		}
450
451 4
		clean_post_cache( $post_id );
452
453 4
		return sprintf(
454 4
			__( 'Successfully updated post ID %d.', 'wp-github-sync' ),
455
			$post_id
456 4
		);
457
	}
458
459
	/**
460
	 * Update the provided post's blob sha.
461
	 *
462
	 * @param WordPress_GitHub_Sync_Post $post Post to update.
463
	 * @param string                     $sha Sha to update to.
464
	 *
465
	 * @return bool|int
466
	 */
467 1
	public function set_post_sha( $post, $sha ) {
468 1
		return update_post_meta( $post->id, '_sha', $sha );
469
	}
470
}
471