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
Branch master (cf5579)
by
unknown
02:50
created

Site_Size::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace HM\BackUpWordPress;
4
5
use HM\Backdrop\Task;
6
use Symfony\Component\Finder\Finder;
7
8
/**
9
 * Site Size class
10
 *
11
 * Use to calculate the total or partial size of the sites database and files.
12
 */
13
class Site_Size {
14
15
	private $size = 0;
16
	private $type = '';
17
	private $excludes = array();
18
19
	/**
20
	 * Constructor
21
	 *
22
	 * Set up some initial conditions including whether we want to calculate the
23
	 * size of the database, files or both and whether to exclude any files from
24
	 * the file size calculation.
25
	 *
26
	 * @param string $type     Whether to calculate the size of the database, files
27
	 *                         or both. Should be one of 'file', 'database' or 'complete'
28
	 * @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...
29
	 */
30
	public function __construct( $type = 'complete', Excludes $excludes = null ) {
31
		$this->type = $type;
32
		$this->excludes = $excludes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $excludes of type null or object<HM\BackUpWordPress\Excludes> is incompatible with the declared type array of property $excludes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
33
	}
34
35
	/**
36
	 * Calculate the size total size of the database + files.
37
	 *
38
	 * Doesn't account for any compression that would be gained by zipping.
39
	 *
40
	 * @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...
41
	 */
42
	public function get_site_size() {
43
44
		if ( $this->size ) {
45
			return $this->size;
46
		}
47
48
		$size = 0;
49
50
		// Include database size except for file only schedule.
51
		if ( 'file' !== $this->type ) {
52
53
			$size = (int) get_transient( 'hmbkp_database_size' );
54
55
			if ( ! $size ) {
56
57
				global $wpdb;
0 ignored issues
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...
58
59
				$tables = $wpdb->get_results( 'SHOW TABLE STATUS FROM `' . DB_NAME . '`', ARRAY_A );
60
61
				foreach ( $tables as $table ) {
62
					$size += (float) $table['Data_length'];
63
				}
64
65
				set_transient( 'hmbkp_database_size', $size, WEEK_IN_SECONDS );
66
			}
67
		}
68
69
		// Include total size of dirs/files except for database only schedule.
70
		if ( 'database' !== $this->type ) {
71
72
			$root = new \SplFileInfo( Path::get_root() );
73
			$size += $this->filesize( $root );
74
75
		}
76
77
		$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...
78
79
		return $size;
80
81
	}
82
83
	/**
84
	 * Get the site size formatted
85
	 *
86
	 * @see size_format
87
	 *
88
	 * @return string
89
	 */
90
	public function get_formatted_site_size() {
91
		return size_format( $this->get_site_size() );
92
	}
93
94
	/**
95
	 * Whether the total filesize is being calculated
96
	 *
97
	 * @return bool
98
	 */
99
	public static function is_site_size_being_calculated() {
100
		return false !== get_transient( 'hmbkp_directory_filesizes_running' );
101
	}
102
103
	/**
104
	 * Whether the total filesize is cached
105
	 *
106
	 * @return bool
107
	 */
108
	public function is_site_size_cached() {
109
		return (bool) $this->get_cached_filesizes();
110
	}
111
112
	/**
113
	 * Recursively scans a directory to calculate the total filesize
114
	 *
115
	 * Locks should be set by the caller with `set_transient( 'hmbkp_directory_filesizes_running', true, HOUR_IN_SECONDS );`
116
	 *
117
	 * @return array $directory_sizes    An array of directory paths => filesize sum of all files in directory
0 ignored issues
show
Documentation introduced by
Should the return type not be array|object|integer|double|string|null|boolean? Also, consider making the array more specific, something like array<String>, or String[].

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.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
118
	 */
119
	public function recursive_filesize_scanner() {
120
121
		/**
122
		 * Raise the `memory_limit` and `max_execution time`
123
		 *
124
		 * Respects the WP_MAX_MEMORY_LIMIT Constant and the `admin_memory_limit`
125
		 * filter.
126
		 */
127
		@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...
128
		@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...
129
130
		// Use the cached array directory sizes if available
131
		$directory_sizes = $this->get_cached_filesizes();
132
133
		// If we do have it in cache then let's use it and also clear the lock
134
		if ( is_array( $directory_sizes ) ) {
135
			delete_transient( 'hmbkp_directory_filesizes_running' );
136
			return $directory_sizes;
137
		}
138
139
		// If we don't have it cached then we'll need to re-calculate
140
		$finder = new Finder();
141
		$finder->followLinks();
142
		$finder->ignoreDotFiles( false );
143
		$finder->ignoreUnreadableDirs( true );
144
145
		$files = $finder->in( Path::get_root() );
146
147
		foreach ( $files as $file ) {
148
149
			if ( $file->isReadable() ) {
150
				$directory_sizes[ wp_normalize_path( $file->getRealpath() ) ] = $file->getSize();
151
			} else {
152
				$directory_sizes[ wp_normalize_path( $file->getRealpath() ) ] = 0;
153
			}
154
		}
155
156
		file_put_contents( PATH::get_path() . '/.files', gzcompress( json_encode( $directory_sizes ) ) );
157
158
		// Remove the lock
159
		delete_transient( 'hmbkp_directory_filesizes_running' );
160
161
		return $directory_sizes;
162
163
	}
164
165
	/**
166
	 * Get the total filesize for a given file or directory. Aware of exclusions.
167
	 *
168
	 * If $file is a file then return the result of `filesize()` or 0 if it's excluded.
169
	 * If $file is a directory then recursively calculate the size without
170
	 * the size of excluded files/directories.
171
	 *
172
	 * @param \SplFileInfo   $file The file or directory you want to know the size of.
173
	 *
174
	 * @return int           The total filesize of the file or directory without
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|null?

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...
175
	 *                       the size of excluded files/directories.
176
	 */
177
	public function filesize( \SplFileInfo $file ) {
178
179
		// Skip missing or unreadable files.
180
		if ( ! file_exists( $file->getPathname() ) || ! $file->getRealpath() || ! $file->isReadable() ) {
181
			return 0;
182
		}
183
184
		// If it's a file then return its filesize or 0 if it's excluded.
185
		if ( $file->isFile() ) {
186
187
			if ( $this->excludes && $this->excludes->is_file_excluded( $file ) ) {
0 ignored issues
show
Bug introduced by
The method is_file_excluded cannot be called on $this->excludes (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug Best Practice introduced by
The expression $this->excludes 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...
188
				return 0;
189
			} else {
190
				return $file->getSize();
191
			}
192
		}
193
194
		// If it's a directory then pull it from the cached filesize array.
195
		if ( $file->isDir() ) {
196
			return $this->directory_filesize( $file );
197
		}
198
	}
199
200
	public function directory_filesize( \SplFileInfo $file ) {
201
202
		// For performance reasons we cache the root.
203
		if ( $file->getRealPath() === PATH::get_root() && $this->excludes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->excludes 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...
204
205
			$directory_sizes = get_transient( 'hmbkp_root_size' );
206
207
			if ( $directory_sizes ) {
208
				return (int) $directory_sizes;
209
			}
210
		}
211
212
		// If we haven't calculated the site size yet then kick it off in a thread.
213
		$directory_sizes = $this->get_cached_filesizes();
214
215
		if ( ! is_array( $directory_sizes ) ) {
216
			$this->rebuild_directory_filesizes();
217
218
			// Intentionally return null so the caller can tell that the size is being calculated.
219
			return null;
220
		}
221
222
		/*
223
		 * Ensure we only include files in the current path, the filepaths are stored in keys
224
		 * so we need to flip for use with preg_grep.
225
		 */
226
		$directory_sizes = array_flip( preg_grep( '(' . wp_normalize_path( $file->getRealPath() ) . ')', array_flip( $directory_sizes ) ) );
227
228
		if ( $this->excludes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->excludes 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...
229
230
			$excludes = implode( '|', $this->excludes->get_excludes_for_regex() );
0 ignored issues
show
Bug introduced by
The method get_excludes_for_regex cannot be called on $this->excludes (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
231
232
			if ( $excludes ) {
233
				// Use PREG_GREP_INVERT to remove any filepaths which match an exclude rule
234
				$directory_sizes = array_flip( preg_grep( '(' . $excludes . ')', array_flip( $directory_sizes ), PREG_GREP_INVERT ) );
235
			}
236
		}
237
238
		$directory_sizes = absint( array_sum( $directory_sizes ) );
239
240
		// For performance reasons we cache the root.
241
		if ( $file->getRealPath() === PATH::get_root() && $this->excludes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->excludes 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...
242
			set_transient( 'hmbkp_root_size', $directory_sizes, DAY_IN_SECONDS );
243
		}
244
245
		// Directory size is now just a sum of all files across all sub directories.
246
		return (int) $directory_sizes;
247
248
	}
249
250
	public function rebuild_directory_filesizes() {
251
252
		if ( $this->is_site_size_being_calculated() ) {
253
			return false;
254
		}
255
256
		// Mark the filesize as being calculated
257
		set_transient( 'hmbkp_directory_filesizes_running', true, HOUR_IN_SECONDS );
258
259
		// Schedule a Backdrop task to trigger a recalculation
260
		$task = new Task( array( $this, 'recursive_filesize_scanner' ) );
261
		$task->schedule();
262
263
	}
264
265
	public function get_cached_filesizes( $max_age = WEEK_IN_SECONDS ) {
266
267
		$cache = PATH::get_path() . '/.files';
268
		$files = false;
269
270
		if ( file_exists( $cache ) ) {
271
272
			// If the file is old then regenerate it
273
			if ( ( time() - filemtime( $cache ) ) <= $max_age ) {
274
				$files = json_decode( gzuncompress( file_get_contents( $cache ) ), 'ARRAY_A' );
275
			}
276
		}
277
278
		return $files;
279
280
	}
281
}
282