PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.0.12
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.0.12
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
mainwp / class / class-mainwp-connect.php

class-mainwp-connect.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.0.12, at class/class-mainwp-connect.php

2,150 lines 86.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Connect
4 *
5 * MainWP Connect functions.
6 *
7 * @package MainWP/Dashboard
8 */
9
10 namespace MainWP\Dashboard;
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class MainWP_Connect
19 *
20 * @package MainWP\Dashboard
21 */
22 class MainWP_Connect { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
23
24 // phpcs:disable WordPress.DB.RestrictedFunctions, Generic.Metrics.CyclomaticComplexity, WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors -- Using cURL functions.
25
26 /**
27 * Method get_class_name()
28 *
29 * Get Class Name.
30 *
31 * @return object Class name.
32 */
33 public static function get_class_name() {
34 return __CLASS__;
35 }
36
37 /**
38 * Method try visit.
39 *
40 * Try connecting to Child Site via cURL.
41 *
42 * @param string $url Child Site URL.
43 * @param bool $ssl_verifyhost Option to check SSL Certificate. Default = null.
44 * @param string $http_user HTTPAuth Username. Default = null.
45 * @param string $http_pass HTTPAuth Password. Default = null.
46 * @param int $sslVersion Child Site SSL Version.
47 * @param bool $forceUseIPv4 Option to force IP4. Default = null.
48 * @param bool $no_body Option to set CURLOPT_NOBODY option. Default = false.
49 *
50 * @return array $out. 'host IP, Returned HTTP Code, Error Message, http Status error message.
51 *
52 * @uses \MainWP\Dashboard\MainWP_Logger::debug()
53 * @uses \MainWP\Dashboard\MainWP_System::$version
54 * @uses \MainWP\Dashboard\MainWP_Utility::value_to_string()
55 * @uses \MainWP\Dashboard\MainWP_Utility::get_http_codes()
56 */
57 public static function try_visit( $url, $ssl_verifyhost = null, $http_user = null, $http_pass = null, $sslVersion = 0, $forceUseIPv4 = null, $no_body = false ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
58
59 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
60 $postdata = array( 'test' => 'yes' );
61
62 $ch = curl_init();
63
64 $proxy = new \WP_HTTP_Proxy();
65 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
66 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
67 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
68 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
69
70 if ( $proxy->use_authentication() ) {
71 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
72 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
73 }
74 }
75
76 curl_setopt( $ch, CURLOPT_URL, $url );
77 if ( $no_body ) {
78 curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'HEAD' ); // HTTP request is 'HEAD', but sometime return 4xx - error code.
79 }
80
81 $follow_loc = apply_filters( 'mainwp_try_visit_follow_location', false ); // to support for case compatible.
82
83 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
84 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, $follow_loc ? true : false );
85 curl_setopt( $ch, CURLOPT_POST, true );
86 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
87 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
88 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
89 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
90
91 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
92 $http_pass = stripslashes( $http_pass );
93 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
94 }
95
96 if ( $ssl_verifyhost ) {
97 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
98 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
99 } else {
100 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR.
101 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR.
102 }
103
104 curl_setopt( $ch, CURLOPT_SSLVERSION, $sslVersion );
105
106 $http_version = apply_filters( 'mainwp_curl_http_version', false, false, $url );
107 if ( false !== $http_version ) {
108 curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
109 }
110
111 $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, false, $url );
112 if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
113 curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
114 curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
115 }
116
117 $headers = array( 'X-Requested-With' => 'XMLHttpRequest' );
118 $headers['Expect'] = static::get_expect_header( $postdata );
119 $headers = apply_filters( 'mainwp_connect_http_request_headers', $headers, false );
120
121 if ( class_exists( '\WpOrg\Requests\Requests' ) ) {
122 $headers = \WpOrg\Requests\Requests::flatten( $headers );
123 } else {
124 $headers = \Requests::flatten( $headers );
125 }
126
127 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
128 curl_setopt( $ch, CURLOPT_REFERER, get_option( 'siteurl' ) );
129
130 $force_use_ipv4 = false;
131 if ( null !== $forceUseIPv4 ) {
132 if ( 1 === $forceUseIPv4 ) {
133 $force_use_ipv4 = true;
134 } elseif ( 2 === $forceUseIPv4 ) {
135 if ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
136 $force_use_ipv4 = true;
137 }
138 }
139 } elseif ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
140 $force_use_ipv4 = true;
141 }
142
143 if ( $force_use_ipv4 && defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
144 curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
145 }
146
147 MainWP_Logger::instance()->debug( ' :: trying Visit :: [url=' . $url . ']' );
148
149 $http_version = false;
150
151 $disabled_functions = ini_get( 'disable_functions' );
152 if ( empty( $disabled_functions ) || ( stristr( $disabled_functions, 'curl_multi_exec' ) === false ) ) {
153 MainWP_Logger::instance()->debug( ' :: trying Visit :: curl_multi_exec => enabled.' );
154 $mh = curl_multi_init();
155 @curl_multi_add_handle( $mh, $ch );
156
157 do {
158 do {
159 $mrc = curl_multi_exec( $mh, $running );
160 } while ( CURLM_CALL_MULTI_PERFORM === $mrc );
161
162 if ( $running ) {
163 $rc = curl_multi_select( $mh, 1.0 );
164 if ( -1 === $rc ) {
165 usleep( 100000 );
166 }
167 }
168
169 while ( $info = curl_multi_info_read( $mh ) ) {
170 $data = curl_multi_getcontent( $info['handle'] );
171 $err = curl_error( $info['handle'] );
172 $http_status = curl_getinfo( $info['handle'], CURLINFO_HTTP_CODE );
173 $realurl = curl_getinfo( $info['handle'], CURLINFO_EFFECTIVE_URL );
174 if ( defined( 'CURLINFO_HTTP_VERSION' ) ) {
175 $http_version = curl_getinfo( $info['handle'], CURLINFO_HTTP_VERSION );
176 }
177
178 curl_multi_remove_handle( $mh, $info['handle'] );
179 curl_close( $info['handle'] );
180 }
181 usleep( 10000 );
182
183 } while ( $running > 0 );
184
185 if ( static::is_valid_curl_handle( $mh ) ) {
186 curl_multi_close( $mh );
187 }
188 } else {
189 $data = curl_exec( $ch );
190 $err = curl_error( $ch );
191 $http_status = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
192 $realurl = curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
193
194 if ( defined( 'CURLINFO_HTTP_VERSION' ) ) {
195 $http_version = curl_getinfo( $ch, CURLINFO_HTTP_VERSION );
196 }
197
198 if ( static::is_valid_curl_handle( $ch ) ) {
199 curl_close( $ch );
200 }
201 }
202
203 MainWP_Logger::instance()->log_execution_time( 'tryVisit :: [url=' . $url . '] [http_status=' . $http_status . ']' );
204
205 $host = wp_parse_url( ( empty( $realurl ) ? $url : $realurl ), PHP_URL_HOST );
206 $ip = false;
207 $target = false;
208
209 $found = false;
210 $dnsRecord = @dns_get_record( $host );
211 MainWP_Logger::instance()->debug( ' :: tryVisit :: [dnsRecord=' . MainWP_Utility::value_to_string( $dnsRecord, 1 ) . ']' );
212
213 if ( false !== $dnsRecord && is_array( $dnsRecord ) ) {
214 if ( ! isset( $dnsRecord['ip'] ) ) {
215 foreach ( $dnsRecord as $dnsRec ) {
216 if ( isset( $dnsRec['ip'] ) ) {
217 $ip = $dnsRec['ip'];
218 break;
219 }
220 }
221 } else {
222 $ip = $dnsRecord['ip'];
223 }
224
225 if ( ! isset( $dnsRecord['host'] ) ) {
226 foreach ( $dnsRecord as $dnsRec ) {
227 if ( $dnsRec['host'] === $host ) {
228 if ( 'CNAME' === $dnsRec['type'] ) {
229 $target = $dnsRec['target'];
230 }
231 $found = true;
232 break;
233 }
234 }
235 } else {
236 $found = ( $dnsRecord['host'] === $host );
237 if ( 'CNAME' === $dnsRecord['type'] ) {
238 $target = $dnsRecord['target'];
239 }
240 }
241 }
242
243 if ( false === $ip ) {
244 $ip = gethostbynamel( $host );
245 }
246 if ( ( false !== $target ) && ( $target !== $host ) ) {
247 $host .= ' (CNAME: ' . $target . ')';
248 }
249
250 $out = array(
251 'host' => $host,
252 'httpCode' => $http_status,
253 'httpCodeString' => MainWP_Utility::get_http_codes( $http_status ),
254 'httpVersion' => $http_version,
255 );
256
257 $hidden_data = '[hidden response data]';
258
259 if ( ( false === $ip || $ip === $host || ! static::validate_ip( $ip ) ) && apply_filters( 'mainwp_hide_raw_connection_response_data', true ) ) { // Failed to resolve hostname.
260 $data = $hidden_data;
261 }
262
263 MainWP_Logger::instance()->debug( ' :: tryVisit :: [url=' . $url . '] [http_status=' . $http_status . '] [http_version=' . ( false === $http_version ? 'N/A' : MainWP_System_Utility::get_http_version_const_str( $http_version ) ) . '] [error=' . $err . '] [data-start]' . $data . '[data-end]' );
264
265 if ( false !== $ip ) {
266 $out['ip'] = $ip;
267 $found = true;
268 }
269
270 $out['error'] = ( '' === $err && false === $found ? 'Invalid host.' : $err );
271
272 return $out;
273 }
274
275
276 /**
277 * Method validate_ip().
278 *
279 * @param string $ip IP check.
280 * @return bool Check IP result.
281 */
282 public static function validate_ip( $ip ) {
283 // Validate the IP and check for private and reserved ranges.
284 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
285 return true;
286 }
287 return false;
288 }
289
290 /**
291 * Method check_ignored_http_code()
292 *
293 * Check if http error code is being ignored.
294 *
295 * @param mixed $value http error code.
296 * @param object|false $website website.
297 *
298 * @return bolean True|False.
299 */
300 public static function check_ignored_http_code( $value, $website = false ) { // phpcs:ignore -- NOSONAR -complex method.
301 $value = (int) $value;
302 $site_id = is_object( $website ) && ! empty( $website->id ) ? $website->id : 0;
303 $global_settings = MainWP_Uptime_Monitoring_Handle::get_global_monitoring_settings();
304
305 $ignored_code = '';
306
307 if ( $site_id ) {
308
309 $primary_monitor = MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by( $site_id, 'issub', 0 );
310 $global_settings = MainWP_Uptime_Monitoring_Handle::get_global_monitoring_settings();
311
312 $mo_active = 0;
313 if ( $primary_monitor ) {
314 $mo_active = MainWP_Uptime_Monitoring_Connect::get_apply_setting( 'active', (int) $primary_monitor->active, $global_settings, -1, 0 );
315 }
316
317 if ( $mo_active ) {
318 $ignored_code = MainWP_Uptime_Monitoring_Connect::instance()->get_up_codes( $primary_monitor, $global_settings );
319 } else {
320 $ignored_code = is_array( $global_settings ) && isset( $global_settings['up_status_codes'] ) ? $global_settings['up_status_codes'] : '';
321 }
322 } else {
323 $ignored_code = ! empty( $global_settings['up_status_codes'] ) ? $global_settings['up_status_codes'] : '';
324 }
325
326 if ( ! empty( $ignored_code ) ) {
327 $ignored_code = explode( ',', $ignored_code );
328 foreach ( $ignored_code as $code ) {
329 $code = trim( $code );
330 if ( (int) $value === (int) $code ) {
331 return true;
332 }
333 }
334 }
335 return false;
336 }
337
338 /**
339 * Method check website status.
340 *
341 * Check if the Website returns and http errors.
342 *
343 * @param object $website Child Site information.
344 * @param bool $chk_http_site Check site http response.
345 *
346 * @return mixed False|try visit result.
347 *
348 * @uses \MainWP\Dashboard\MainWP_Utility::is_domain_valid()
349 */
350 public static function check_website_status( $website, $chk_http_site = false ) { //phpcs:ignore -- NOSONAR - complexity.
351
352 if ( is_object( $website ) && isset( $website->id ) ) {
353 $primary_monitor = MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by( $website->id, 'issub', 0 );
354 if ( $primary_monitor ) {
355 // return compatible uptime status here.
356 return MainWP_Uptime_Monitoring_Handle::check_website_uptime_monitoring_status(
357 $primary_monitor,
358 array(
359 'ignore_compatible_save' => 1,
360 'check_http_site' => $chk_http_site,
361 )
362 ); // to ignore save compatible uptime status.
363 }
364 }
365
366 $http_user = null;
367 $http_pass = null;
368 $sslVersion = null;
369 $verifyCertificate = null;
370 $forceUseIPv4 = null;
371 if ( is_object( $website ) && isset( $website->url ) ) {
372 $url = $website->url;
373 $verifyCertificate = isset( $website->verify_certificate ) ? (int) $website->verify_certificate : null;
374 $forceUseIPv4 = $website->force_use_ipv4;
375 $http_user = $website->http_user;
376 $http_pass = $website->http_pass;
377 $sslVersion = $website->ssl_version;
378 } else {
379 $url = $website;
380 }
381
382 if ( ! MainWP_Utility::is_domain_valid( $url ) ) {
383 return false;
384 }
385
386 $ssl_verifyhost = false;
387
388 if ( 1 === $verifyCertificate ) {
389 $ssl_verifyhost = true;
390 } elseif ( 2 === $verifyCertificate || null === $verifyCertificate ) {
391 if ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
392 $ssl_verifyhost = true;
393 }
394 }
395
396 $noBody = false;
397 return static::try_visit( $url, $ssl_verifyhost, $http_user, $http_pass, $sslVersion, $forceUseIPv4, $noBody );
398 }
399
400 /**
401 * Method get_post_data_authed()
402 *
403 * Get authorized $_POST data & build query.
404 *
405 * @param mixed $website Array of Child Site Info.
406 * @param mixed $what What we are posting.
407 * @param null $params Post parameters.
408 *
409 * @return mixed null|http_build_query()
410 */
411 public static function get_post_data_authed( &$website, $what, $params = null ) { //phpcs:ignore -- NOSONAR - complex method.
412 if ( $website && '' !== $what ) {
413 $data = array();
414 $data['user'] = $website->adminname;
415 $data['function'] = $what;
416 $data['nonce'] = wp_rand( 0, 9999 );
417 $data['mainwpver'] = MainWP_System::$version;
418
419 $params_filter = apply_filters( 'mainwp_pre_fetch_authed_data', false, $params, $what, $website );
420 if ( is_array( $params_filter ) && ! empty( $params_filter ) ) {
421 $data = array_merge( $data, $params_filter );
422 }
423
424 if ( null !== $params ) {
425 $data = array_merge( $data, $params );
426 }
427
428 $alg = false;
429 $sign_success = null;
430 $use_seclib = false;
431
432 $data = apply_filters( 'mainwp_get_post_data_authed', $data, $website, $what, $params );
433 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
434 $sign_success = MainWP_Connect_Lib::connect_sign( $what . $data['nonce'], $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
435 $use_seclib = true;
436 } elseif ( function_exists( 'openssl_verify' ) ) {
437 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
438 $sign_success = static::connect_sign( $what . $data['nonce'], $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
439 if ( false !== $alg ) {
440 $data['sign_algo'] = $alg;
441 }
442 }
443
444 if ( $use_seclib ) {
445 $data['verifylib'] = 1;
446 }
447
448 if ( null !== $sign_success && empty( $sign_success ) ) {
449 $sign_error = '';
450 while ( $msg = openssl_error_string() ) {
451 if ( is_string( $msg ) ) {
452 $sign_error .= $msg;
453 }
454 }
455 $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
456 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . '] :: [pkey start =' . $pk_info . '...]', false );
457 }
458
459 $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
460
461 /** This filter is documented in ../widgets/widget-mainwp-recent-posts.php */
462 $recent_number = apply_filters( 'mainwp_recent_posts_pages_number', 5 );
463 if ( 5 !== $recent_number ) {
464 $data['recent_number'] = $recent_number;
465 }
466
467 $scan_dir = apply_filters( 'mainwp_stats_scan_dir', false, $website );
468 if ( ! empty( $scan_dir ) ) {
469 $data['scan_dir'] = 1;
470 }
471
472 /**
473 * Current user global.
474 *
475 * @global string
476 */
477 global $current_user;
478
479 if ( ( ! defined( 'DOING_CRON' ) || false === DOING_CRON ) && ( ! defined( 'WP_CLI' ) || false === WP_CLI ) && is_object( $current_user ) && property_exists( $current_user, 'ID' ) && $current_user->ID ) {
480 /**
481 * Filter: mainwp_alter_login_user
482 *
483 * Filters users accounts so it allows you user to jump to child site under alternative administrator account.
484 *
485 * @param int $website->id Child site ID.
486 * @param int $current_user->ID User ID.
487 *
488 * @since Unknown
489 */
490 $alter_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
491 if ( ! empty( $alter_user ) ) {
492 $data['alt_user'] = rawurlencode( $alter_user );
493 }
494 }
495
496 return http_build_query( $data, '', '&' );
497 }
498
499 return null;
500 }
501
502 /**
503 * Method get_renew_post_data_authed()
504 *
505 * Get authorized $_POST data & build query for renew connection action only.
506 *
507 * @param mixed $website Array of Child Site Info.
508 * @param mixed $what What we are posting.
509 *
510 * @return mixed null|http_build_query()
511 */
512 private static function get_renew_post_data_authed( &$website, $what ) { // phpcs:ignore -- NOSONAR - complex.
513
514 if ( $website && '' !== $what ) {
515 $compat_what = 'disconnect'; // to compatible, renew will call disconnect.
516 $data = array();
517 $data['user'] = $website->adminname;
518 $data['function'] = $compat_what;
519 $data['nonce'] = wp_rand( 0, 9999 );
520
521 $alg = false;
522 $sign_success = null;
523 $use_seclib = false;
524
525 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
526 // to disconnect.
527 $sign_success = MainWP_Connect_Lib::connect_sign( $compat_what . $data['nonce'], $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
528 $use_seclib = true;
529 } elseif ( function_exists( 'openssl_verify' ) ) {
530 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
531 $sign_success = static::connect_sign( $compat_what . $data['nonce'], $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
532 if ( empty( $sign_success ) ) { // error from openssl, openssl_sign().
533 $alg = defined( 'OPENSSL_ALGO_SHA1' ) ? OPENSSL_ALGO_SHA1 : false; // to set default SHA1, to disconnect.
534 MainWP_Logger::instance()->debug_for_website( $website, 'get_renew_post_data_authed', '[' . $website->url . '] :: [openssl_sign:failed] :: Set sign_algo=SHA1' );
535 $sign_success = static::connect_sign( $compat_what . $data['nonce'], $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
536 }
537
538 if ( false !== $alg ) {
539 $data['sign_algo'] = $alg;
540 }
541 }
542
543 if ( $use_seclib ) {
544 $data['verifylib'] = 1;
545 }
546
547 if ( null !== $sign_success && empty( $sign_success ) ) {
548 $sign_error = '';
549 while ( $msg = openssl_error_string() ) {
550 if ( is_string( $msg ) ) {
551 $sign_error .= $msg;
552 }
553 }
554 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
555 }
556
557 $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
558
559 return http_build_query( $data, '', '&' );
560 }
561 return null;
562 }
563
564
565 /**
566 * Method get_get_data_authed()
567 *
568 * Get authorized $_GET data & build query.
569 *
570 * @param mixed $website Child Site data.
571 * @param mixed $paramValue OpenSSL parameter.
572 * @param string $paramName Parameter name.
573 * @param bool $asArray true|false Default is false.
574 * @param array $other_params other params.
575 *
576 * @return string $url
577 */
578 public static function get_get_data_authed( $website, $paramValue, $paramName = 'where', $asArray = false, $other_params = array() ) { //phpcs:ignore -- NOSONAR - complex method.
579 $params = array();
580 if ( $website && '' !== $paramValue ) {
581
582 $sign_success = null;
583 $alg = false;
584 $use_seclib = false;
585 $nonce = wp_rand( 0, 9999 );
586 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
587 $sign_success = MainWP_Connect_Lib::connect_sign( $paramValue . $nonce, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
588 $use_seclib = true;
589 } elseif ( function_exists( 'openssl_verify' ) ) {
590 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
591 $sign_success = static::connect_sign( $paramValue . $nonce, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
592 }
593
594 $signature = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
595
596 if ( null !== $sign_success && empty( $sign_success ) ) {
597 $sign_error = '';
598 while ( $msg = openssl_error_string() ) {
599 if ( is_string( $msg ) ) {
600 $sign_error .= $msg;
601 }
602 }
603 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [login_required=1] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
604 }
605
606 $params = array(
607 'login_required' => 1,
608 'user' => rawurlencode( $website->adminname ),
609 'mainwpsignature' => rawurlencode( $signature ),
610 'nonce' => $nonce,
611 $paramName => rawurlencode( $paramValue ),
612 );
613
614 if ( is_array( $other_params ) ) {
615 foreach ( $other_params as $name => $value ) {
616 if ( is_string( $name ) && ! empty( $name ) && is_scalar( $value ) ) {
617 $params[ sanitize_text_field( wp_unslash( $name ) ) ] = rawurlencode( sanitize_text_field( wp_unslash( $value ) ) );
618 }
619 }
620 }
621
622 if ( ! empty( $params['login_required'] ) && ! empty( $params['where'] ) ) {
623 $open_params = apply_filters( 'mainwp_open_site_login_required_params', false, $params, $website );
624 if ( is_array( $open_params ) && ! empty( $open_params ) ) {
625 $where_params = '';
626 foreach ( $open_params as $key => $value ) {
627 $where_params .= rawurlencode( sanitize_text_field( wp_unslash( $key ) ) ) . '=' . rawurlencode( sanitize_text_field( wp_unslash( $value ) ) ) . '&';
628 }
629 if ( ! empty( $where_params ) ) {
630 $params['where_params'] = rawurlencode( rtrim( $where_params, '&' ) );
631 }
632 }
633 }
634
635 if ( false !== $alg ) {
636 $params['sign_algo'] = $alg;
637 }
638
639 if ( ! empty( $use_seclib ) ) {
640 $params['verifylib'] = 1;
641 }
642
643 /**
644 * Current user global.
645 *
646 * @global string
647 */
648 global $current_user;
649
650 if ( ( ( ! defined( 'DOING_CRON' ) || false === DOING_CRON ) && ( ! defined( 'WP_CLI' ) || false === WP_CLI ) ) && $current_user && $current_user->ID ) {
651 /** This filter is documented in ../class/class-mainwp-connect.php */
652 $alter_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
653 if ( ! empty( $alter_user ) ) {
654 $params['alt_user'] = rawurlencode( $alter_user );
655 }
656 }
657 }
658
659 if ( $asArray ) {
660 return $params;
661 }
662
663 $url = ( isset( $website->url ) && '' !== $website->url ? $website->url : $website->siteurl );
664 $url .= ( substr( $url, - 1 ) !== '/' ? '/' : '' );
665 $url .= '?';
666
667 foreach ( $params as $key => $value ) {
668 $url .= $key . '=' . $value . '&';
669 }
670 return rtrim( $url, '&' );
671 }
672
673 /**
674 * Method connect_sign()
675 *
676 * Sign connect.
677 *
678 * @param string $data Data sign.
679 * @param string $signature signature.
680 * @param string $privkey Private key.
681 * @param mixed $algorithm signature algorithm.
682 * @param int $site_id site id.
683 *
684 * @return bool Success or not.
685 */
686 public static function connect_sign( $data, &$signature, $privkey, $algorithm, $site_id ) {
687 $de_privkey = MainWP_Encrypt_Data_Lib::instance()->decrypt_privkey( $privkey, $site_id );
688
689 if ( empty( $de_privkey ) ) {
690 MainWP_Logger::instance()->debug( 'Error: Failed to decrypt the priv key.' );
691 }
692
693 if ( empty( $de_privkey ) ) {
694 $de_privkey = $privkey; // compatible.
695 }
696 if ( false === $algorithm ) {
697 return openssl_sign( $data, $signature, $de_privkey ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
698 } else {
699 return openssl_sign( $data, $signature, $de_privkey, $algorithm ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
700 }
701 }
702
703 /**
704 * Method get_post_data_not_authed()
705 *
706 * Get not authorized $_POST data.
707 *
708 * @param mixed $url Child site URL.
709 * @param mixed $admin Admin Username.
710 * @param mixed $what What function to perform.
711 * @param null $params Function parameters.
712 *
713 * @return mixed null|http_build_query()
714 */
715 public static function get_post_data_not_authed( $url, $admin, $what, $params = null ) {
716 if ( '' !== $url && '' !== $admin && '' !== $what ) {
717 $data = array();
718 $data['user'] = $admin;
719 $data['function'] = $what;
720 $data['mainwpver'] = MainWP_System::$version;
721
722 if ( null !== $params ) {
723 $data = array_merge( $data, $params );
724 }
725
726 return http_build_query( $data, '', '&' );
727 }
728
729 return null;
730 }
731
732 /**
733 * Method fetch_urls_authed()
734 *
735 * Fetches data from child sites if authenticated.
736 *
737 * @param object $websites Websites information.
738 * @param string $what Action to perform.
739 * @param array $params Request parameters.
740 * @param mixed $handler Request handler.
741 * @param mixed $output Request output.
742 * @param mixed $whatPage Request URL. Default /admin-ajax.php.
743 * @param array $others Request additional information.
744 *
745 * @return bool true|false
746 *
747 * @uses \MainWP\Dashboard\MainWP_System::$version
748 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_mainwp_dir()
749 */
750 public static function fetch_urls_authed( &$websites, $what, $params, $handler, &$output, $whatPage = null, $others = array() ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity -- NOSONAR - complex function. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
751
752 if ( ! is_array( $websites ) || empty( $websites ) ) {
753 return false;
754 }
755
756 if ( ! is_array( $params ) ) {
757 $params = array();
758 }
759
760 $sleep_int = (int) get_option( 'mainwp_chunksleepinterval', 5 );
761 $chunkSize = (int) get_option( 'mainwp_chunksitesnumber', 10 );
762
763 $chunkSize = apply_filters( 'mainwp_fetch_urls_chunk_size', $chunkSize );
764 if ( count( $websites ) > $chunkSize ) {
765 $total = count( $websites );
766 $loops = ceil( $total / $chunkSize );
767 for ( $i = 0; $i < $loops; $i++ ) {
768 $newSites = array_slice( $websites, $i * $chunkSize, $chunkSize, true );
769 static::fetch_urls_authed( $newSites, $what, $params, $handler, $output, $whatPage, $others );
770 sleep( $sleep_int );
771 }
772
773 return false;
774 }
775
776 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
777 $mh = curl_multi_init();
778
779 $timeout = 20 * 60 * 60;
780
781 $disabled_functions = ini_get( 'disable_functions' );
782 $handleToWebsite = array();
783 $requestUrls = array();
784 $requestHandles = array();
785
786 $dirs = MainWP_System_Utility::get_mainwp_dir();
787 $cookieDir = $dirs[0] . 'cookies';
788
789 static::init_cookiesdir( $cookieDir );
790
791 $_org_params = null;
792
793 foreach ( $websites as $website ) {
794
795 if ( MainWP_Demo_Handle::get_instance()->is_demo_website( $website ) ) {
796 MainWP_Demo_Handle::get_instance()->handle_fetch_urls_demo( $data, $website, $output, $what, $params );
797 continue;
798 }
799
800 $url = $website->url;
801 if ( '/' !== substr( $url, - 1 ) ) {
802 $url .= '/';
803 }
804
805 if ( false === strpos( $url, 'wp-admin' ) ) {
806 $url .= 'wp-admin/';
807 }
808
809 if ( null !== $whatPage ) {
810 $url .= $whatPage;
811 } else {
812 $url .= 'admin-ajax.php';
813 }
814
815 if ( property_exists( $website, 'http_user' ) ) {
816 $http_user = $website->http_user;
817 }
818 if ( property_exists( $website, 'http_pass' ) ) {
819 $http_pass = $website->http_pass;
820 }
821
822 if ( isset( $params ) && isset( $params['new_post'] ) ) {
823
824 if ( null === $_org_params ) {
825 $_org_params = $params;
826 }
827
828 /**
829 * Filter is being replaced with mainwp_pre_posting_posts.
830 *
831 * @deprecated
832 */
833 $params = apply_filters_deprecated(
834 'mainwp-pre-posting-posts',
835 array(
836 ( is_array( $params ) ? $params : array() ),
837 (object) array(
838 'id' => $website->id,
839 'url' => $website->url,
840 'name' => $website->name,
841 ),
842 ),
843 '4.0.7.2', // NOSONAR - not IP.
844 'mainwp_pre_posting_posts'
845 );
846
847 /**
848 * Filter: mainwp_pre_posting_posts
849 *
850 * Prepares parameters for the authenticated cURL post.
851 *
852 * @since 4.1
853 */
854 $params = apply_filters(
855 'mainwp_pre_posting_posts',
856 ( is_array( $params ) ? $params : array() ),
857 (object) array(
858 'id' => $website->id,
859 'url' => $website->url,
860 'name' => $website->name,
861 )
862 );
863 }
864
865 $ch = curl_init();
866
867 $proxy = new \WP_HTTP_Proxy();
868 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
869 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
870 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
871 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
872
873 if ( $proxy->use_authentication() ) {
874 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
875 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
876 }
877 }
878
879 if ( ( null !== $website ) && ( ( property_exists( $website, 'wpe' ) && 1 !== $website->wpe ) || ( isset( $others['upgrade'] ) && ( true === $others['upgrade'] ) ) ) ) {
880 // to fix.
881 if ( defined( 'LOGGED_IN_SALT' ) && defined( 'NONCE_SALT' ) ) {
882 $cookie_salt = sha1( sha1( 'mainwp' . LOGGED_IN_SALT . $website->id ) . NONCE_SALT . 'WP_Cookie' ); // NOSONAR - safe for salt file name.
883 } else {
884 $cookie_salt = sha1( sha1( 'mainwp' . $website->id ) . 'WP_Cookie' ); // NOSONAR - safe for salt file name.
885 }
886 $cookieFile = $cookieDir . '/' . $cookie_salt;
887 if ( ! file_exists( $cookieFile ) ) {
888 @file_put_contents( $cookieFile, '' );
889 }
890
891 if ( file_exists( $cookieFile ) ) {
892 @chmod( $cookieFile, 0644 ); // NOSONAR - correct file permissions, owner: rwe, group & others: r.
893 curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookieFile );
894 curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookieFile );
895 }
896 }
897
898 curl_setopt( $ch, CURLOPT_URL, $url );
899 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
900 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
901 curl_setopt( $ch, CURLOPT_POST, true );
902
903 $postdata = static::get_post_data_authed( $website, $what, $params );
904 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
905 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
906 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
907 curl_setopt( $ch, CURLOPT_REFERER, get_option( 'siteurl' ) );
908 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
909 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
910 $http_pass = stripslashes( $http_pass );
911 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
912 }
913
914 $ssl_verifyhost = false;
915 $verifyCertificate = isset( $website->verify_certificate ) ? (int) $website->verify_certificate : null;
916 if ( null !== $verifyCertificate ) {
917 if ( 1 === $verifyCertificate ) {
918 $ssl_verifyhost = true;
919 } elseif ( 2 === $verifyCertificate ) {
920 if ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
921 $ssl_verifyhost = true;
922 }
923 }
924 } elseif ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
925 $ssl_verifyhost = true;
926 }
927
928 if ( $ssl_verifyhost ) {
929 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
930 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
931 } else {
932 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR.
933 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR.
934 }
935
936 curl_setopt( $ch, CURLOPT_SSLVERSION, $website->ssl_version );
937
938 if ( is_object( $website ) && property_exists( $website, 'id' ) ) {
939 $http_version = apply_filters( 'mainwp_curl_http_version', false, $website->id );
940 if ( false !== $http_version ) {
941 curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
942 }
943
944 $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, $website->id, $website->url );
945 if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
946 curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
947 curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
948 }
949 }
950
951 curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
952 MainWP_System_Utility::set_time_limit( $timeout );
953
954 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
955 @curl_multi_add_handle( $mh, $ch );
956 }
957
958 $handleToWebsite[ static::get_resource_id( $ch ) ] = $website;
959 $requestUrls[ static::get_resource_id( $ch ) ] = $website->url;
960 $requestHandles[ static::get_resource_id( $ch ) ] = $ch;
961
962 if ( null !== $_org_params ) {
963 $params = $_org_params;
964 }
965 }
966
967 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
968 $lastRun = 0;
969 do {
970 if ( 20 < time() - $lastRun ) {
971 MainWP_System_Utility::set_time_limit( $timeout );
972 $lastRun = time();
973 }
974
975 curl_multi_exec( $mh, $running );
976 curl_multi_select( $mh );
977 while ( $info = curl_multi_info_read( $mh ) ) {
978 $data = curl_multi_getcontent( $info['handle'] );
979 $contains = ( 0 < preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) );
980 curl_multi_remove_handle( $mh, $info['handle'] );
981
982 $rid = static::get_resource_id( $info['handle'] );
983 if ( ! $contains && isset( $requestUrls[ $rid ] ) ) {
984 curl_setopt( $info['handle'], CURLOPT_URL, $requestUrls[ $rid ] );
985 curl_setopt( $info['handle'], CURLOPT_FRESH_CONNECT, true );
986 curl_setopt( $info['handle'], CURLOPT_FORBID_REUSE, true );
987 curl_multi_add_handle( $mh, $info['handle'] );
988 unset( $requestUrls[ $rid ] );
989 continue; // libcurl updates $running automatically.
990 }
991
992 if ( null !== $handler ) {
993 $site = &$handleToWebsite[ static::get_resource_id( $info['handle'] ) ];
994 call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
995 }
996
997 unset( $handleToWebsite[ static::get_resource_id( $info['handle'] ) ] );
998 if ( static::is_valid_curl_handle( $info['handle'] ) ) {
999 curl_close( $info['handle'] );
1000 }
1001 unset( $info['handle'] );
1002 }
1003 usleep( 10000 );
1004 } while ( $running > 0 );
1005
1006 if ( static::is_valid_curl_handle( $mh ) ) {
1007 curl_multi_close( $mh );
1008 }
1009 } else {
1010 foreach ( $requestHandles as $ch ) {
1011 $data = curl_exec( $ch );
1012
1013 if ( null !== $handler ) {
1014 $site = &$handleToWebsite[ static::get_resource_id( $ch ) ];
1015 call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
1016 }
1017 }
1018 }
1019
1020 return true;
1021 }
1022
1023 /**
1024 * Credits WordPress org.
1025 *
1026 * Get the correct "Expect" header for the given request data.
1027 *
1028 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
1029 * @return string The "Expect" header.
1030 */
1031 public static function get_expect_header( $data ) {
1032 if ( ! is_array( $data ) ) {
1033 return strlen( (string) $data ) >= 1048576 ? '100-Continue' : '';
1034 }
1035
1036 $bytesize = 0;
1037 $iterator = new \RecursiveIteratorIterator( new \RecursiveArrayIterator( $data ) );
1038
1039 foreach ( $iterator as $datum ) {
1040 $bytesize += strlen( (string) $datum );
1041
1042 if ( $bytesize >= 1048576 ) {
1043 return '100-Continue';
1044 }
1045 }
1046
1047 return '';
1048 }
1049
1050 /**
1051 * Method get_resource_id()
1052 *
1053 * Get resource id.
1054 *
1055 * @param mixed $res The given resource.
1056 *
1057 * @return $result Resource ID only.
1058 */
1059 public static function get_resource_id( $res ) {
1060 $result = false;
1061 if ( is_a( $res, 'CurlHandle' ) ) {
1062 $result = spl_object_hash( $res );
1063 } elseif ( is_resource( $res ) ) {
1064 $resourceString = (string) $res;
1065 $exploded = explode( '#', $resourceString );
1066 $result = array_pop( $exploded );
1067 }
1068 return $result;
1069 }
1070
1071 /**
1072 * Method get_lock_identifier().
1073 *
1074 * Get lock identifier.
1075 *
1076 * @param mixed $pLockName Provided Lock Name.
1077 *
1078 * @return mixed false|sem_get()|@fopen
1079 */
1080 public static function get_lock_identifier( $pLockName ) {
1081 if ( ( null === $pLockName ) || ( false === $pLockName ) ) {
1082 return false;
1083 }
1084
1085 if ( function_exists( 'sem_get' ) ) {
1086 return sem_get( $pLockName );
1087 } else {
1088 $fh = @fopen( sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'lock' . $pLockName . '.txt', 'w+' );
1089 if ( ! $fh ) {
1090 return false;
1091 }
1092
1093 return $fh;
1094 }
1095 }
1096
1097 /**
1098 * Method lock()
1099 *
1100 * Use sem_acquire or @flock to lock the $identifier.
1101 *
1102 * @param mixed $identifier Identifier.
1103 *
1104 * @return mixed false|sem_acquire()|@flock
1105 */
1106 public static function lock( $identifier ) {
1107 if ( ( null === $identifier ) || ( false === $identifier ) ) {
1108 return false;
1109 }
1110
1111 if ( function_exists( 'sem_acquire' ) ) {
1112 return sem_acquire( $identifier );
1113 } else {
1114 if ( ! is_resource( $identifier ) ) {
1115 return false; // to fix.
1116 }
1117 for ( $i = 0; $i < 3; $i++ ) {
1118 if ( @flock( $identifier, LOCK_EX ) ) {
1119 return $identifier;
1120 } else {
1121 sleep( 1 );
1122 }
1123 }
1124 return false;
1125 }
1126 }
1127
1128 /**
1129 * Method release()
1130 *
1131 * Use sem_release or @flock, @fclose to unlock $identifier.
1132 *
1133 * @param mixed $identifier Identifier.
1134 *
1135 * @return mixed false|sem_release()|@flock
1136 */
1137 public static function release( $identifier ) {
1138 if ( ( null === $identifier ) || ( false === $identifier ) ) {
1139 return false;
1140 }
1141
1142 if ( function_exists( 'sem_release' ) ) {
1143 return sem_release( $identifier );
1144 } else {
1145 if ( ! is_resource( $identifier ) ) {
1146 return false; // to fix.
1147 }
1148 @flock( $identifier, LOCK_UN );
1149 @fclose( $identifier );
1150 }
1151
1152 return false;
1153 }
1154
1155 /**
1156 * Method fetch_url_authed()
1157 *
1158 * Updates the child site via authenticated request.
1159 *
1160 * @param object $website Website information.
1161 * @param string $what Function to perform.
1162 * @param null $params Function parameters.
1163 * @param bool $checkConstraints Whether or not to check constraints.
1164 * @param bool $pForceFetch Whether or not to force the fetch.
1165 * @param bool $pRetryFailed Whether or not to retry the fetch process.
1166 * @param null $rawResponse Raw response.
1167 *
1168 * @return mixed $information
1169 *
1170 * @uses \MainWP\Dashboard\MainWP_Monitoring_Handler::handle_check_website()
1171 * @uses \MainWP\Dashboard\MainWP_Premium_Update::maybe_request_premium_updates()
1172 * @uses \MainWP\Dashboard\MainWP_Sync::sync_information_array()
1173 */
1174 public static function fetch_url_authed( // phpcs:ignore -- NOSONAR - complex.
1175 &$website,
1176 $what,
1177 $params = null,
1178 $checkConstraints = false,
1179 $pForceFetch = false,
1180 $pRetryFailed = true,
1181 $rawResponse = null
1182 ) {
1183 unset( $pForceFetch );
1184
1185 // to support demo data.
1186 if ( MainWP_Demo_Handle::get_instance()->is_demo_website( $website ) ) {
1187 return MainWP_Demo_Handle::get_instance()->handle_action_demo( $website, $what );
1188 }
1189
1190 /**
1191 * Filter to mock fetch_url_authed response before any HTTP/signing occurs.
1192 *
1193 * This filter fires early, before OpenSSL signing or HTTP requests, allowing
1194 * tests to bypass child site communication entirely.
1195 *
1196 * SECURITY WARNING - TEST ONLY:
1197 * This filter ONLY fires when ALL of the following conditions are met:
1198 * 1. MAINWP_TESTING_MODE constant is defined and true
1199 * 2. A PHPUnit test harness constant is present (WP_TESTS_DOMAIN, PHPUNIT_COMPOSER_INSTALL, or WP_TESTS_DIR)
1200 *
1201 * This triple-check prevents malicious code from defining MAINWP_TESTING_MODE
1202 * in production to spoof child site responses.
1203 *
1204 * IMPORTANT: MAINWP_TESTING_MODE must ONLY be defined in the PHPUnit bootstrap
1205 * file (tests/bootstrap.php). Defining it in production code, wp-config.php, or
1206 * plugin files would create a security vulnerability allowing response spoofing.
1207 *
1208 * @since 5.4
1209 *
1210 * @param mixed $pre_result Return non-false to short-circuit and return this value.
1211 * @param object $website Website object being communicated with.
1212 * @param string $what Action being performed (e.g., 'plugin_action').
1213 * @param array $params Request parameters.
1214 * @return mixed Array to return early, false to proceed normally.
1215 */
1216 $is_phpunit_env = defined( 'WP_TESTS_DOMAIN' ) || defined( 'PHPUNIT_COMPOSER_INSTALL' ) || ( defined( 'WP_TESTS_DIR' ) && WP_TESTS_DIR );
1217 if ( defined( 'MAINWP_TESTING_MODE' ) && MAINWP_TESTING_MODE && $is_phpunit_env ) {
1218 $pre_result = apply_filters( 'mainwp_fetch_url_authed_pre', false, $website, $what, $params );
1219 if ( false !== $pre_result ) {
1220 return $pre_result;
1221 }
1222 }
1223
1224 if ( ! is_array( $params ) ) {
1225 $params = array();
1226 }
1227
1228 $others = array(
1229 'force_use_ipv4' => $website->force_use_ipv4,
1230 'upgrade' => ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ),
1231 );
1232
1233 $request_update = MainWP_Premium_Update::maybe_request_premium_updates( $website, $what, $params );
1234
1235 if ( isset( $rawResponse ) && $rawResponse ) {
1236 $others['raw_response'] = 'yes';
1237 }
1238
1239 $params['optimize'] = ( ( 1 === (int) get_option( 'mainwp_optimize', 1 ) ) ? 1 : 0 );
1240
1241 $updating_website = false;
1242 $type = '';
1243 $list = '';
1244 if ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ) {
1245 $updating_website = true;
1246 if ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1247 $type = $params['type'];
1248 $list = $params['list'];
1249 } else {
1250 $type = 'wp';
1251 $list = '';
1252 }
1253 }
1254
1255 if ( $updating_website ) {
1256 /**
1257 * Action: mainwp_website_before_updated
1258 *
1259 * Fires before the child site update process.
1260 *
1261 * @param object $website Object containing child site info.
1262 * @param string $type Type parameter.
1263 * @param string $list List parameter.
1264 *
1265 * @since Unknown
1266 */
1267 do_action( 'mainwp_website_before_updated', $website, $type, $list );
1268 }
1269
1270 if ( 'renew' === $what ) {
1271 $postdata = static::get_renew_post_data_authed( $website, $what );
1272 } else {
1273 $postdata = static::get_post_data_authed( $website, $what, $params );
1274
1275 }
1276 $others['function'] = $what;
1277
1278 $information = array();
1279 $output = array();
1280
1281 if ( ! $request_update ) {
1282 $information = static::fetch_url( $website, $website->url, $postdata, $checkConstraints, $website->verify_certificate, $pRetryFailed, $website->http_user, $website->http_pass, $website->ssl_version, $others, $output );
1283 if ( ! empty( $output ) ) {
1284 if ( ! is_array( $information ) ) {
1285 $information = array();
1286 }
1287 $information['fetch_url_output'] = $output;
1288 }
1289 /**
1290 * Fires immediately after fetch url action.
1291 *
1292 * @param object $website website.
1293 * @param array $information information result data.
1294 * @param string $what action.
1295 * @param array $params params input array.
1296 * @param array $others others input array.
1297 *
1298 * @since 4.5.1.1
1299 */
1300 do_action( 'mainwp_fetch_url_authed', $website, $information, $what, $params, $others );
1301 } else {
1302 $slug = $params['list'];
1303 $information['upgrades'] = array( $slug => 1 );
1304 }
1305
1306 if ( is_array( $information ) && isset( $information['sync'] ) && ! empty( $information['sync'] ) ) {
1307 MainWP_Sync::sync_information_array( $website, $information['sync'] );
1308 unset( $information['sync'] );
1309 }
1310
1311 if ( $updating_website ) {
1312 /**
1313 * Action: mainwp_website_updated
1314 *
1315 * Fires after the child site update process.
1316 *
1317 * @param object $website Object containing child site info.
1318 * @param string $type Type parameter.
1319 * @param string $list List parameter.
1320 * @param array $information Array containing the information fetched from the child site.
1321 *
1322 * @since Unknown
1323 */
1324 do_action( 'mainwp_website_updated', $website, $type, $list, $information );
1325 if ( 1 === (int) get_option( 'mainwp_check_http_response', 0 ) ) {
1326 MainWP_Monitoring_Handler::handle_check_website( $website, true );
1327 }
1328 }
1329
1330 return $information;
1331 }
1332
1333 /**
1334 * Method fetch_url_not_authed()
1335 *
1336 * Fetch not authorized URL.
1337 *
1338 * @param string $url URL to fetch from.
1339 * @param string $admin Admin name.
1340 * @param string $what Function to perform.
1341 * @param null $params Function parameters.
1342 * @param bool $pForceFetch true|false Whether or not to force the fetch.
1343 * @param null $verifyCertificate Verify the SSL Certificate.
1344 * @param null $http_user htaccess username.
1345 * @param null $http_pass htaccess password.
1346 * @param integer $sslVersion SSL version to check for.
1347 * @param array $others Other functions to perform.
1348 * @param array $output Output values.
1349 *
1350 * @return mixed static::fetch_url() Fetch URL.
1351 */
1352 public static function fetch_url_not_authed( // phpcs:ignore -- NOSONAR - compatible.
1353 $url,
1354 $admin,
1355 $what,
1356 $params = null,
1357 $pForceFetch = false,
1358 $verifyCertificate = null,
1359 $http_user = null,
1360 $http_pass = null,
1361 $sslVersion = 0,
1362 $others = array(),
1363 &$output = array()
1364 ) {
1365 unset( $pForceFetch );
1366
1367 if ( empty( $params ) ) {
1368 $params = array();
1369 }
1370
1371 $postdata = static::get_post_data_not_authed( $url, $admin, $what, $params );
1372 $website = null;
1373
1374 $others['function'] = $what;
1375 return static::fetch_url( $website, $url, $postdata, false, $verifyCertificate, true, $http_user, $http_pass, $sslVersion, $others, $output );
1376 }
1377
1378 /**
1379 * Method fetch_url()
1380 *
1381 * Fetch URL.
1382 *
1383 * @param object $website Child Site info.
1384 * @param string $url URL to fetch from.
1385 * @param mixed $postdata Post data to fetch.
1386 * @param bool $checkConstraints true|false Whether or not to check constraints.
1387 * @param null $verifyCertificate Verify SSL Certificate.
1388 * @param bool $pRetryFailed ture|false Whether or not the Retry has failed.
1389 * @param null $http_user htaccess username.
1390 * @param null $http_pass htaccess password.
1391 * @param integer $sslVersion SSL version.
1392 * @param array $others Other functions to perform.
1393 * @param array $output Output values.
1394 *
1395 * @throws \Exception Exception message.
1396 *
1397 * @return mixed static::fetch_url_site()
1398 */
1399 public static function fetch_url( // phpcs:ignore -- NOSONAR - complex.
1400 &$website,
1401 $url,
1402 $postdata,
1403 $checkConstraints = false,
1404 $verifyCertificate = null,
1405 $pRetryFailed = true,
1406 $http_user = null,
1407 $http_pass = null,
1408 $sslVersion = 0,
1409 $others = array(),
1410 &$output = array()
1411 ) {
1412
1413 $start = time();
1414
1415 try {
1416 $tmpUrl = $url;
1417 if ( '/' !== substr( $tmpUrl, - 1 ) ) {
1418 $tmpUrl .= '/';
1419 }
1420
1421 if ( false === strpos( $url, 'wp-admin' ) ) {
1422 $tmpUrl .= 'wp-admin/admin-ajax.php';
1423 }
1424
1425 return static::fetch_url_site( $website, $tmpUrl, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1426 } catch ( \Exception $e ) {
1427 if ( ! $pRetryFailed || ( 30 < ( time() - $start ) ) ) {
1428 throw $e;
1429 }
1430
1431 try {
1432 return static::fetch_url_site( $website, $url, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1433 } catch ( \Exception $ex ) {
1434 throw $e;
1435 }
1436 }
1437 }
1438
1439 /**
1440 * Method fetch_url_site()
1441 *
1442 * M Fetch URL.
1443 *
1444 * @param object $website Child Site info.
1445 * @param string $url URL to fetch from.
1446 * @param mixed $postdata Post data to fetch.
1447 * @param bool $checkConstraints true|false Whether or not to check constraints.
1448 * @param null $verifyCertificate Verify SSL Certificate.
1449 * @param null $http_user htaccess username.
1450 * @param null $http_pass htaccess password.
1451 * @param integer $sslVersion SSL version.
1452 * @param array $others Other functions to perform.
1453 * @param array $output Output values.
1454 *
1455 * @return mixed $data, $information.
1456 * @throws MainWP_Exception Exception message.
1457 *
1458 * @uses \MainWP\Dashboard\MainWP_DB_Common::insert_or_update_request_log()
1459 * @uses \MainWP\Dashboard\MainWP_Exception
1460 * @uses \MainWP\Dashboard\MainWP_Logger::debug_for_website()
1461 * @uses \MainWP\Dashboard\MainWP_System::$version
1462 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_mainwp_dir()
1463 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_child_response()
1464 * @uses \MainWP\Dashboard\MainWP_Utility::value_to_string()
1465 * @uses \MainWP\Dashboard\MainWP_Utility::end_session()
1466 */
1467 public static function fetch_url_site( // phpcs:ignore -- NOSONAR - complex method. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1468 &$website,
1469 $url,
1470 $postdata,
1471 $checkConstraints = false,
1472 $verifyCertificate = null,
1473 $http_user = null,
1474 $http_pass = null,
1475 $sslVersion = 0,
1476 $others = array(),
1477 &$output = array()
1478 ) {
1479
1480 /**
1481 * Enables data to be returned prior to connecting to the site.
1482 *
1483 * @since 5.5
1484 *
1485 * @param mixed false
1486 * @param mixed $website
1487 * @param mixed $url
1488 * @param mixed $postdata
1489 * @param mixed $checkConstraints
1490 * @param mixed $verifyCertificate
1491 * @param mixed $http_user
1492 * @param mixed $http_pass
1493 * @param mixed $sslVersion
1494 * @param mixed $others
1495 * @param mixed $output
1496 */
1497 $dev_data = apply_filters( 'mainwp_dev_return_data_before_connect_site', false, $website, $url, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1498 if ( false !== $dev_data ) {
1499 return $dev_data;
1500 }
1501
1502 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1503
1504 if ( ! empty( $website ) ) {
1505 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Request to [' . $url . '] [' . MainWP_Utility::value_to_string( $postdata, 1 ) . ']' );
1506 }
1507
1508 $identifier = null;
1509 if ( $checkConstraints ) {
1510 static::check_constraints( $identifier, $website );
1511 }
1512
1513 if ( null !== $website ) {
1514 MainWP_DB_Common::instance()->insert_or_update_request_log( $website->id, null, microtime( true ), null );
1515 }
1516
1517 if ( null !== $identifier ) {
1518 static::release( $identifier );
1519 }
1520
1521 $dirs = MainWP_System_Utility::get_mainwp_dir();
1522 $cookieDir = $dirs[0] . 'cookies';
1523
1524 static::init_cookiesdir( $cookieDir );
1525
1526 $fetch_track_id = MainWP_Execution_Helper::execute_call_track( 'start_point', $website, $postdata );
1527
1528 $ch = curl_init();
1529
1530 $proxy = new \WP_HTTP_Proxy();
1531 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1532 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1533 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1534 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
1535
1536 if ( $proxy->use_authentication() ) {
1537 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1538 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1539 }
1540 }
1541
1542 if ( ( null !== $website ) && ( ( property_exists( $website, 'wpe' ) && 1 !== $website->wpe ) || ( isset( $others['upgrade'] ) && ( true === $others['upgrade'] ) ) ) ) {
1543 // to fix.
1544 if ( defined( 'LOGGED_IN_SALT' ) && defined( 'NONCE_SALT' ) ) {
1545 $cookie_salt = sha1( sha1( 'mainwp' . LOGGED_IN_SALT . $website->id ) . NONCE_SALT . 'WP_Cookie' ); // NOSONAR - safe for salt file name.
1546 } else {
1547 $cookie_salt = sha1( sha1( 'mainwp' . $website->id ) . 'WP_Cookie' ); // NOSONAR - safe for salt file name.
1548 }
1549 $cookieFile = $cookieDir . '/' . $cookie_salt;
1550 if ( ! file_exists( $cookieFile ) ) {
1551 @file_put_contents( $cookieFile, '' );
1552 }
1553
1554 if ( file_exists( $cookieFile ) ) {
1555 @chmod( $cookieFile, 0644 ); // NOSONAR - correct file permissions, owner: rwe, group & others: r.
1556 curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookieFile );
1557 curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookieFile );
1558 }
1559 }
1560
1561 curl_setopt( $ch, CURLOPT_URL, $url );
1562 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1563 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
1564 curl_setopt( $ch, CURLOPT_POST, true );
1565 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
1566 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
1567 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1568 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1569
1570 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
1571 $http_pass = stripslashes( $http_pass );
1572 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
1573 }
1574
1575 $ssl_verifyhost = false;
1576 if ( null !== $verifyCertificate ) {
1577 if ( 1 === (int) $verifyCertificate ) {
1578 $ssl_verifyhost = true;
1579 } elseif ( 2 === (int) $verifyCertificate ) {
1580 if ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
1581 $ssl_verifyhost = true;
1582 }
1583 }
1584 } elseif ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
1585 $ssl_verifyhost = true;
1586 }
1587
1588 if ( $ssl_verifyhost ) {
1589 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
1590 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
1591 } else {
1592 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR.
1593 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR.
1594 }
1595
1596 curl_setopt( $ch, CURLOPT_SSLVERSION, $sslVersion );
1597
1598 $http_version = apply_filters( 'mainwp_curl_http_version', false, $website ? $website->id : false, $url );
1599 if ( false !== $http_version ) {
1600 curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
1601 }
1602
1603 $curlopt_resolve = false;
1604
1605 if ( is_object( $website ) && property_exists( $website, 'id' ) ) {
1606 $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, $website->id, $website->url );
1607 }
1608
1609 if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
1610 curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
1611 curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
1612 }
1613
1614 $headers = array( 'X-Requested-With' => 'XMLHttpRequest' );
1615 $headers['Expect'] = static::get_expect_header( $postdata );
1616
1617 $headers = apply_filters( 'mainwp_connect_http_request_headers', $headers, $website );
1618
1619 if ( class_exists( '\WpOrg\Requests\Requests' ) ) {
1620 $headers = \WpOrg\Requests\Requests::flatten( $headers );
1621 } else {
1622 $headers = \Requests::flatten( $headers );
1623 }
1624
1625 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
1626 curl_setopt( $ch, CURLOPT_REFERER, get_option( 'siteurl' ) );
1627
1628 $force_use_ipv4 = false;
1629 $forceUseIPv4 = isset( $others['force_use_ipv4'] ) ? (int) $others['force_use_ipv4'] : null;
1630 if ( null !== $forceUseIPv4 ) {
1631 if ( 1 === $forceUseIPv4 ) {
1632 $force_use_ipv4 = true;
1633 } elseif ( 2 === $forceUseIPv4 ) {
1634 if ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
1635 $force_use_ipv4 = true;
1636 }
1637 }
1638 } elseif ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
1639 $force_use_ipv4 = true;
1640 }
1641
1642 if ( $force_use_ipv4 && defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
1643 curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
1644 }
1645
1646 $timeout = 20 * 60 * 60;
1647 curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1648 MainWP_System_Utility::set_time_limit( $timeout );
1649
1650 MainWP_Utility::end_session();
1651
1652 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Executing handlers' );
1653
1654 $disabled_functions = ini_get( 'disable_functions' );
1655 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
1656 $mh = @curl_multi_init();
1657 @curl_multi_add_handle( $mh, $ch );
1658
1659 $lastRun = 0;
1660 $running = null;
1661
1662 do {
1663 if ( 20 < time() - $lastRun ) {
1664 MainWP_System_Utility::set_time_limit( $timeout );
1665 $lastRun = time();
1666 }
1667
1668 do {
1669 $mrc = curl_multi_exec( $mh, $running );
1670 } while ( CURLM_CALL_MULTI_PERFORM === $mrc );
1671
1672
1673 $rc = curl_multi_select( $mh, 1.0 );
1674 if ( -1 === $rc ) {
1675 usleep( 100000 );
1676 }
1677
1678 while ( $info = @curl_multi_info_read( $mh ) ) {
1679 $data = @curl_multi_getcontent( $info['handle'] );
1680 $http_status = @curl_getinfo( $info['handle'], CURLINFO_HTTP_CODE );
1681 $err = @curl_error( $info['handle'] );
1682 $real_url = @curl_getinfo( $info['handle'], CURLINFO_EFFECTIVE_URL );
1683
1684 @curl_multi_remove_handle( $mh, $info['handle'] );
1685 curl_close( $info['handle'] );
1686 }
1687 usleep( 10000 );
1688 } while ( $running > 0 );
1689
1690 if ( static::is_valid_curl_handle( $mh ) ) {
1691 @curl_multi_close( $mh );
1692 }
1693 } else {
1694 $data = @curl_exec( $ch );
1695 $http_status = @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
1696 $err = @curl_error( $ch );
1697 $real_url = @curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
1698 curl_close( $ch );
1699 }
1700
1701 $host = wp_parse_url( $real_url, PHP_URL_HOST );
1702 $ip = gethostbyname( $host );
1703
1704 MainWP_Execution_Helper::execute_call_track( 'end_point', $website, $postdata, $fetch_track_id, 'fetch site' );
1705
1706 if ( null !== $website ) {
1707 MainWP_DB_Common::instance()->insert_or_update_request_log( $website->id, $ip, null, microtime( true ) );
1708 }
1709
1710 $raw_response = isset( $others['raw_response'] ) && 'yes' === $others['raw_response'] ? true : false;
1711
1712 $hidden_data = '[hidden response data]';
1713
1714 if ( ! apply_filters( 'mainwp_hide_raw_connection_response_data', true ) ) {
1715 $hidden_data = $data;
1716 }
1717
1718 if ( ! is_array( $output ) ) {
1719 $output = array();
1720 }
1721
1722 $output['fetch_data'] = $hidden_data;
1723
1724 $output['http_status'] = (int) $http_status;
1725
1726 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'http status: [' . $http_status . '] err: [' . $err . ']' );
1727 if ( '400' === $http_status ) {
1728 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'post data: [' . MainWP_Utility::value_to_string( $postdata, 1 ) . ']' );
1729 }
1730
1731 MainWP_Logger::instance()->log_execution_time( 'fetch_url_site :: [url=' . $url . ']' );
1732
1733 $thr_error = null;
1734
1735 if ( isset( $others['function'] ) ) {
1736 $what = $others['function'];
1737 if ( in_array( $what, array( 'installplugintheme', 'upgradeplugintheme', 'upgradetranslation', 'upgrade', 'stats', 'renew', 'reconnect' ), true ) ) {
1738 MainWP_Cache_Helper::invalidate_cache_group( MainWP_Cache_Helper::CGR_UPDATES );
1739 MainWP_Cache_Warm_Helper::invalidate_pages_by_site_actions( $what );
1740 }
1741 }
1742
1743 if ( ( false === $data ) && empty( $http_status ) ) {
1744 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=0][' . $err . ']' );
1745 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1746 $output['error_category'] = 'http_error';
1747 } elseif ( empty( $data ) && ! empty( $err ) ) {
1748 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=' . $http_status . '][' . $err . ']' );
1749 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1750 $output['error_category'] = 'http_error';
1751 $output['error_code'] = 'http_request_failed';
1752 $output['error_message'] = $err;
1753 } elseif ( 0 < preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) ) {
1754 $output['connection_step'] = 'verify_credentials';
1755 $result = $results[1];
1756 $information = MainWP_System_Utility::get_child_response( base64_decode( $result ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1757 unset( $output['fetch_data'] ); // hide the data.
1758 $pdt = is_string( $postdata ) ? $postdata : '';
1759 $data_log = is_array( $postdata ) ? print_r( $postdata, true ) : $pdt; //phpcs:ignore -- good.
1760 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', '[' . $url . '] postdata [' . $data_log . '] information: [OK]' ); //phpcs:ignore -- ok.
1761
1762 $error_code = is_array( $information ) && isset( $information['error_code'] ) ? sanitize_text_field( wp_unslash( $information['error_code'] ) ) : '';
1763 if ( ! empty( $error_code ) ) {
1764 $output['child_error_code'] = $error_code;
1765 }
1766 return $information;
1767 } elseif ( 200 === (int) $http_status && ! empty( $err ) ) {
1768 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1769 $output['error_category'] = 'http_error';
1770 $output['error_code'] = 'http_request_failed';
1771 $output['error_message'] = $err;
1772 } elseif ( $raw_response ) {
1773 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Response: [RAW]' );
1774 return $data;
1775 } else {
1776 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] Error: NOMAINWP [data=' . $hidden_data . ']' );
1777 $detect_wsidchk = is_string( $data ) ? strpos( $data, 'wsidchk' ) : false;
1778 if ( false !== $detect_wsidchk ) {
1779 $err_msg = 'Connection Failed. We suspect that Imunify360, a security layer added by your host, is causing this problem. Please contact your host to whitelist your Dashboard IP in their system. If you need help determining your MainWP Dashboard site IP address, check with your hosting provider.';
1780 $thr_error = new MainWP_Exception( 'ERROR:' . $err_msg, $url );
1781 } else {
1782 $thr_error = new MainWP_Exception( 'NOMAINWP', $url ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1783 $err_msg = 'Connection Failed. Please ensure that the MainWP Child plugin is installed and activated on the child site.';
1784 }
1785 $output['error_category'] = 'child_plugin_missing';
1786 $output['error_message'] = $err_msg;
1787 }
1788
1789 if ( null !== $thr_error ) {
1790 $thr_error->set_data( $hidden_data ); // to compatible.
1791 throw $thr_error;
1792 }
1793 }
1794
1795 /**
1796 * Method check_constraints()
1797 *
1798 * Check connection delay constraints.
1799 *
1800 * @param mixed $identifier Lock identifier.
1801 * @param mixed $website Object child site.
1802 *
1803 * @uses \MainWP\Dashboard\MainWP_DB_Common::close_open_requests()
1804 * @uses \MainWP\Dashboard\MainWP_DB::get_wp_ip()
1805 * @uses \MainWP\Dashboard\MainWP_Utility::end_session()
1806 */
1807 private static function check_constraints( &$identifier, $website ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1808 $semLock = '103218';
1809 $identifier = static::get_lock_identifier( $semLock );
1810 $minimumDelay = ( ( false === get_option( 'mainwp_minimumDelay' ) ) ? 200 : get_option( 'mainwp_minimumDelay' ) );
1811 if ( 0 < $minimumDelay ) {
1812 $minimumDelay = $minimumDelay / 1000;
1813 }
1814 $minimumIPDelay = ( ( false === get_option( 'mainwp_minimumIPDelay' ) ) ? 1000 : get_option( 'mainwp_minimumIPDelay' ) );
1815 if ( 0 < $minimumIPDelay ) {
1816 $minimumIPDelay = $minimumIPDelay / 1000;
1817 }
1818
1819 MainWP_Utility::end_session();
1820 $delay = true;
1821 while ( $delay ) {
1822 static::lock( $identifier );
1823 if ( 0 < $minimumDelay && static::check_constraints_last_request( $identifier, $minimumDelay ) ) {
1824 continue;
1825 }
1826
1827 if ( 0 < $minimumIPDelay && null !== $website ) {
1828 $ip = MainWP_DB::instance()->get_wp_ip( $website->id );
1829 if ( null !== $ip && '' !== $ip && static::check_constraints_last_request( $identifier, $minimumIPDelay, $ip ) ) {
1830 continue;
1831 }
1832 }
1833 $delay = false;
1834 }
1835
1836 $maximumRequests = ( ( false === get_option( 'mainwp_maximumRequests' ) ) ? 4 : get_option( 'mainwp_maximumRequests' ) );
1837 $maximumIPRequests = ( ( false === get_option( 'mainwp_maximumIPRequests' ) ) ? 1 : get_option( 'mainwp_maximumIPRequests' ) );
1838
1839 $first = true;
1840 $delay = true;
1841 while ( $delay ) {
1842 if ( ! $first ) {
1843 static::lock( $identifier );
1844 } else {
1845 $first = false;
1846 }
1847
1848 MainWP_DB_Common::instance()->close_open_requests();
1849
1850 if ( 0 < $maximumRequests && static::check_constraints_open_requests( $identifier, $maximumRequests ) ) {
1851 continue;
1852 }
1853
1854 if ( 0 < $maximumIPRequests && null !== $website ) {
1855 $ip = MainWP_DB::instance()->get_wp_ip( $website->id );
1856 if ( null !== $ip && '' !== $ip && static::check_constraints_open_requests( $identifier, $maximumIPRequests, $ip ) ) {
1857 continue;
1858 }
1859 }
1860 $delay = false;
1861 }
1862 }
1863
1864 /**
1865 * Method check_constraints_last_request().
1866 *
1867 * Check constraints for last requests.
1868 *
1869 * @param mixed $identifier connect identifier.
1870 * @param int $minimumDelay minimum delay.
1871 * @param string|null $ip ip address.
1872 *
1873 * @uses \MainWP\Dashboard\MainWP_DB_Common::get_last_request_timestamp()
1874 */
1875 private static function check_constraints_last_request( $identifier, $minimumDelay, $ip = null ) {
1876 $lastRequest = MainWP_DB_Common::instance()->get_last_request_timestamp( $ip );
1877 if ( $lastRequest > ( ( microtime( true ) ) - $minimumDelay ) ) {
1878 static::release( $identifier );
1879 $sleep = ( $minimumDelay - ( ( microtime( true ) ) - $lastRequest ) ) * 1000 * 1000;
1880 $sleep = max( 0, intval( $sleep ) );
1881 usleep( $sleep );
1882 return true;
1883 }
1884 return false;
1885 }
1886
1887 /**
1888 * Method check_constraints_open_requests().
1889 *
1890 * Check constraints for open requests.
1891 *
1892 * @param mixed $identifier connect identifier.
1893 * @param int $maximumRequests maximum requests.
1894 * @param string|null $ip ip address.
1895 *
1896 * @uses \MainWP\Dashboard\MainWP_DB_Common::get_nrof_open_requests()
1897 */
1898 private static function check_constraints_open_requests( $identifier, $maximumRequests, $ip = null ) {
1899 $nrOfOpenRequests = MainWP_DB_Common::instance()->get_nrof_open_requests( $ip );
1900 if ( $nrOfOpenRequests >= $maximumRequests ) {
1901 static::release( $identifier );
1902 usleep( 200000 );
1903 return true;
1904 }
1905 return false;
1906 }
1907
1908 /**
1909 * Method download_to_file()
1910 *
1911 * Download to file.
1912 *
1913 * @param mixed $url Download URL.
1914 * @param mixed $file File to download to.
1915 * @param bool $size Size of file.
1916 * @param null $http_user htaccess username.
1917 * @param null $http_pass htaccess password.
1918 *
1919 * @throws MainWP_Exception Exception message.
1920 *
1921 * @uses \MainWP\Dashboard\MainWP_Exception
1922 * @uses \MainWP\Dashboard\MainWP_System::$version
1923 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_wp_file_system()
1924 */
1925 public static function download_to_file( $url, $file, $size = false, $http_user = null, $http_pass = null ) { // phpcs:ignore -- NOSONAR - complex.
1926
1927 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
1928
1929 /**
1930 * WordPress files system object.
1931 *
1932 * @global object
1933 */
1934 global $wp_filesystem;
1935
1936 if ( $wp_filesystem->exists( $file ) && ( ( false === $size ) || ( $wp_filesystem->size( $file ) > $size ) ) ) {
1937 $wp_filesystem->delete( $file );
1938 }
1939
1940 if ( ! $wp_filesystem->exists( dirname( $file ) ) ) {
1941 $wp_filesystem->mkdir( dirname( $file ), 0777 );
1942 }
1943
1944 if ( ! $wp_filesystem->exists( dirname( $file ) ) ) {
1945 throw new MainWP_Exception( esc_html__( 'MainWP plugin could not create directory in order to download the file.', 'mainwp' ) );
1946 }
1947
1948 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
1949 if ( ! $wp_filesystem->is_writable( @dirname( $file ) ) ) {
1950 throw new MainWP_Exception( esc_html__( 'MainWP upload directory is not writable.', 'mainwp' ) );
1951 }
1952 } elseif ( ! is_writable( @dirname( $file ) ) ) { //phpcs:ignore -- ok.
1953 throw new MainWP_Exception( esc_html__( 'MainWP upload directory is not writable.', 'mainwp' ) );
1954 }
1955
1956 $fp = fopen( $file, 'a' );
1957 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1958 if ( false !== $size && $wp_filesystem->exists( $file ) ) {
1959 $size = $wp_filesystem->size( $file );
1960 $url .= '&foffset=' . $size;
1961 }
1962 $ch = curl_init( str_replace( ' ', '%20', $url ) );
1963
1964 $proxy = new \WP_HTTP_Proxy();
1965 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1966 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1967 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1968 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
1969
1970 if ( $proxy->use_authentication() ) {
1971 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1972 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1973 }
1974 }
1975 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
1976 curl_setopt( $ch, CURLOPT_HEADER, false );
1977 curl_setopt( $ch, CURLOPT_FILE, $fp );
1978
1979 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1980 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1981
1982 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
1983 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
1984 $http_pass = stripslashes( $http_pass );
1985 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
1986 }
1987 curl_exec( $ch );
1988 if ( static::is_valid_curl_handle( $ch ) ) {
1989 curl_close( $ch );
1990 }
1991 fclose( $fp );
1992 }
1993
1994 /**
1995 * Method init_coockiesdir()
1996 *
1997 * Check for cookies directory and create it if it doesn't already exist,
1998 * set the file permissions and update htaccess.
1999 *
2000 * @param mixed $cookieDir Cookies directory.
2001 *
2002 * @return void
2003 *
2004 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_wp_file_system()
2005 */
2006 public static function init_cookiesdir( $cookieDir ) {
2007
2008 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
2009
2010 /**
2011 * WordPress files system object.
2012 *
2013 * @global object
2014 */
2015 global $wp_filesystem;
2016
2017 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
2018
2019 if ( ! $wp_filesystem->is_dir( $cookieDir ) ) {
2020 $wp_filesystem->mkdir( $cookieDir, 0777 );
2021 }
2022
2023 if ( ! file_exists( $cookieDir . '/.htaccess' ) ) {
2024 $file_htaccess = $cookieDir . '/.htaccess';
2025 $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
2026 }
2027
2028 if ( ! file_exists( $cookieDir . '/index.php' ) ) {
2029 $file_index = $cookieDir . '/index.php';
2030 $wp_filesystem->touch( $file_index );
2031 }
2032 } else {
2033
2034 if ( ! file_exists( $cookieDir ) ) {
2035 @mkdir( $cookieDir, 0777, true );
2036 }
2037
2038 if ( ! file_exists( $cookieDir . '/.htaccess' ) ) {
2039 $file_htaccess = @fopen( $cookieDir . '/.htaccess', 'w+' );
2040 @fwrite( $file_htaccess, 'deny from all' );
2041 @fclose( $file_htaccess );
2042 }
2043
2044 if ( ! file_exists( $cookieDir . '/index.php' ) ) {
2045 $file_index = @fopen( $cookieDir . '/index.php', 'w+' );
2046 @fclose( $file_index );
2047 }
2048 }
2049 }
2050
2051 /**
2052 * Method get_file_content()
2053 *
2054 * Get contents of file.
2055 *
2056 * @param mixed $url File Location.
2057 *
2058 * @return mixed false|$data
2059 *
2060 * @uses \MainWP\Dashboard\MainWP_System::$version
2061 */
2062 public static function get_file_content( $url ) {
2063 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
2064 $ch = curl_init();
2065
2066 $proxy = new \WP_HTTP_Proxy();
2067 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
2068 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
2069 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
2070 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
2071
2072 if ( $proxy->use_authentication() ) {
2073 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
2074 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
2075 }
2076 }
2077
2078 curl_setopt( $ch, CURLOPT_HEADER, 0 );
2079 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
2080 curl_setopt( $ch, CURLOPT_URL, $url );
2081 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
2082 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
2083
2084 $data = @curl_exec( $ch );
2085 $httpCode = @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
2086 if ( static::is_valid_curl_handle( $ch ) ) {
2087 curl_close( $ch );
2088 }
2089 if ( 200 === (int) $httpCode ) {
2090 return $data;
2091 } else {
2092 return false;
2093 }
2094 }
2095
2096 /**
2097 * Method is_valid_curl_handle
2098 *
2099 * @param mixed $ch
2100 * @return bool Valid curl handle.
2101 */
2102 public static function is_valid_curl_handle( $ch ) {
2103 return is_resource( $ch )
2104 || ( is_object( $ch )
2105 && class_exists( 'CurlHandle', false )
2106 && $ch instanceof \CurlHandle
2107 );
2108 }
2109
2110 /**
2111 * Method get_favico_url()
2112 *
2113 * Get Child Site favicon URL.
2114 *
2115 * @param mixed $website Child Site info.
2116 *
2117 * @return mixed $faviurl Favicon URL.
2118 *
2119 * @uses \MainWP\Dashboard\MainWP_DB::get_website_option()
2120 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_icons_dir()
2121 * @uses \MainWP\Dashboard\MainWP_Utility::remove_http_prefix()
2122 */
2123 public static function get_favico_url( $website ) {
2124 $favi = MainWP_DB::instance()->get_website_option( $website, 'favi_icon', '' );
2125 $faviurl = '';
2126
2127 if ( ! empty( $favi ) ) {
2128 if ( false !== strpos( $favi, 'favi-' . intval( $website->id ) . '-' ) ) {
2129 $dirs = MainWP_System_Utility::get_icons_dir();
2130 if ( file_exists( $dirs[0] . $favi ) ) {
2131 $faviurl = $dirs[1] . $favi;
2132 } else {
2133 $faviurl = '';
2134 }
2135 } elseif ( ( 0 === strpos( $favi, '//' ) ) || ( 0 === strpos( $favi, 'http' ) ) ) {
2136 $faviurl = $favi;
2137 } else {
2138 $faviurl = $website->url . $favi;
2139 $faviurl = MainWP_Utility::remove_http_prefix( $faviurl );
2140 }
2141 }
2142
2143 if ( empty( $faviurl ) ) {
2144 $faviurl = false;
2145 }
2146
2147 return $faviurl;
2148 }
2149 }
2150