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 (#988)
by Paul
02:34
created

Path::get_home_path()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 1
Metric Value
c 7
b 0
f 1
dl 0
loc 35
rs 5.3846
cc 8
eloc 23
nc 8
nop 1
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 ) {
1 ignored issue
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...
Coding Style introduced by
get_home_path uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
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
		if ( path_in_php_open_basedir( dirname( $site_path ) ) ) {
107
108
			$home    = set_url_scheme( get_option( 'home' ), 'http' );
109
			$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
110
			if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
111
				$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
112
				$pos = strripos( wp_normalize_path( $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
113
				$home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
114
				$home_path = trailingslashit( $home_path );
115
			} else {
116
				$home_path = wp_normalize_path( ABSPATH );
117
			}
118
119
			if ( is_multisite() ) {
120
				require_once ABSPATH . '/wp-admin/includes/network.php';
121
				$hostname          = get_clean_basedomain();
0 ignored issues
show
Unused Code introduced by
$hostname 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...
122
				$slashed_home      = trailingslashit( get_option( 'home' ) );
123
				$base              = parse_url( $slashed_home, PHP_URL_PATH );
124
				$document_root_fix = wp_normalize_path( realpath( $_SERVER['DOCUMENT_ROOT'] ) );
125
				$abspath_fix       = wp_normalize_path( ABSPATH );
126
				$home_path         = 0 === strpos( $abspath_fix, $document_root_fix ) ? $document_root_fix . $base : $home_path;
127
			}
128
		}
129
130
		return wp_normalize_path( untrailingslashit( $home_path ) );
131
132
	}
133
134
	/**
135
	 * get the calculated path to the directory where backups will be stored
136
	 */
137
	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...
138
139
		// Calculate the path if needed
140
		if ( empty( $this->path ) || ! wp_is_writable( $this->path ) ) {
141
			$this->calculate_path();
142
		}
143
144
		// Ensure the backup directory is protected
145
		$this->protect_path();
146
147
		return wp_normalize_path( $this->path );
148
149
	}
150
151
	/**
152
	 * Set the path directly, overriding the default
153
	 *
154
	 * @param $path
155
	 */
156
	public function set_path( $path ) {
157
158
		$this->custom_path = $path;
159
160
		// Re-calculate the backup path
161
		$this->calculate_path();
162
163
	}
164
165
	/**
166
	 * get the calculated path to the directory that will be backed up
167
	 */
168
	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...
169
170
		$root = self::get_home_path();
171
172
		if ( defined( 'HMBKP_ROOT' ) && HMBKP_ROOT ) {
173
			$root = HMBKP_ROOT;
174
		}
175
176
		if ( $this->root ) {
177
			$root = $this->root;
178
		}
179
180
		return wp_normalize_path( $root );
181
182
	}
183
184
	/**
185
	 * Set the root path directly, overriding the default
186
	 *
187
	 * @param $root
188
	 */
189
	public function set_root( $root ) {
190
		$this->root = $root;
191
	}
192
193
	public function reset_path() {
194
		$this->set_path( false );
195
	}
196
197
	/**
198
	 * Get the path to the default backup location in wp-content
199
	 */
200
	public function get_default_path() {
201
		return trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ) . 'backupwordpress-' . substr( HMBKP_SECURE_KEY, 0, 10 ) . '-backups';
202
	}
203
204
	/**
205
	 * Get the path to the fallback backup location in uploads
206
	 */
207
	public function get_fallback_path() {
208
209
		$upload_dir = wp_upload_dir();
210
211
		return trailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . 'backupwordpress-' . substr( HMBKP_SECURE_KEY, 0, 10 ) . '-backups';
212
213
	}
214
215
	/**
216
	 * Get the path to the custom backup location if it's been set
217
	 */
218
	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...
219
220
		if ( $this->custom_path ) {
221
			return $this->custom_path;
222
		}
223
224
		if ( defined( 'HMBKP_PATH' ) && wp_is_writable( HMBKP_PATH ) ) {
225
			return HMBKP_PATH;
226
		}
227
228
		return '';
229
230
	}
231
232
	/**
233
	 * Builds an array containing existing backups folders.
234
	 *
235
	 * @return array
236
	 */
237
	public function get_existing_paths() {
238
239
		if ( false === $default = glob( WP_CONTENT_DIR . '/backupwordpress-*-backups', GLOB_ONLYDIR ) ) {
240
			$default = array();
241
		}
242
243
		$upload_dir = wp_upload_dir();
244
245
		if ( false === $fallback = glob( $upload_dir['basedir'] . '/backupwordpress-*-backups', GLOB_ONLYDIR ) ) {
246
			$fallback = array();
247
		}
248
249
		$paths = array_merge( $default, $fallback );
250
251
        $paths = array_map( 'wp_normalize_path', $paths );
252
253
		return $paths;
254
255
	}
256
257
	/**
258
	 * Returns the first existing path if there is one
259
	 *
260
	 * @return string Backup path if found empty string if not
261
	 */
262
	public function get_existing_path() {
263
264
		$paths = $this->get_existing_paths();
265
266
		if ( ! empty( $paths[0] ) ) {
267
			return $paths[0];
268
		}
269
270
		return '';
271
272
	}
273
274
	/**
275
	 * Calculate the backup path and create the directory if it doesn't exist.
276
	 *
277
	 * Tries all possible locations and uses the first one possible
278
	 *
279
	 * @return
280
	 */
