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

WordPress_GitHub_Sync_Database::sha_exists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
/**
4
 * Database interface.
5
 * @package WordPress_GitHub_Sync
6
 */
7
8
/**
9
 * Class WordPress_GitHub_Sync_Database
10
 */
11
class WordPress_GitHub_Sync_Database {
12
13
	/**
14
	 * Application container.
15
	 *
16
	 * @var WordPress_GitHub_Sync
17
	 */
18
	protected $app;
19
20
	/**
21
	 * Cached list of currenly used shas
22
	 *
23
	 * @var array
24
	 */
25
	protected $sha_list;
26
27
	/**
28
	 * Currently whitelisted post types.
29
	 *
30
	 * @var array
31
	 */
32
	protected $whitelisted_post_types = array( 'post', 'page' );
33
34
	/**
35
	 * Currently whitelisted post statuses.
36
	 *
37
	 * @var array
38
	 */
39
	protected $whitelisted_post_statuses = array( 'publish' );
40
41
	/**
42
	 * Instantiates a new Database object.
43
	 *
44
	 * @param WordPress_GitHub_Sync $app Application container.
45
	 */
46
	public function __construct( WordPress_GitHub_Sync $app ) {
47
		$this->app = $app;
48
	}
49
50
	/**
51
	 * Queries the database for all of the supported posts.
52
	 *
53
	 * @return WordPress_GitHub_Sync_Post[]|WP_Error
54
	 */
55
	public function fetch_all_supported() {
56
		$args  = array(
57
			'post_type'   => $this->get_whitelisted_post_types(),
58
			'post_status' => $this->get_whitelisted_post_statuses(),
59
			'nopaging'    => true,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set nopaging to true ever.
Loading history...
60
			'fields'      => 'ids',
61
		);
62
63
		$query = new WP_Query( apply_filters( 'wpghs_pre_fetch_all_supported', $args ) );
64
65
		$post_ids = $query->get_posts();
66
67
		if ( ! $post_ids ) {
68
			return new WP_Error(
69
				'no_results',
70
				__( 'Querying for supported posts returned no results.', 'wordpress-github-sync' )
71
			);
72
		}
73
74
		$results = array();
75
		foreach ( $post_ids as $post_id ) {
76
			$results[] = new WordPress_GitHub_Sync_Post( $post_id, $this->app->api() );
77
		}
78
79
		return $results;
80
	}
81
82
	/**
83
	 * Queries a post and returns it if it's supported.
84
	 *
85
	 * @param int $post_id Post ID to fetch.
86
	 *
87
	 * @return WP_Error|WordPress_GitHub_Sync_Post
88
	 */
89
	public function fetch_by_id( $post_id ) {
90
		$post = new WordPress_GitHub_Sync_Post( $post_id, $this->app->api() );
91
92
		if ( ! $this->is_post_supported( $post ) ) {
93
			return new WP_Error(
94
				'unsupported_post',
95
				sprintf(
96
					__(
97
						'Post ID %s is not supported by WPGHS. See wiki to find out how to add support.',
98
						'wordpress-github-sync'
99
					),
100
					$post_id
101
				)
102
			);
103
		}
104
105
		return $post;
106
	}
107
108
	/**
109
	 * Queries for a post by provided sha.
110
	 *
111
	 * @param string $sha Post sha to fetch by.
112
	 *
113
	 * @return WordPress_GitHub_Sync_Post|WP_Error
114
	 */
115
	public function fetch_by_sha( $sha ) {
116
		$query = new WP_Query( array(
117
			'meta_key'       => '_sha',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
118
			'meta_value'     => $sha,
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
119
			'meta_compare'   => '=',
120
			'posts_per_page' => 1,
121
			'fields'         => 'ids',
122
		) );
123
124
		$post_id = $query->get_posts();
125
		$post_id = array_pop( $post_id );
126
127
		if ( ! $post_id ) {
128
			return new WP_Error(
129
				'sha_not_found',
130
				sprintf(
131
					__(
132
						'Post for sha %s not found.',
133
						'wordpress-github-sync'
134
					),
135
					$sha
136
				)
137
			);
138
		}
139
140
		return new WordPress_GitHub_Sync_Post( $post_id, $this->app->api() );
141
	}
142
143
	/**
144
	 * Generates a cache of currenly used shas and
145
	 * their accociated paths
146
	 *
147
	 * @return Array
148
	 */
149
	public function get_sha_list(){
150
		if(!$this->sha_list){
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sha_list 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...
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
introduced by
No space after opening parenthesis is prohibited
Loading history...
introduced by
Expected 1 space before "!"; 0 found
Loading history...
introduced by
Expected 1 space after "!"; 0 found
Loading history...
introduced by
No space before closing parenthesis is prohibited
Loading history...
151
			global $wpdb;
152
			$results = $wpdb->get_results("
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
153
				SELECT
154
					post_id as id,
155
					(select meta_value from $wpdb->postmeta where post_id = id and meta_key = '_sha' ) as sha,
156
					(select meta_value from $wpdb->postmeta where post_id = id and meta_key = '_wpghs_github_path' ) as path
157
				FROM $wpdb->postmeta
158
				WHERE meta_key = '_sha'
159
			");
160
			$this->sha_list = [];
161
			foreach ($results as &$data) {
0 ignored issues
show
introduced by
No space after opening parenthesis is prohibited
Loading history...
introduced by
No space before closing parenthesis is prohibited
Loading history...
162
				$this->sha_list[$data->sha] = $data;
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
163
			}
164
		}
165
		return $this->sha_list;
166
	}
167
168
	/**
169
	 * Checks to see is a sha is in use.
170
	 *
171
	 * @param string $sha Post sha to check for.
172
     *
0 ignored issues
show
Coding Style introduced by
Tabs must be used to indent lines; spaces are not allowed
Loading history...
173
	 * @return Booelan
174
	 */
175
	public function sha_exists($sha){
176
		$sha_list = $this->get_sha_list();
0 ignored issues
show
Unused Code introduced by
$sha_list 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...
177
		return isset($this->sha_list[$sha]);
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...
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
178
	}
179
180
	/**
181
	 * Checks to see is a sha is in use, and that the
182
	 * accociated path matches the given path
183
	 *
184
	 * @param string $sha Post sha to check for.
185
	 * @param string $path Patn to match the sha to.
186
	 *
187
	 * @return Booelan
188
	 */
189
	public function sha_exists_with_path($sha, $path){
190
		$sha_list = $this->get_sha_list();
0 ignored issues
show
Unused Code introduced by
$sha_list 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...
191
		if(isset($this->sha_list[$sha])){
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
No space after 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
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
introduced by
No space before closing parenthesis is prohibited
Loading history...
192
			$data = $this->sha_list[$sha];
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
193
			return ($data->path == $path);
194
		}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...
195
			return false;
196
		}
197
	}
198
199
	public function get_post_by_path($path){
200
		$sha_list = $this->get_sha_list();
201
		foreach ($sha_list as $key => $value) {
0 ignored issues
show
introduced by
No space after opening parenthesis is prohibited
Loading history...
introduced by
No space before closing parenthesis is prohibited
Loading history...
202
			if($value->path==$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...
introduced by
No space after opening parenthesis is prohibited
Loading history...
introduced by
Expected 1 space before "=="; 0 found
Loading history...
introduced by
Expected 1 space after "=="; 0 found
Loading history...
introduced by
No space before closing parenthesis is prohibited
Loading history...
203
				return $value->id;
204
			}
205
		}
206
		return false;
207
	}
208
209
	/**
210
	 * Saves an array of Post objects to the database
211
	 * and associates their author as well as their latest
212
	 *
213
	 * @param WordPress_GitHub_Sync_Post[] $posts Array of Posts to save.
214
	 * @param string                       $email Author email.
215
	 *
216
	 * @return string|WP_Error
217
	 */
218
	public function save_posts( array $posts, $email ) {
219
		$user    = $this->fetch_commit_user( $email );
220
		$user_id = ! is_wp_error( $user ) ? $user->ID : 0;
221
222
		/**
223
		 * Whether an error has occurred.
224
		 *
225
		 * @var WP_Error|false $error
226
		 */
227
		$error = false;
228
229
		foreach ( $posts as $post ) {
230
			$args    = apply_filters( 'wpghs_pre_import_args', $post->get_args(), $post );
231
			$post_id = $post->is_new() ?
232
				wp_insert_post( $args, true ) :
233
				wp_update_post( $args, true );
234
235
			if ( is_wp_error( $post_id ) ) {
236
				if ( ! $error ) {
237
					$error = $post_id;
238
				} else {
239
					$error->add( $post_id->get_error_code(), $post_id->get_error_message() );
240
				}
241
242
				// Abort saving if updating the post fails.
243
				continue;
244
			}
245
246
			$this->set_revision_author( $post_id, $user_id );
247
248
			if ( $post->is_new() ) {
249
				$this->set_post_author( $post_id, $user_id );
250
			}
251
252
			$post->set_post( get_post( $post_id ) );
253
254
			$meta = apply_filters( 'wpghs_pre_import_meta', $post->get_meta(), $post );
255
256
			foreach ( $meta as $key => $value ) {
257
				update_post_meta( $post_id, $key, $value );
258
			}
259
		}
260
261
		if ( $error ) {
262
			return $error;
263
		}
264
265
		return __( 'Successfully saved posts.', 'wordpress-github-sync' );
266
	}
267
268
	/**
269
	 * Deletes a post from the database based on its GitHub path.
270
	 *
271
	 * @param string $path Path of Post to delete.
272
	 *
273
	 * @return string|WP_Error
274
	 */
275
	public function delete_post_by_path( $path ) {
276
		$query = new WP_Query( array(
277
			'meta_key'       => '_wpghs_github_path',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
278
			'meta_value'     => $path,
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
279
			'meta_compare'   => '=',
280
			'posts_per_page' => 1,
281
			'fields'         => 'ids',
282
		) );
283
284
		$post_id = $query->get_posts();
285
		$post_id = array_pop( $post_id );
286
287
		if ( $post_id ) {
288
289
			$result = wp_delete_post( $post_id );
290
291
		}
292
293
		// If deleting fails...
294
		if ( false === $result ) {
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
295
			$post = get_post( $post_id );
296
297
			// ...and the post both exists and isn't in the trash...
298
			if ( $post && 'trash' !== $post->post_status ) {
299
				// ... then something went wrong.
300
				return new WP_Error(
301
					'db_error',
302
					sprintf(
303
						__( 'Failed to delete post ID %d.', 'wordpress-github-sync' ),
304
						$post_id
305
					)
306
				);
307
			}
308
		}
309
310
		return sprintf(
311
			__( 'Successfully deleted post ID %d.', 'wordpress-github-sync' ),
312
			$post_id
313
		);
314
	}
315
316
	/**
317
	 * Returns the list of post type permitted.
318
	 *
319
	 * @return array
320
	 */
321
	protected function get_whitelisted_post_types() {
322
		return apply_filters( 'wpghs_whitelisted_post_types', $this->whitelisted_post_types );
323
	}
324
325
	/**
326
	 * Returns the list of post status permitted.
327
	 *
328
	 * @return array
329
	 */
330
	protected function get_whitelisted_post_statuses() {
331
		return apply_filters( 'wpghs_whitelisted_post_statuses', $this->whitelisted_post_statuses );
332
	}
333
334
	/**
335
	 * Formats a whitelist array for a query.
336
	 *
337
	 * @param array $whitelist Whitelisted posts to format into query.
338
	 *
339
	 * @return string Whitelist formatted for query
340
	 */
341
	protected function format_for_query( $whitelist ) {
342
		foreach ( $whitelist as $key => $value ) {
343
			$whitelist[ $key ] = "'$value'";
344
		}
345
346
		return implode( ', ', $whitelist );
347
	}
348
349
	/**
350
	 * Verifies that both the post's status & type
351
	 * are currently whitelisted
352
	 *
353
	 * @param  WordPress_GitHub_Sync_Post $post Post to verify.
354
	 *
355
	 * @return boolean                          True if supported, false if not.
356
	 */
357
	protected function is_post_supported( WordPress_GitHub_Sync_Post $post ) {
358
		if ( wp_is_post_revision( $post->id ) ) {
359
			return false;
360
		}
361
362
		// We need to allow trashed posts to be queried, but they are not whitelisted for export.
363
		if ( ! in_array( $post->status(), $this->get_whitelisted_post_statuses() ) && 'trash' !== $post->status() ) {
364
			return false;
365
		}
366
367
		if ( ! in_array( $post->type(), $this->get_whitelisted_post_types() ) ) {
368
			return false;
369
		}
370
371
		if ( $post->has_password() ) {
372
			return false;
373
		}
374
375
		return apply_filters( 'wpghs_is_post_supported', true, $post );
376
	}
377
378
	/**
379
	 * Retrieves the commit user for a provided email address.
380
	 *
381
	 * Searches for a user with provided email address or returns
382
	 * the default user saved in the database.
383
	 *
384
	 * @param string $email User email address to search for.
385
	 *
386
	 * @return WP_Error|WP_User
387
	 */
388
	protected function fetch_commit_user( $email ) {
389
		// If we can't find a user and a default hasn't been set,
390
		// we're just going to set the revision author to 0.
391
		$user = get_user_by( 'email', $email );
392
393
		if ( ! $user ) {
394
			// Use the default user.
395
			$user = get_user_by( 'id', (int) get_option( 'wpghs_default_user' ) );
396
		}
397
398
		if ( ! $user ) {
399
			return new WP_Error(
400
				'user_not_found',
401
				sprintf(
402
					__( 'Commit user not found for email %s', 'wordpress-github-sync' ),
403
					$email
404
				)
405
			);
406
		}
407
408
		return $user;
409
	}
410
411
	/**
412
	 * Sets the author latest revision
413
	 * of the provided post ID to the provided user.
414
	 *
415
	 * @param int $post_id Post ID to update revision author.
416
	 * @param int $user_id User ID for revision author.
417
	 *
418
	 * @return string|WP_Error
419
	 */
420
	protected function set_revision_author( $post_id, $user_id ) {
421
		$revision = wp_get_post_revisions( $post_id );
422
423
		if ( ! $revision ) {
424
			$new_revision = wp_save_post_revision( $post_id );
425
426
			if ( ! $new_revision || is_wp_error( $new_revision ) ) {
427
				return new WP_Error( 'db_error', 'There was a problem saving a new revision.' );
428
			}
429
430
			// `wp_save_post_revision` returns the ID, whereas `get_post_revision` returns the whole object
431
			// in order to be consistent, let's make sure we have the whole object before continuing.
432
			$revision = get_post( $new_revision );
433
434
			if ( ! $revision ) {
435
				return new WP_Error( 'db_error', 'There was a problem retrieving the newly recreated revision.' );
436
			}
437
		} else {
438
			$revision = array_shift( $revision );
439
		}
440
441
		return $this->set_post_author( $revision->ID, $user_id );
442
	}
443
444
	/**
445
	 * Updates the user ID for the provided post ID.
446
	 *
447
	 * Bypassing triggering any hooks, including creating new revisions.
448
	 *
449
	 * @param int $post_id Post ID to update.
450
	 * @param int $user_id User ID to update to.
451
	 *
452
	 * @return string|WP_Error
453
	 */
454
	protected function set_post_author( $post_id, $user_id ) {
455
		global $wpdb;
456
457
		$result = $wpdb->update(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
458
			$wpdb->posts,
459
			array(
460
				'post_author' => (int) $user_id,
461
			),
462
			array(
463
				'ID' => (int) $post_id,
464
			),
465
			array( '%d' ),
466
			array( '%d' )
467
		);
468
469
		if ( false === $result ) {
470
			return new WP_Error( 'db_error', $wpdb->last_error );
471
		}
472
473
		if ( 0 === $result ) {
474
			return sprintf(
475
				__( 'No change for post ID %d.', 'wordpress-github-sync' ),
476
				$post_id
477
			);
478
		}
479
480
		clean_post_cache( $post_id );
481
482
		return sprintf(
483
			__( 'Successfully updated post ID %d.', 'wordpress-github-sync' ),
484
			$post_id
485
		);
486
	}
487
488
	/**
489
	 * Update the provided post's blob sha.
490
	 *
491
	 * @param WordPress_GitHub_Sync_Post $post Post to update.
492
	 * @param string                     $sha Sha to update to.
493
	 *
494
	 * @return bool|int
495
	 */
496
	public function set_post_sha( $post, $sha ) {
497
		return update_post_meta( $post->id, '_sha', $sha );
498
	}
499
}