GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#936)
by Tom
02:44
created

Path   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 424
Duplicated Lines 1.42 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 76
c 5
b 0
f 0
lcom 1
cbo 1
dl 6
loc 424
rs 5.4881

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 1 1
A __clone() 0 1 1
A __wakeup() 0 1 1
A get_instance() 0 8 2
A get_path() 0 3 1
A get_root() 0 3 1
B get_home_path() 6 21 9
A get_calculated_path() 0 13 3
A set_path() 0 8 1
A get_calculated_root() 0 15 4
A set_root() 0 3 1
A reset_path() 0 3 1
A get_default_path() 0 3 1
A get_fallback_path() 0 7 1
A get_custom_path() 0 13 4
A get_existing_paths() 0 19 3
A get_existing_path() 0 11 2
C calculate_path() 0 32 8
C protect_path() 0 40 11
B merge_existing_paths() 0 11 5
D move_old_backups() 0 41 9
B cleanup() 0 18 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
namespace HM\BackUpWordPress;
4
5
/**
6
 * Manages both the backup path and site root
7
 *
8
 * Handles calculating & protecting the directory that backups will be stored in
9
 * as well as the directory that is being backed up
10
 */
11
class Path {
12
13
	/**
14
	 * The path to the directory that backup files are stored in
15
	 *
16
	 * @var string $this->path
17
	 */
18
	private $path;
19
20
	/**
21
	 * The path to the directory that will be backed up
22
	 *
23
	 * @var string $this->root
24
	 */
25
	private $root;
26
27
	/**
28
	 * The path to the directory that backup files are stored in
29
	 *
30
	 * @var string $this->path
31
	 */
32
	private $custom_path;
33
34
	/**
35
	 * Contains the instantiated Path instance
36
	 *
37
	 * @var Path $this->instance
38
	 */
39
	private static $instance;
40
41
	/**
42
	 * Protected constructor to prevent creating a new instance of the
43
	 * *Singleton* via the `new` operator from outside of this class.
44
	 */
45
	protected function __construct() {}
46
47
	/**
48
	 * Private clone method to prevent cloning of the instance of the
49
	 * *Singleton* instance.
50
	 */
51
	private function __clone() {}
52
53
	/**
54
	 * Private unserialize method to prevent unserializing of the *Singleton*
55
	 * instance.
56
	 */
57
	private function __wakeup() {}
58
59
	/**
60
	 * Returns the *Singleton* instance of this class.
61
	 *
62
	 * @staticvar Path $instance The *Singleton* instances of this class.
63
	 *
64
	 * @return Path The *Singleton* instance.
65
	 */
66
	public static function get_instance() {
67
68
		if ( ! ( self::$instance instanceof Path ) ) {
69
			self::$instance = new Path();
70
		}
71
72
		return self::$instance;
73
	}
74
75
	/**
76
	 * Convenience method for quickly grabbing the path
77
	 */
78
	public static function get_path() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
79
		return self::get_instance()->get_calculated_path();
80
	}
81
82
	/**
83
	 * Convenience method for quickly grabbing the root
84
	 */
85
	public static function get_root() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
86
		return self::get_instance()->get_calculated_root();
87
	}
88
89
	/**
90
	 * Calculate the path to the site "home" directory.
91
	 *
92
	 * The home directory is the path equivalent to the home_url. That is,
93
	 * the path to the true root of the website. In situations where WordPress is
94
	 * installed in a subdirectory the home path is different to ABSPATH
95
	 *
96
	 * @param string $site_path The site_path to use when calculating the home path, defaults to ABSPATH
97
	 */
98
	public static function get_home_path( $site_path = ABSPATH ) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
99
100
		if ( defined( 'HMBKP_ROOT' ) && HMBKP_ROOT ) {
101
			return wp_normalize_path( HMBKP_ROOT );
102
		}
103
104
		$home_path = $site_path;
105
106
		// Handle wordpress installed in a subdirectory
107 View Code Duplication
		if ( file_exists( dirname( $site_path ) . '/wp-config.php' ) && ! file_exists( $site_path . '/wp-config.php' ) && file_exists( dirname( $site_path ) . '/index.php' ) ) {
1 ignored issue
show
Duplication introduced by
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...
108
			$home_path = dirname( $site_path );
109
		}
110
111
		// Handle wp-config.php being above site_path
112 View Code Duplication
		if ( file_exists( dirname( $site_path ) . '/wp-config.php' ) && ! file_exists( $site_path . '/wp-config.php' ) && ! file_exists( dirname( $site_path ) . '/index.php' ) ) {
1 ignored issue
show
Duplication introduced by
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...
113
			$home_path = $site_path;
114
		}
115
116
		return wp_normalize_path( untrailingslashit( $home_path ) );
117
118
	}
119
120
	/**
121
	 * get the calculated path to the directory where backups will be stored
122
	 */
123
	private function get_calculated_path() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
124
125
		// Calculate the path if needed
