1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace HM\BackUpWordPress; |
4
|
|
|
|
5
|
|
|
use Symfony\Component\Process\Exception\ProcessTimedOutException; |
6
|
|
|
use Symfony\Component\Process\Process as Process; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Perform a file backup using the zip cli command |
10
|
|
|
*/ |
11
|
|
|
class Zip_File_Backup_Engine extends File_Backup_Engine { |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* The path to the zip executable |
15
|
|
|
* |
16
|
|
|
* @var string |
17
|
|
|
*/ |
18
|
|
|
private $zip_executable_path = ''; |
19
|
|
|
|
20
|
|
|
public function __construct() { |
21
|
|
|
parent::__construct(); |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Calculate the path to the zip executable. |
26
|
|
|
* |
27
|
|
|
* The executable path can be overridden using either the `HMBKP_ZIP_PATH` |
28
|
|
|
* Constant or the `hmbkp_zip_executable_path` filter. |
29
|
|
|
* |
30
|
|
|
* If neither of those are set then we fallback to checking a number of |
31
|
|
|
* common locations. |
32
|
|
|
* |
33
|
|
|
* @return string|false The path to the executable or false. |
34
|
|
|
*/ |
35
|
|
|
public function get_zip_executable_path() { |
36
|
|
|
|
37
|
|
|
if ( defined( 'HMBKP_ZIP_PATH' ) ) { |
38
|
|
|
return HMBKP_ZIP_PATH; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Allow the executable path to be set via a filter |
43
|
|
|
* |
44
|
|
|
* @param string The path to the zip executable |
45
|
|
|
*/ |
46
|
|
|
$this->zip_executable_path = apply_filters( 'hmbkp_zip_executable_path', '' ); |
47
|
|
|
|
48
|
|
|
if ( ! $this->zip_executable_path ) { |
49
|
|
|
|
50
|
|
|
// List of possible zip locations |
51
|
|
|
$paths = array( |
52
|
|
|
'zip', |
53
|
|
|
'/usr/bin/zip', |
54
|
|
|
'/usr/local/bin/zip', |
55
|
|
|
'/opt/local/bin/zip', |
56
|
|
|
); |
57
|
|
|
|
58
|
|
|
$this->zip_executable_path = Backup_Utilities::get_executable_path( $paths ); |
|
|
|
|
59
|
|
|
|
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
return $this->zip_executable_path; |
63
|
|
|
|
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Perform the file backup. |
68
|
|
|
* |
69
|
|
|
* @return bool Whether the backup completed successfully or not. |
70
|
|
|
*/ |
71
|
|
|
public function backup() { |
72
|
|
|
|
73
|
|
|
if ( ! $this->get_zip_executable_path() ) { |
|
|
|
|
74
|
|
|
return false; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
// cd to the site root |
78
|
|
|
$command[] = 'cd ' . escapeshellarg( Path::get_root() ); |
|
|
|
|
79
|
|
|
|
80
|
|
|
// Run the zip command with the recursive and quiet flags |
81
|
|
|
$command[] = '&& ' . escapeshellcmd( $this->get_zip_executable_path() ) . ' -rq'; |
82
|
|
|
|
83
|
|
|
// Save the zip file to the correct path |
84
|
|
|
$command[] = escapeshellarg( $this->get_backup_filepath() ) . ' ./'; |
85
|
|
|
|
86
|
|
|
// Pass exclude rules in if we have them |
87
|
|
|
if ( $this->get_exclude_string() ) { |
88
|
|
|
$command[] = '-x ' . $this->get_exclude_string(); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
$command = implode( ' ', $command ); |
92
|
|
|
|
93
|
|
|
$process = new Process( $command ); |
94
|
|
|
$process->setTimeout( HOUR_IN_SECONDS ); |
95
|
|
|
|
96
|
|
|
try { |
97
|
|
|
|
98
|
|
|
$process->run(); |
99
|
|
|
|
100
|
|
|
if ( ! $process->isSuccessful() ) { |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Exit Code 18 is returned when an unreadable file is encountered during the zip process. |
104
|
|
|
* |
105
|
|
|
* Given the zip process still completes correctly and the unreadable file is simple skipped |
106
|
|
|
* we don't want to treat 18 as an actual error. |
107
|
|
|
*/ |
108
|
|
|
if ( $process->getExitCode() !== 18 ) { |
109
|
|
|
$this->error( __CLASS__, $process->getErrorOutput() ); |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
} catch ( ProcessTimedOutException $e ) { |
114
|
|
|
$this->error( __CLASS__, $e->getMessage() ); |
115
|
|
|
} catch ( \Exception $e ) { |
116
|
|
|
$this->error( __CLASS__, $e->getMessage() ); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
return $this->verify_backup(); |
120
|
|
|
|
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Convert the exclude rules to a format zip accepts |
125
|
|
|
* |
126
|
|
|
* @return string The exclude string ready to pass to `zip -x` |
127
|
|
|
*/ |
128
|
|
|
public function get_exclude_string() { |
129
|
|
|
|
130
|
|
|
if ( ! $this->excludes ) { |
|
|
|
|
131
|
|
|
return ''; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
$excludes = $this->excludes->get_excludes(); |
|
|
|
|
135
|
|
|
|
136
|
|
|
foreach ( $excludes as $key => &$rule ) { |
137
|
|
|
|
138
|
|
|
$file = $absolute = $fragment = false; |
139
|
|
|
|
140
|
|
|
// Files don't end with / |
141
|
|
|
if ( ! in_array( substr( $rule, - 1 ), array( '\\', '/' ) ) ) { |
142
|
|
|
$file = true; |
143
|
|
|
} // If rule starts with a / then treat as absolute path |
144
|
|
View Code Duplication |
elseif ( in_array( substr( $rule, 0, 1 ), array( '\\', '/' ) ) ) { |
|
|
|
|
145
|
|
|
$absolute = true; |
146
|
|
|
} // Otherwise treat as dir fragment |
147
|
|
|
else { |
148
|
|
|
$fragment = true; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$rule = str_ireplace( Path::get_root(), '', untrailingslashit( wp_normalize_path( $rule ) ) ); |
152
|
|
|
|
153
|
|
|
// Strip the preceeding slash |
154
|
|
View Code Duplication |
if ( in_array( substr( $rule, 0, 1 ), array( '\\', '/' ) ) ) { |
|
|
|
|
155
|
|
|
$rule = substr( $rule, 1 ); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
// Wrap directory fragments and files in wildcards for zip |
159
|
|
|
if ( $fragment || $file ) { |
160
|
|
|
$rule = '*' . $rule . '*'; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
// Add a wildcard to the end of absolute url for zips |
164
|
|
|
if ( $absolute ) { |
165
|
|
|
$rule .= '*'; |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// Escape shell args for zip command |
170
|
|
|
$excludes = array_map( 'escapeshellarg', array_unique( $excludes ) ); |
171
|
|
|
|
172
|
|
|
return implode( ' -x ', $excludes ); |
173
|
|
|
|
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
|
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 theid
property of an instance of theAccount
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.