Issues (54)

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 (8 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.', 'wordpress-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
						'wordpress-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
						'wordpress-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 6
			$post_id = $post->is_new() ?
158 6
				wp_insert_post( $args, true ) :
159 6
				wp_update_post( $args, true );
160
161 6
			if ( is_wp_error( $post_id ) ) {
162
				if ( ! $error ) {
163
					$error = $post_id;
164
				} else {
165
					$error->add( $post_id->get_error_code(), $post_id->get_error_message() );
166
				}
167
168
				// Abort saving if updating the post fails.
169
				continue;
170
			}
171
172 6
			$this->set_revision_author( $post_id, $user_id );
173
174 6
			if ( $post->is_new() ) {
175 3
				$this->set_post_author( $post_id, $user_id );
176 3
			}
177
178 6
			$post->set_post( get_post( $post_id ) );
179
180 6
			$meta = apply_filters( 'wpghs_pre_import_meta', $post->get_meta(), $post );
181
182 6
			foreach ( $meta as $key => $value ) {
183 6
				update_post_meta( $post_id, $key, $value );
184 6
			}
185 6
		}
186
187 6
		if ( $error ) {
188
			return $error;
189
		}
190
191 6
		return __( 'Successfully saved posts.', 'wordpress-github-sync' );
192
	}
193
194
	/**
195
	 * Deletes a post from the database based on its GitHub path.
196
	 *
197
	 * @param string $path Path of Post to delete.
198
	 *
199
	 * @return string|WP_Error
200
	 */
201 5
	public function delete_post_by_path( $path ) {
202 5
		$query = new WP_Query( array(
203 5
			'meta_key'       => '_wpghs_github_path',
0 ignored issues
show
Detected usage of meta_key, possible slow query.
Loading history...
204 5
			'meta_value'     => $path,
0 ignored issues
show
Detected usage of meta_value, possible slow query.
Loading history...
205 5
			'meta_compare'   => '=',
206 5
			'posts_per_page' => 1,
207 5
			'fields'         => 'ids',
208 5
		) );
209
210 5
		$post_id = $query->get_posts();
211 5
		$post_id = array_pop( $post_id );
212
213 5
		if ( ! $post_id ) {
214 4
			$parts     = explode( '/', $path );
215 4
			$filename  = array_pop( $parts );
216 4
			$directory = $parts ? array_shift( $parts ) : '';
217
218 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...
219 2
				preg_match( '/([0-9]{4})-([0-9]{2})-([0-9]{2})-(.*)\.md/', $filename, $matches );
220 2
				$title = $matches[4];
221
222 2
				$query = new WP_Query( array(
223 2
					'name'     => $title,
224 2
					'posts_per_page' => 1,
225 2
					'post_type' => $this->get_whitelisted_post_types(),
226 2
					'fields'         => 'ids',
227 2
				) );
228
229 2
				$post_id = $query->get_posts();
230 2
				$post_id = array_pop( $post_id );
231 2
			}
232
233 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...
234 3
				preg_match( '/(.*)\.md/', $filename, $matches );
235 3
				$title = $matches[1];
236
237 3
				$query = new WP_Query( array(
238 3
					'name'     => $title,
239 3
					'posts_per_page' => 1,
240 3
					'post_type' => $this->get_whitelisted_post_types(),
241 3
					'fields'         => 'ids',
242 3
				) );
243
244 3
				$post_id = $query->get_posts();
245 3
				$post_id = array_pop( $post_id );
246 3
			}
247 4
		}
248
249 5
		if ( ! $post_id ) {
250 1
			return new WP_Error(
251 1
				'path_not_found',
252 1
				sprintf(
253 1
					__( 'Post not found for path %s.', 'wordpress-github-sync' ),
254
					$path
255 1
				)
256 1
			);
257
		}
258
259 4
		$result = wp_delete_post( $post_id );
260
261
		// If deleting fails...
262 4
		if ( false === $result ) {
263
			$post = get_post( $post_id );
264
265
			// ...and the post both exists and isn't in the trash...
266
			if ( $post && 'trash' !== $post->post_status ) {
267
				// ... then something went wrong.
268
				return new WP_Error(
269
					'db_error',
270
					sprintf(
271
						__( 'Failed to delete post ID %d.', 'wordpress-github-sync' ),
272
						$post_id
273
					)
274
				);
275
			}
276
		}
277
278 4
		return sprintf(
279 4
			__( 'Successfully deleted post ID %d.', 'wordpress-github-sync' ),
280
			$post_id
281 4
		);
282
	}
283
284
	/**
285
	 * Returns the list of post type permitted.
286
	 *
287
	 * @return array
288
	 */
289 10
	protected function get_whitelisted_post_types() {
290 10
		return apply_filters( 'wpghs_whitelisted_post_types', $this->whitelisted_post_types );
291
	}
292
293
	/**
294
	 * Returns the list of post status permitted.
295
	 *
296
	 * @return array
297
	 */
298 7
	protected function get_whitelisted_post_statuses() {
299 7
		return apply_filters( 'wpghs_whitelisted_post_statuses', $this->whitelisted_post_statuses );
300
	}
301
302
	/**
303
	 * Formats a whitelist array for a query.
304
	 *
305
	 * @param array $whitelist Whitelisted posts to format into query.
306
	 *
307
	 * @return string Whitelist formatted for query
308
	 */