126
		if ( empty( $this->path ) || ! wp_is_writable( $this->path ) ) {
127
			$this->calculate_path();
128
		}
129
130
		// Ensure the backup directory is protected
131
		$this->protect_path();
132
133
		return wp_normalize_path( $this->path );
134
135
	}
136
137
	/**
138
	 * Set the path directly, overriding the default
139
	 *
140
	 * @param $path
141
	 */
142
	public function set_path( $path ) {
143
144
		$this->custom_path = $path;
145
146
		// Re-calculate the backup path
147
		$this->calculate_path();
148
149
	}
150
151
	/**
152
	 * get the calculated path to the directory that will be backed up
153
	 */
154
	private function get_calculated_root() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
155
156
		$root = self::get_home_path();
157
158
		if ( defined( 'HMBKP_ROOT' ) && HMBKP_ROOT ) {
159
			$root = HMBKP_ROOT;
160
		}
161
162
		if ( $this->root ) {
163
			$root = $this->root;
164
		}
165
166
		return wp_normalize_path( $root );
167
168
	}
169
170
	/**
171
	 * Set the root path directly, overriding the default
172
	 *
173
	 * @param $root
174
	 */
175
	public function set_root( $root ) {
176
		$this->root = $root;
177
	}
178
179
	public function reset_path() {
180
		$this->set_path( false );
181
	}
182
183
	/**
184
	 * Get the path to the default backup location in wp-content
185
	 */
186
	public function get_default_path() {
187
		return trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ) . 'backupwordpress-' . substr( HMBKP_SECURE_KEY, 0, 10 ) . '-backups';
188
	}
189
190
	/**
191
	 * Get the path to the fallback backup location in uploads
192
	 */
193
	public function get_fallback_path() {
194
195
		$upload_dir = wp_upload_dir();
196
197
		return trailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . 'backupwordpress-' . substr( HMBKP_SECURE_KEY, 0, 10 ) . '-backups';
198
199
	}
200
201
	/**
202
	 * Get the path to the custom backup location if it's been set
203
	 */
204
	public function get_custom_path() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
205
206
		if ( $this->custom_path ) {
207
			return $this->custom_path;
208
		}
209
210
		if ( defined( 'HMBKP_PATH' ) && wp_is_writable( HMBKP_PATH ) ) {
211
			return HMBKP_PATH;
212
		}
213
214
		return '';
215
216
	}
217
218
	/**
219
	 * Builds an array containing existing backups folders.
220
	 *
221
	 * @return array
222
	 */
223
	public function get_existing_paths() {
224
225
		if ( false === $default = glob( WP_CONTENT_DIR . '/backupwordpress-*-backups', GLOB_ONLYDIR ) ) {
226
			$default = array();
227
		}
228
229
		$upload_dir = wp_upload_dir();
230
231
		if ( false === $fallback = glob( $upload_dir['basedir'] . '/backupwordpress-*-backups', GLOB_ONLYDIR ) ) {
232
			$fallback = array();
233
		}
234
235
		$paths = array_merge( $default, $fallback );
236
237
        $paths = array_map( 'wp_normalize_path', $paths );
238
239
		return $paths;
240
241
	}
242
243
	/**
244
	 * Returns the first existing path if there is one
245
	 *
246
	 * @return string Backup path if found empty string if not
247
	 */
248
	public function get_existing_path() {
249
250
		$paths = $this->get_existing_paths();
251
252
		if ( ! empty( $paths[0] ) ) {
253
			return $paths[0];
254
		}
255
256
		return '';
257
258
	}
259
260
	/**
261
	 * Calculate the backup path and create the directory if it doesn't exist.
262
	 *
263
	 * Tries all possible locations and uses the first one possible
264
	 *
265
	 * @return
266
	 */
267
	public function calculate_path() {
268
269
		$paths = array();
270
271
		// If we have a custom path then try to use it
272
		if ( $this->get_custom_path() ) {
273
			$paths[] = $this->get_custom_path();
274
		}
275
276
		// If there is already a backups directory then try to use that
277
		if ( $this->get_existing_path() ) {
278
			$paths[] = $this->get_existing_path();
279
		}
280
281
		// If not then default to a new directory in wp-content
282
		$paths[] = $this->get_default_path();
283
284
		// If that didn't work then fallback to a new directory in uploads
285
		$paths[] = $this->get_fallback_path();
286
287
		// Loop through possible paths, use the first one that exists/can be created and is writable
288
		foreach ( $paths as $path ) {
289
			if ( wp_mkdir_p( $path ) && wp_is_writable( $path ) ) { // Also handles fixing perms / directory already exists
290
				break;
291
			}
292
		}
293
294
		if ( file_exists( $path ) && wp_is_writable( $path ) ) {
295
			$this->path = $path;
0 ignored issues
show
Bug introduced by
The variable $path seems to be defined by a foreach iteration on line 288. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
296
		}
297
298
	}
