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:41
created

Site_Size::filesize()   C

Complexity

Conditions 11
Paths 8

Size

Total Lines 51
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 51
rs 5.7333
cc 11
eloc 19
nc 8
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace HM\BackUpWordPress;
4
5
use Symfony\Component\Finder\Finder;
6
7
/**
8
 * Site Size class
9
 *
10
 * Use to calculate the total or partial size of the sites database and files.
11
 */
12
class Site_Size {
13
14
	private $size = 0;
15
16
	/**
17
	 * Constructor
18
	 *
19
	 * Set up some initial conditions including whether we want to calculate the
20
	 * size of the database, files or both and whether to exclude any files from
21
	 * the file size calculation.
22
	 *
23
	 * @param string $type     Whether to calculate the size of the database, files
24
	 *                         or both. Should be one of 'file', 'database' or 'complete'
25
	 * @param array  $excludes An array of exclude rules
0 ignored issues
show
Documentation introduced by
Should the type for parameter $excludes not be null|Excludes?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
26
	 */
27
	public function __construct( $type = 'complete', Excludes $excludes = null ) {
28
		$this->type = $type;
0 ignored issues
show
Bug introduced by
The property type does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
29
		$this->excludes = $excludes;
0 ignored issues
show
Bug introduced by
The property excludes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
30
	}
31
32
	/**
33
	 * Calculate the size total size of the database + files.
34
	 *
35
	 * Doesn't account for any compression that would be gained by zipping.
36
	 *
37
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
38
	 */
39
	public function get_site_size() {
40
41
		if ( $this->size ) {
42
			return $this->size;
43
		}
44
45
		$size = 0;
46
47
		// Include database size except for file only schedule.
48
		if ( 'file' !== $this->type ) {
49
50
			global $wpdb;
51
			$tables = $wpdb->get_results( 'SHOW TABLE STATUS FROM `' . DB_NAME . '`', ARRAY_A );
52
53
			foreach ( $tables as $table ) {
54
				$size += (float) $table['Data_length'];
55
			}
56
		}
57
58
		// Include total size of dirs/files except for database only schedule.
59
		if ( 'database' !== $this->type ) {
60
61
			$root = new \SplFileInfo( Path::get_root() );
62
			$size += $this->filesize( $root );
63
64
		}
65
66
		$this->size = $size;
0 ignored issues
show
Documentation Bug introduced by
It seems like $size can also be of type double. However, the property $size is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
67
68
		return $size;
69
70
	}
71
72
	/**
73
	 * Get the site size formatted
74
	 *
75
	 * @see size_format
76
	 *
77
	 * @return string
78
	 */
79
	public function get_formatted_site_size() {
80
		return size_format( $this->get_site_size() );
81
	}
82
83
	/**
84
	 * Whether the total filesize is being calculated
85
	 *
86
	 * @return bool
87
	 */
88
	public static function is_site_size_being_calculated() {
89
		return false !== get_transient( 'hmbkp_directory_filesizes_running' );
90
	}
91
92
	/**
93
	 * Whether the total filesize is cached
94
	 *
95
	 * @return bool
96
	 */
97
	public static function is_site_size_cached() {
98
		return false !== get_transient( 'hmbkp_directory_filesizes' );
99
	}
100
101
	/**
102
	 * Return the contents of `$directory` as a single depth list ordered by total filesize.
103
	 *
104
	 * Will schedule background threads to recursively calculate the filesize of subdirectories.
105
	 * The total filesize of each directory and subdirectory is cached in a transient for 1 week.
106
	 *
107
	 * @param string $directory The directory to list
108
	 *
109
	 * @return array            returns an array of files ordered by filesize
110
	 */
111
	public function list_directory_by_total_filesize( $directory ) {
112
113
		$files = $files_with_no_size = $empty_files = $files_with_size = $unreadable_files = array();
114
115
		if ( ! is_dir( $directory ) ) {
116
			return $files;
117
		}
118
119
		$finder = new Finder();
120
		$finder->followLinks();
121
		$finder->ignoreDotFiles( false );
122
		$finder->ignoreUnreadableDirs();
123
		$finder->depth( '== 0' );
124
125
		$files = $finder->in( $directory );
0 ignored issues
show
Security File Exposure introduced by
$directory can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
126
127
		foreach ( $files as $entry ) {
128
129
			// Get the total filesize for each file and directory
130
			$filesize = $this->filesize( $entry );
131
132
			if ( $filesize ) {
133
134
				// If there is already a file with exactly the same filesize then let's keep increasing the filesize of this one until we don't have a clash
135
				while ( array_key_exists( $filesize, $files_with_size ) ) {
136
					$filesize ++;
137
				}
138
139
				$files_with_size[ $filesize ] = $entry;
140
141
			} elseif ( 0 === $filesize ) {
142
143
				$empty_files[] = $entry;
144
145
			} else {
146
147
				$files_with_no_size[] = $entry;
148
149
			}
150
151
		}
152
153
		// Sort files by filesize, largest first
154
		krsort( $files_with_size );
155
156
		// Add 0 byte files / directories to the bottom
157
		$files = $files_with_size + array_merge( $empty_files, $unreadable_files );
158
159
		// Add directories that are still calculating to the top
160
		if ( $files_with_no_size ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files_with_no_size 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...
161
162
			// We have to loop as merging or concatenating the array would re-flow the keys which we don't want because the filesize is stored in the key
163
			foreach ( $files_with_no_size as $entry ) {
164
				array_unshift( $files, $entry );
165
			}
166
		}
167
168
		return $files;
169
170
	}
171
172
	/**
173
	 * Recursively scans a directory to calculate the total filesize
174
	 *
175
	 * Locks should be set by the caller with `set_transient( 'hmbkp_directory_filesizes_running', true, HOUR_IN_SECONDS );`
176
	 *
177
	 * @return array $directory_sizes    An array of directory paths => filesize sum of all files in directory
178
	 */
179
	public function recursive_filesize_scanner() {
180
181
		/**
182
		 * Raise the `memory_limit` and `max_execution time`
183
		 *
184
		 * Respects the WP_MAX_MEMORY_LIMIT Constant and the `admin_memory_limit`
185
		 * filter.
186
		 */
187
		@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
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...
188
		@set_time_limit( 0 );
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...
189
190
		// Use the cached array directory sizes if available
191
		$directory_sizes = get_transient( 'hmbkp_directory_filesizes' );
192
193
		// If we do have it in cache then let's use it and also clear the lock
194
		if ( is_array( $directory_sizes ) ) {
195
			delete_transient( 'hmbkp_directory_filesizes_running' );
196
			return $directory_sizes;
197
		}
198
199
		// If we don't have it cached then we'll need to re-calculate
200
		$finder = new Finder();
201
		$finder->followLinks();
202
		$finder->ignoreDotFiles( false );
203
		$finder->ignoreUnreadableDirs( true );
204
205
		$files = $finder->in( Path::get_root() );
206
207
		foreach ( $files as $file ) {
208
209
			if ( $file->isReadable() ) {
210
				$directory_sizes[ wp_normalize_path( $file->getRealpath() ) ] = $file->getSize();
211
			} else {
212
				$directory_sizes[ wp_normalize_path( $file->getRealpath() ) ] = 0;
213
			}
214
215
		}
216
217
		set_transient( 'hmbkp_directory_filesizes', $directory_sizes, DAY_IN_SECONDS );
218
219
		// Remove the lock
220
		delete_transient( 'hmbkp_directory_filesizes_running' );
221
222
		return $directory_sizes;
223
224
	}
225
226
	/**
227
	 * Get the total filesize for a given file or directory
228
	 *
229
	 * If $file is a file then just return the result of `filesize()`.
230
	 * If $file is a directory then schedule a recursive filesize scan.
231
	 *
232
	 * @param \SplFileInfo   $file The file or directory you want to know the size of
233
	 *
234
	 * @return int           The total of the file or directory
235
	 */
236
	public function filesize( \SplFileInfo $file ) {
237
238
		// Skip missing or unreadable files
239
		if ( ! file_exists( $file->getPathname() ) || ! $file->getRealpath() || ! $file->isReadable() ) {
240
			return 0;
241
		}
242
243
		// If it's a file then just pass back the filesize
244
		if ( $file->isFile() && $file->isReadable() ) {
245
			return $file->getSize();
246
		}
247
248
		// If it's a directory then pull it from the cached filesize array
249
		if ( $file->isDir() ) {
250
251
			// If we haven't calculated the site size yet then kick it off in a thread
252
			$directory_sizes = get_transient( 'hmbkp_directory_filesizes' );
253
254
			if ( ! is_array( $directory_sizes ) ) {
255
256
				if ( ! $this->is_site_size_being_calculated() ) {
257
258
					// Mark the filesize as being calculated
259
					set_transient( 'hmbkp_directory_filesizes_running', true, HOUR_IN_SECONDS );
260
261
					// Schedule a Backdrop task to trigger a recalculation
262
					$task = new \HM\Backdrop\Task( array( $this, 'recursive_filesize_scanner' ) );
263
					$task->schedule();
264
265
				}
266
267
				// If the filesize is being calculated then return null
268
				return null;
269
270
			}
271
272
			$directory_sizes = array_flip( preg_grep( '(' . wp_normalize_path( $file->getRealPath() ) . ')', array_flip( $directory_sizes ) ) );
273
274
			if ( $this->excludes ) {
275
				$excludes = implode( '|', $this->excludes->get_excludes_for_regex() );
276
				if ( $excludes ) {
277
					$directory_sizes = array_flip( preg_grep( '(' . $excludes . ')', array_flip( $directory_sizes ), PREG_GREP_INVERT ) );
278
				}
279
			}
280
281
			// Directory size is now just a sum of all files across all sub directories
282
			return absint( array_sum( $directory_sizes ) );
283
284
		}
285
286
	}
287
288
}
289