309
	protected function format_for_query( $whitelist ) {
310
		foreach ( $whitelist as $key => $value ) {
311
			$whitelist[ $key ] = "'$value'";
312
		}
313
314
		return implode( ', ', $whitelist );
315
	}
316
317
	/**
318
	 * Verifies that both the post's status & type
319
	 * are currently whitelisted
320
	 *
321
	 * @param  WordPress_GitHub_Sync_Post $post Post to verify.
322
	 *
323
	 * @return boolean                          True if supported, false if not.
324
	 */
325 5
	protected function is_post_supported( WordPress_GitHub_Sync_Post $post ) {
326 5
		if ( wp_is_post_revision( $post->id ) ) {
327 1
			return false;
328
		}
329
330
		// We need to allow trashed posts to be queried, but they are not whitelisted for export.
331 4
		if ( ! in_array( $post->status(), $this->get_whitelisted_post_statuses() ) && 'trash' !== $post->status() ) {
332 1
			return false;
333
		}
334
335 3
		if ( ! in_array( $post->type(), $this->get_whitelisted_post_types() ) ) {
336 1
			return false;
337
		}
338
339 2
		if ( $post->has_password() ) {
340 1
			return false;
341
		}
342
343 1
		return apply_filters( 'wpghs_is_post_supported', true, $post );
344
	}
345
346
	/**
347
	 * Retrieves the commit user for a provided email address.
348
	 *
349
	 * Searches for a user with provided email address or returns
350
	 * the default user saved in the database.
351
	 *
352
	 * @param string $email User email address to search for.
353
	 *
354
	 * @return WP_Error|WP_User
355
	 */
356 6
	protected function fetch_commit_user( $email ) {
357
		// If we can't find a user and a default hasn't been set,
358
		// we're just going to set the revision author to 0.
359 6
		$user = get_user_by( 'email', $email );
360
361 6
		if ( ! $user ) {
362
			// Use the default user.
363 4
			$user = get_user_by( 'id', (int) get_option( 'wpghs_default_user' ) );
364 4
		}
365
366 6
		if ( ! $user ) {
367 2
			return new WP_Error(
368 2
				'user_not_found',
369 2
				sprintf(
370 2
					__( 'Commit user not found for email %s', 'wordpress-github-sync' ),
371
					$email
372 2
				)
373 2
			);
374
		}
375
376 4
		return $user;
377
	}
378
379
	/**
380
	 * Sets the author latest revision
381
	 * of the provided post ID to the provided user.
382
	 *
383
	 * @param int $post_id Post ID to update revision author.
384
	 * @param int $user_id User ID for revision author.
385
	 *
386
	 * @return string|WP_Error
387
	 */
388 6
	protected function set_revision_author( $post_id, $user_id ) {
389 6
		$revision = wp_get_post_revisions( $post_id );
390
391 6
		if ( ! $revision ) {
392 3
			$new_revision = wp_save_post_revision( $post_id );
393
394 3
			if ( ! $new_revision || is_wp_error( $new_revision ) ) {
395
				return new WP_Error( 'db_error', 'There was a problem saving a new revision.' );
396
			}
397
398
			// `wp_save_post_revision` returns the ID, whereas `get_post_revision` returns the whole object
399
			// in order to be consistent, let's make sure we have the whole object before continuing.
400 3
			$revision = get_post( $new_revision );
401
402 3
			if ( ! $revision ) {
403
				return new WP_Error( 'db_error', 'There was a problem retrieving the newly recreated revision.' );
404
			}
405 3
		} else {
406 3
			$revision = array_shift( $revision );
407
		}
408
409 6
		return $this->set_post_author( $revision->ID, $user_id );
410
	}
411
412
	/**
413
	 * Updates the user ID for the provided post ID.
414
	 *
415
	 * Bypassing triggering any hooks, including creating new revisions.
416
	 *
417
	 * @param int $post_id Post ID to update.
418
	 * @param int $user_id User ID to update to.
419
	 *
420
	 * @return string|WP_Error
421
	 */
422 6
	protected function set_post_author( $post_id, $user_id ) {
423 6
		global $wpdb;
424
425 6
		$result = $wpdb->update(
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
426 6
			$wpdb->posts,
427
			array(
428 6
				'post_author' => (int) $user_id,
429 6
			),
430
			array(
431 6
				'ID' => (int) $post_id,
432 6
			),
433 6
			array( '%d' ),
434 6
			array( '%d' )
435 6
		);
436
437 6
		if ( false === $result ) {
438
			return new WP_Error( 'db_error', $wpdb->last_error );
439
		}
440
441 6
		if ( 0 === $result ) {
442 2
			return sprintf(
443 2
				__( 'No change for post ID %d.', 'wordpress-github-sync' ),
444
				$post_id
445 2
			);
446
		}
447
448 4
		clean_post_cache( $post_id );
449
450 4
		return sprintf(
451 4
			__( 'Successfully updated post ID %d.', 'wordpress-github-sync' ),
452
			$post_id
453 4
		);
454
	}
455
456
	/**
457
	 * Update the provided post's blob sha.
458
	 *
459
	 * @param WordPress_GitHub_Sync_Post $post Post to update.
460
	 * @param string                     $sha Sha to update to.
461
	 *
462
	 * @return bool|int
463
	 */
464 1
	public function set_post_sha( $post, $sha ) {
465 1
		return update_post_meta( $post->id, '_sha', $sha );
466
	}
467
}
468