281
	public function calculate_path() {
282
283
		$paths = array();
284
285
		// If we have a custom path then try to use it
286
		if ( $this->get_custom_path() ) {
287
			$paths[] = $this->get_custom_path();
288
		}
289
290
		// If there is already a backups directory then try to use that
291
		if ( $this->get_existing_path() ) {
292
			$paths[] = $this->get_existing_path();
293
		}
294
295
		// If not then default to a new directory in wp-content
296
		$paths[] = $this->get_default_path();
297
298
		// If that didn't work then fallback to a new directory in uploads
299
		$paths[] = $this->get_fallback_path();
300
301
		// Loop through possible paths, use the first one that exists/can be created and is writable
302
		foreach ( $paths as $path ) {
303
			if ( wp_mkdir_p( $path ) && wp_is_writable( $path ) ) { // Also handles fixing perms / directory already exists
304
				break;
305
			}
306
		}
307
308
		if ( file_exists( $path ) && wp_is_writable( $path ) ) {
309
			$this->path = $path;
0 ignored issues
show
Bug introduced by
The variable $path seems to be defined by a foreach iteration on line 302. 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...
310
		}
311
312
	}
313
314
	/**
315
	 * Protect the directory that backups are stored in
316
	 *
317
	 * - Adds an index.html file in an attempt to disable directory browsing
318
	 * - Adds a .httaccess file to deny direct access if on Apache
319
	 *
320
	 * @param string $reset
321
	 */
322
	public function protect_path( $reset = 'no' ) {
323
324
		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...
325
326
		// Protect against directory browsing by including an index.html file
327
		$index = $this->path . '/index.html';
328
329
		if ( 'reset' === $reset && file_exists( $index ) ) {
330
			@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...
331
		}
332
333
		if ( ! file_exists( $index ) && wp_is_writable( $this->path ) ) {
334
			file_put_contents( $index, '' );
335
		}
336
337
		$htaccess = $this->path . '/.htaccess';
338
339
		if ( ( 'reset' === $reset ) && file_exists( $htaccess ) ) {
340
			@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...
341
		}
342
343
		// Protect the directory with a .htaccess file on Apache servers
344
		if ( $is_apache && function_exists( 'insert_with_markers' ) && ! file_exists( $htaccess ) && wp_is_writable( $this->path ) ) {
345
346
			$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...
347
			$contents[] = '';
348
			$contents[] = '<IfModule mod_rewrite.c>';
349
			$contents[] = 'RewriteEngine On';
350
			$contents[] = 'RewriteCond %{QUERY_STRING} !key=' . HMBKP_SECURE_KEY;
351
			$contents[] = 'RewriteRule (.*) - [F]';
352
			$contents[] = '</IfModule>';
353
			$contents[] = '';
354
355
			file_put_contents( $htaccess, '' );
356
357
			insert_with_markers( $htaccess, 'BackUpWordPress', $contents );
358
359
		}
360
361
	}
362
363
	/**
364
	 * If we have more than one path then move any existing backups to the current path and remove them
365
	 */
366
	public function merge_existing_paths() {
367
368
		$paths = $this->get_existing_paths();
369
370
		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...
371
			foreach ( $paths as $old_path ) {
372
				$this->move_old_backups( $old_path );
373
			}
374
		}
375
376
	}
377
378
	/**
379
	 * Move backup files from an existing directory and the new
380
	 * location
381
	 *
382
	 * @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...
383
	 */
384
	public function move_old_backups( $from ) {
385
386
		if ( ! is_readable( $from ) ) {
387
			return;
388
		}
389
390
		if ( ! wp_is_writable( Path::get_path() ) ) {
391
			return;
392
		}
393
394
		// Move any existing backups
395
		if ( $handle = opendir( $from ) ) {
396
397
			// Loop through the backup directory
398
			while ( false !== ( $file = readdir( $handle ) ) ) {
399
400
				// Find all zips
401
				if ( 'zip' === pathinfo( $file, PATHINFO_EXTENSION ) ) {
402
403
					// Try to move them
404
					if ( ! @rename( trailingslashit( $from ) . $file, trailingslashit( Path::get_path() ) . $file ) ) {
405
406
407
						// If we can't move them then try to copy them
408
						copy( trailingslashit( $from ) . $file, trailingslashit( Path::get_path() ) . $file );
409
410
					}
411
412
				}
413
			}
414
415
			closedir( $handle );
416
417
		}
418
419
		// Delete the old directory if it's inside WP_CONTENT_DIR
420
		if ( false !== strpos( $from, WP_CONTENT_DIR ) && $from !== Path::get_path() ) {
421
			rmdirtree( $from );
422
		}
423
424
	}
425
426
	/**
427
	 * Clean any temporary / incomplete backups from the backups directory
428
	 */
429
	public function cleanup() {
430
431
		// Don't cleanup a custom path, who knows what other stuff is there
432
		if ( Path::get_path() === $this->get_custom_path() ) {
433
			return;
434
		}
435
436
		foreach ( new CleanUpIterator( new \DirectoryIterator( $this->path ) ) as $file ) {
437
438
			if ( $file->isDot() || ! $file->isReadable() || ! $file->isFile() ) {
439
				continue;
440
			}
441
442
			@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...
443
444
		}
445
446
	}
447
448
}
449
450
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...
451
452
	// Don't match index.html,files with zip extension or status logfiles.
453
	public function accept() {
454
		return ! preg_match( '/(index\.html|.*\.zip|.*-running)/', $this->current() );
455
	}
456
}
457