299
300
	/**
301
	 * Protect the directory that backups are stored in
302
	 *
303
	 * - Adds an index.html file in an attempt to disable directory browsing
304
	 * - Adds a .httaccess file to deny direct access if on Apache
305
	 *
306
	 * @param string $reset
307
	 */
308
	public function protect_path( $reset = 'no' ) {
309
310
		global $is_apache;
1 ignored issue
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
311
312
		// Protect against directory browsing by including an index.html file
313
		$index = $this->path . '/index.html';
314
315
		if ( 'reset' === $reset && file_exists( $index ) ) {
316
			@unlink( $index );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
317
		}
318
319
		if ( ! file_exists( $index ) && wp_is_writable( $this->path ) ) {
320
			file_put_contents( $index, '' );
321
		}
322
323
		$htaccess = $this->path . '/.htaccess';
324
325
		if ( ( 'reset' === $reset ) && file_exists( $htaccess ) ) {
326
			@unlink( $htaccess );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
327
		}
328
329
		// Protect the directory with a .htaccess file on Apache servers
330
		if ( $is_apache && function_exists( 'insert_with_markers' ) && ! file_exists( $htaccess ) && wp_is_writable( $this->path ) ) {
331
332
			$contents[] = '# ' . sprintf( __( 'This %s file ensures that other people cannot download your backup files.', 'backupwordpress' ), '.htaccess' );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$contents was never initialized. Although not strictly required by PHP, it is generally a good practice to add $contents = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
333
			$contents[] = '';
334
			$contents[] = '<IfModule mod_rewrite.c>';
335
			$contents[] = 'RewriteEngine On';
336
			$contents[] = 'RewriteCond %{QUERY_STRING} !key=' . HMBKP_SECURE_KEY;
337
			$contents[] = 'RewriteRule (.*) - [F]';
338
			$contents[] = '</IfModule>';
339
			$contents[] = '';
340
341
			file_put_contents( $htaccess, '' );
342
343
			insert_with_markers( $htaccess, 'BackUpWordPress', $contents );
344
345
		}
346
347
	}
348
349
	/**
350
	 * If we have more than one path then move any existing backups to the current path and remove them
351
	 */
352
	public function merge_existing_paths() {
353
354
		$paths = $this->get_existing_paths();
355
356
		if ( ( $paths && $this->get_custom_path() ) || count( $paths ) > 1 ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $paths 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...
357
			foreach ( $paths as $old_path ) {
358
				$this->move_old_backups( $old_path );
359
			}
360
		}
361
362
	}
363
364
	/**
365
	 * Move backup files from an existing directory and the new
366
	 * location
367
	 *
368
	 * @param string $path 	The path to move the backups from
0 ignored issues
show
Bug introduced by
There is no parameter named $path. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
369
	 */
370
	public function move_old_backups( $from ) {
371
372
		if ( ! is_readable( $from ) ) {
373
			return;
374
		}
375
376
		if ( ! wp_is_writable( Path::get_path() ) ) {
377
			return;
378
		}
379
380
		// Move any existing backups
381
		if ( $handle = opendir( $from ) ) {
382
383
			// Loop through the backup directory
384
			while ( false !== ( $file = readdir( $handle ) ) ) {
385
386
				// Find all zips
387
				if ( 'zip' === pathinfo( $file, PATHINFO_EXTENSION ) ) {
388
389
					// Try to move them
390
					if ( ! @rename( trailingslashit( $from ) . $file, trailingslashit( Path::get_path() ) . $file ) ) {
391
392
393
						// If we can't move them then try to copy them
394
						copy( trailingslashit( $from ) . $file, trailingslashit( Path::get_path() ) . $file );
395
396
					}
397
398
				}
399
			}
400
401
			closedir( $handle );
402
403
		}
404
405
		// Delete the old directory if it's inside WP_CONTENT_DIR
406
		if ( false !== strpos( $from, WP_CONTENT_DIR ) && $from !== Path::get_path() ) {
407
			rmdirtree( $from );
408
		}
409
410
	}
411
412
	/**
413
	 * Clean any temporary / incomplete backups from the backups directory
414
	 */
415
	public function cleanup() {
416
417
		// Don't cleanup a custom path, who knows what other stuff is there
418
		if ( Path::get_path() === $this->get_custom_path() ) {
419
			return;
420
		}
421
422
		foreach ( new CleanUpIterator( new \DirectoryIterator( $this->path ) ) as $file ) {
423
424
			if ( $file->isDot() || ! $file->isReadable() || ! $file->isFile() ) {
425
				continue;
426
			}
427
428
			@unlink( $file->getPathname() );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
429
430
		}
431
432
	}
433
434
}
435
436
class CleanUpIterator extends \FilterIterator {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
437
438
	// Don't match index.html,files with zip extension or status logfiles.
439
	public function accept() {
440
		return ! preg_match( '/(index\.html|.*\.zip|.*-running)/', $this->current() );
441
	}
442
}
443