PluginProbe
WP Synchro – The Ultimate WordPress Migration Tool / trunk
WP Synchro – The Ultimate WordPress Migration Tool vtrunk
1.16.1 1.16.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.10.0 1.11.0 1.11.1 1.11.2 1.11.3 1.11.4 1.11.5 1.12.0 1.13.0 1.14.0 1.15.0 1.2.0 1.3.0 1.3.1 1.3.2 All 45 releases
wpsynchro / src / API / HealthCheck.php

HealthCheck.php in WP Synchro – The Ultimate WordPress Migration Tool trunk, at src/API/HealthCheck.php

369 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPSynchro\API;
4
5 use WPSynchro\Utilities\CommonFunctions;
6 use WPSynchro\Masterdata\MasterdataRetrieval;
7 use WPSynchro\Transport\TransferToken;
8 use WPSynchro\Transport\TransferAccessKey;
9 use WPSynchro\API\MasterData;
10 use WPSynchro\Logger\NullLogger;
11 use WPSynchro\Initiate\InitiateTokenRetrieval;
12 use WPSynchro\Transport\BasicAuth;
13 use WPSynchro\Transport\Destination;
14 use WPSynchro\Utilities\Configuration\PluginConfiguration;
15 use WPSynchro\Utilities\Licensing\Licensing;
16 use WPSynchro\Utilities\PluginDirs;
17
18 /**
19 * Class for handling service to do healthcheck
20 * Call should already be verified by permissions callback
21 *
22 */
23 class HealthCheck extends WPSynchroService
24 {
25 public $healthcheck_errors;
26 private $healthcheck;
27
28 public function __construct()
29 {
30 $this->healthcheck = new \stdClass();
31 $this->healthcheck->errors = [];
32 $this->healthcheck->warnings = [];
33 }
34
35 public function service()
36 {
37 // Get methods and execute the tests
38 $check_methods = $this->getTestFunctions();
39 foreach ($check_methods as $method) {
40 $this->$method();
41 if (count($this->healthcheck->errors) > 0) {
42 break;
43 }
44 }
45
46 // If no errors or warnings, set timestamp in database
47 if (count($this->healthcheck->errors) == 0) {
48 update_site_option("wpsynchro_healthcheck_timestamp", time());
49 }
50
51 echo json_encode($this->healthcheck);
52 return;
53 }
54
55 /**
56 * Get functions to test
57 */
58 public function getTestFunctions()
59 {
60 // Find test functions
61 $class_methods = get_class_methods($this);
62 $check_methods = [];
63 foreach ($class_methods as $method) {
64 if (strpos($method, 'check') === 0) {
65 $check_methods[] = $method;
66 }
67 }
68 return $check_methods;
69 }
70
71 /**
72 * Check MU plugin loaded
73 */
74 public function checkMUPluginLoaded()
75 {
76 global $wpdb;
77 if (!defined('WPSYNCHRO_MU_COMPATIBILITY_LOADED')) {
78 // It is NOT loaded. Check if it should be
79 $plugin_configuration = new PluginConfiguration();
80 $should_mu_plugin_loaded = $plugin_configuration->getMUPluginEnabledState();
81 if ($should_mu_plugin_loaded) {
82 // It is enabled, but not loaded. Bad!
83 $this->healthcheck->errors[] = __("WP Synchro MU-plugin is enabled in Setup, but is not loading. That can cause problems and bad performance in migrations. Try to disable it and re-enable it in WP Synchro > Setup menu and see if this error persist.", "wpsynchro");
84 } else {
85 $this->healthcheck->warnings[] = __("WP Synchro MU-plugin is not currently loaded - You should really consider enabling it in WP Synchro > Setup menu, as it boosts performance and cause much less problems during migrations.", "wpsynchro");
86 }
87 }
88 }
89
90 /**
91 * Check table prefix
92 */
93 public function checkDatabaseTablePrefix()
94 {
95 global $wpdb;
96 if (strlen($wpdb->prefix) == 0) {
97 $this->healthcheck->errors[] = __("Empty database table prefix is not supported by WP Synchro (or by WordPress in newer versions) - To fix this, you must set a table prefix", "wpsynchro");
98 }
99 }
100
101 /**
102 * Check environment, WP/PHP/SQL
103 */
104 public function checkEnvironment()
105 {
106 $commonfunctions = new CommonFunctions();
107 $errors_from_env = $commonfunctions->checkEnvCompatability();
108 if (count($errors_from_env) > 0) {
109 $this->healthcheck->errors = array_merge($this->healthcheck->errors, $errors_from_env);
110 }
111 }
112
113 /**
114 * Check that database is current, but not newer
115 */
116 public function checkDatabaseIsCurrent()
117 {
118 $dbversion = get_option('wpsynchro_dbversion');
119 if (!$dbversion || $dbversion == "") {
120 $dbversion = 0;
121 }
122 if ($dbversion > WPSYNCHRO_DB_VERSION) {
123 $this->healthcheck->errors[] = __("WP Synchro database version is newer than the currently installed plugin version - Please upgrade plugin to newest version - Continue at own risk", "wpsynchro");
124 }
125 }
126
127 /**
128 * Check that local migration has access key set
129 */
130 public function checkAccessKeyIsSet()
131 {
132 $accesskey = TransferAccessKey::getAccessKey();
133 if (strlen(trim($accesskey)) < 20) {
134 $this->healthcheck->errors[] = __("Access key for this site is not set - This needs to be configured for WP Synchro to work.", "wpsynchro");
135 }
136 }
137
138 /**
139 * Check proper PHP extensions
140 */
141 public function checkPHPExtensions()
142 {
143 $required_php_extensions = ["curl", "mbstring", "openssl", "mysqli"];
144 $php_extensions_loaded = get_loaded_extensions();
145 $missing_extensions = [];
146 foreach ($required_php_extensions as $required_php_extension) {
147 if (!in_array($required_php_extension, $php_extensions_loaded)) {
148 $missing_extensions[] = $required_php_extension;
149 }
150 }
151 if (count($missing_extensions) > 0) {
152 // translators: %s is replaced with comma separated list of PHP extensions
153 $this->healthcheck->errors[] = sprintf(__("Missing PHP extensions for WP Synchro to work. Enable extension(s) '%s' to php.ini and reload.", "wpsynchro"), implode(", ", $missing_extensions));
154 }
155 }
156
157 /**
158 * Check that sql max_allowed_packet is set to something proper
159 */
160 public function checkSQLMaxAllowPacket()
161 {
162 global $wpdb;
163 $max_allowed_packet = (int) $wpdb->get_row("SHOW VARIABLES LIKE 'max_allowed_packet'")->Value;
164 if ($max_allowed_packet < 1024) {
165 $this->healthcheck->errors[] = sprintf(
166 // translators: %d is replaced with number
167 __("Your database server is misconfigured - The setting 'max_allowed_packet' is too low. It is currently set to: %d. Check out the documentation for the SQL server you are using and correct this setting.", "wpsynchro"),
168 $max_allowed_packet
169 );
170 }
171 }
172
173 /**
174 * Check that SAVEQUERIES are not active
175 */
176 public function checkSaveQueries()
177 {
178 if (defined("SAVEQUERIES") && SAVEQUERIES == true) {
179 $this->healthcheck->errors[] = __("SAVEQUERIES constant is set. This is normally only for debugging. It will generate out of memory errors with WP Synchro migrations", "wpsynchro");
180 }
181 }
182
183 /**
184 * Check license okay, if PRO
185 */
186 public function checkLicenseIfPRO()
187 {
188 if (CommonFunctions::isPremiumVersion()) {
189 $licensing = new Licensing();
190 if ($licensing->hasProblemWithLicensing()) {
191 $this->healthcheck->errors[] = $licensing->getLicenseErrorMessage();
192 }
193 }
194 }
195
196 /**
197 * Check that multiple connections to local services can be done - LocalWP problems most of time or misconfigured hosting
198 */
199 public function checkMultipleConnections()
200 {
201 $multiple_connection_test_url = trailingslashit(get_home_url()) . '?action=wpsynchro_test';
202
203 $args = [
204 'method' => 'GET',
205 'redirection' => 0,
206 'timeout' => 5,
207 'sslverify' => false,
208 'headers' => [],
209 ];
210
211 // Check for basic auth setup
212 $destination = new Destination(Destination::LOCAL);
213 $destination_basic_auth = $destination->getBasicAuthentication();
214 if ($destination_basic_auth !== false) {
215 $args["headers"]["Authorization"] = "Basic " . base64_encode($destination_basic_auth[0] . ":" . $destination_basic_auth[1]);
216 }
217
218 $tests_per_http_type = 5;
219 $error_runs = [];
220 $expected_result_from_service = 'it-works';
221
222 for ($i = 0; $i < $tests_per_http_type; $i++) {
223 $response = wp_remote_get($multiple_connection_test_url, $args);
224 $response_code = wp_remote_retrieve_response_code($response);
225 if ($response_code === 200) {
226 // Check correct body
227 $body = wp_remote_retrieve_body($response);
228 if ($body != $expected_result_from_service) {
229 $error_runs[$i] = $response;
230 }
231 } else {
232 $error_runs[$i] = $response;
233 }
234 }
235 if (count($error_runs) > 0) {
236 $this->healthcheck->errors[] = sprintf(
237 // translators: 1%d is replaced with simple number, 2%s is replaced with HTTP return code, like 200, 3%d er replaced by simple number
238 __("Service test error - Tried making %d consecutive requests (with HTTP %s) to a test service on this site - %d of them failed.", "wpsynchro"),
239 $tests_per_http_type,
240 'GET',
241 count($error_runs)
242 );
243
244 // Get basic auth class, to check if we are hitting basic auth
245 $basic_auth = new BasicAuth();
246 $atleast_one_used_basic_auth = false;
247 $problem_found = false;
248
249 foreach ($error_runs as $error_run_num => $response) {
250 if (is_wp_error($response)) {
251 $this->healthcheck->errors[] = sprintf(__("Error from request (number %d):", "wpsynchro"), $error_run_num + 1) . " " . $response->get_error_message();
252 } else {
253 $body = wp_remote_retrieve_body($response);
254 // Check for authentication on remote
255 if ($basic_auth->checkResponseHeaderForBasicAuth($response)) {
256 $atleast_one_used_basic_auth = true;
257 $this->healthcheck->errors[] = __("This site is protected by Basic Authentication, which requires a username and password.
258 You can add the correct username/password in the 'Setup' menu.", "wpsynchro");
259 $problem_found = true;
260 break;
261 } elseif (preg_match('/\s/', substr($body, 0, 1)) || preg_match('/\s/', substr($body, -1, 1))) {
262 // Check first if the first or last character is a space, as this would indicate that something is echoing stuff it should not
263 $this->healthcheck->errors[] = __('Got spaces in the response from API, either before or after the expected content. This is an indication that there is a problem somewhere in your code. Can sometimes be fixed by reinstalling WordPress files. Otherwise looks for spaces after closing PHP tags. This can cause problems for WP Synchro and other plugins also, so you should get that fixed.', "wpsynchro");
264 $problem_found = true;
265 break;
266 } elseif (strlen($expected_result_from_service) != strlen($body)) {
267 $this->healthcheck->errors[] = sprintf(__("The response length is different from expected length. This is often because of invalid characters before or after the expected response. This often comes from errors in the code other places on the site - Expected '%s' - Got: '%s'", "wpsynchro"), $expected_result_from_service, $body);
268 $problem_found = true;
269 break;
270 } elseif ($body != $expected_result_from_service) {
271 // Check if the body contain what we expect
272 $this->healthcheck->errors[] = sprintf(__("Error from request (number %d) - Got wrong data in response from webservice - Expected '%s' - Got: '%s' - This means that somewhere in the code, extra characters are being sent, most likely as an error. Look for characters or spaces after closing PHP tags.", "wpsynchro"), $error_run_num + 1, $expected_result_from_service, $body);
273 }
274 }
275 }
276 if ($atleast_one_used_basic_auth === false && $problem_found == false) {
277 $this->healthcheck->errors[] = $problem_found;
278 // Catch LocalWP bug
279 if (isset($error_runs[1]) && isset($error_runs[3]) && count($error_runs) === 2) {
280 $this->healthcheck->errors[] = __("The pattern of errors suggest you are using LocalWP as development environment. It contains a bug where 50% of remote requests fail, when called from the code. That is why request 2 and 4 fails, but 1,3 and 5 succeed. Read more about it in our documentation.", "wpsynchro");
281 } else {
282 $this->healthcheck->errors[] = __("This issue is most likely caused by a misconfiguration of the hosting environment. Most often because of too few available worker processes. See more documentation on this in our documentation.", "wpsynchro");
283 }
284 }
285 }
286 }
287
288 /**
289 * Check local service urls for connectivity and proper response
290 */
291 public function checkInitiateAndMastedata()
292 {
293 $initiate_token = "";
294
295 $initiate_server_okay = false;
296
297 $logger = new NullLogger();
298 $destination = new Destination(Destination::LOCAL);
299 $retrieval = new InitiateTokenRetrieval($logger, $destination, "local");
300 $result = $retrieval->getInitiateToken();
301
302 if ($result && isset($retrieval->token) && strlen($retrieval->token) > 0) {
303 $initiate_token = $retrieval->token;
304 $initiate_server_okay = true;
305 } else {
306 $this->healthcheck->errors = array_merge($this->healthcheck->errors, $retrieval->getErrors());
307 $this->healthcheck->warnings = array_merge($this->healthcheck->warnings, $retrieval->getWarnings());
308 $this->healthcheck->errors[] = __("Service error - Can not reach 'initiate' service - Check that services is accessible and not being blocked", "wpsynchro");
309 }
310
311 if ($initiate_server_okay) {
312 // Create a transfer token based on the token we just got
313 $transfer_token = TransferToken::getTransferToken(TransferAccessKey::getAccessKey(), $initiate_token);
314
315 // Get masterdata retrival object
316 $retrieval = new MasterdataRetrieval($destination);
317 $retrieval->setDataToRetrieve(['dbtables', 'filedetails']);
318 $retrieval->setToken($transfer_token);
319 $retrieval->setEncryptionKey(TransferAccessKey::getAccessKey());
320 $result = $retrieval->getMasterdata();
321
322 // Check for errors
323 if ($result) {
324 if (!$retrieval->data->dbtables) {
325 $this->healthcheck->errors[] = __("Service error - Masterdata service returns improper response - Data was not returned in usable way - Check PHP error log", "wpsynchro");
326 }
327 } else {
328 $this->healthcheck->errors[] = __("Service error - Can not reach 'masterdata' service - Check that WP Synchro is activated and service accessible", "wpsynchro");
329 }
330 }
331 }
332
333 /**
334 * Check writable log directory
335 */
336 public function checkWritableLogDir()
337 {
338 $plugins_dirs = new PluginDirs();
339 $log_location = $plugins_dirs->getUploadsFilePath();
340 $log_dir = realpath($log_location);
341 if (!is_writable($log_dir)) {
342 $this->healthcheck->errors[] = sprintf(__("WP Synchro log dir is not writable for PHP - Path: %s ", "wpsynchro"), $log_dir);
343 }
344 }
345
346 /**
347 * Check other relevant dir for writability (typically for files sync)
348 */
349 public function checkRelevantDirsForWritable()
350 {
351 if (!\WPSynchro\Utilities\CommonFunctions::isPremiumVersion()) {
352 return;
353 }
354 $paths_check = [
355 // Document root
356 $_SERVER['DOCUMENT_ROOT'],
357 // Absolut directory of WP_CONTENT folder, or whatever it is called
358 WP_CONTENT_DIR,
359 // One dir above webroot
360 dirname(realpath($_SERVER['DOCUMENT_ROOT']))
361 ];
362 foreach ($paths_check as $path) {
363 if (!MasterData::checkReadWriteOnDir($path)) {
364 $this->healthcheck->warnings[] = sprintf(__("Path that WP Synchro might use for migration is not writable- Path: %s - This can be caused by PHP's open_basedir setting or file permissions", "wpsynchro"), $path);
365 }
366 }
367 }
368 }
369