PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / trunk
MainWP Dashboard: Self-hosted WordPress Management for Agencies vtrunk
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 trunk, at class/class-mainwp-connect.php

2,498 lines 105.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 // Ask only for the record types read below. dns_get_record() defaults to DNS_ANY, which
210 // most resolvers now refuse or answer with a stub (RFC 8482), so it buys retries and
211 // timeouts instead of answers. Names that exist only in the hosts file are not resolved
212 // here at all -- dns_get_record() never reads the hosts file -- they fall through to the
213 // gethostbynamel() call below.
214 $found = false;
215 $dnsRecord = @dns_get_record( $host, DNS_A | DNS_AAAA | DNS_CNAME );
216 MainWP_Logger::instance()->debug( ' :: tryVisit :: [dnsRecord=' . MainWP_Utility::value_to_string( $dnsRecord, 1 ) . ']' );
217
218 if ( false !== $dnsRecord && is_array( $dnsRecord ) ) {
219 if ( ! isset( $dnsRecord['ip'] ) ) {
220 foreach ( $dnsRecord as $dnsRec ) {
221 if ( isset( $dnsRec['ip'] ) ) {
222 $ip = $dnsRec['ip'];
223 break;
224 }
225 }
226 } else {
227 $ip = $dnsRecord['ip'];
228 }
229
230 if ( ! isset( $dnsRecord['host'] ) ) {
231 foreach ( $dnsRecord as $dnsRec ) {
232 if ( $dnsRec['host'] === $host ) {
233 if ( 'CNAME' === $dnsRec['type'] ) {
234 $target = $dnsRec['target'];
235 }
236 $found = true;
237 break;
238 }
239 }
240 } else {
241 $found = ( $dnsRecord['host'] === $host );
242 if ( 'CNAME' === $dnsRecord['type'] ) {
243 $target = $dnsRecord['target'];
244 }
245 }
246 }
247
248 if ( false === $ip ) {
249 $ip = gethostbynamel( $host );
250 }
251 if ( ( false !== $target ) && ( $target !== $host ) ) {
252 $host .= ' (CNAME: ' . $target . ')';
253 }
254
255 $out = array(
256 'host' => $host,
257 'httpCode' => $http_status,
258 'httpCodeString' => MainWP_Utility::get_http_codes( $http_status ),
259 'httpVersion' => $http_version,
260 );
261
262 $hidden_data = '[hidden response data]';
263
264 if ( ( false === $ip || $ip === $host || ! static::validate_ip( $ip ) ) && apply_filters( 'mainwp_hide_raw_connection_response_data', true ) ) { // Failed to resolve hostname.
265 $data = $hidden_data;
266 }
267
268 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]' );
269
270 if ( false !== $ip ) {
271 $out['ip'] = $ip;
272 $found = true;
273 }
274
275 $out['error'] = ( '' === $err && false === $found ? 'Invalid host.' : $err );
276
277 return $out;
278 }
279
280
281 /**
282 * Method validate_ip().
283 *
284 * @param string $ip IP check.
285 * @return bool Check IP result.
286 */
287 public static function validate_ip( $ip ) {
288 // Validate the IP and check for private and reserved ranges.
289 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
290 return true;
291 }
292 return false;
293 }
294
295 /**
296 * Method check_ignored_http_code()
297 *
298 * Check if http error code is being ignored.
299 *
300 * @param mixed $value http error code.
301 * @param object|false $website website.
302 *
303 * @return bolean True|False.
304 */
305 public static function check_ignored_http_code( $value, $website = false ) { // phpcs:ignore -- NOSONAR -complex method.
306 $value = (int) $value;
307 $site_id = is_object( $website ) && ! empty( $website->id ) ? $website->id : 0;
308 $global_settings = MainWP_Uptime_Monitoring_Handle::get_global_monitoring_settings();
309
310 $ignored_code = '';
311
312 if ( $site_id ) {
313
314 $primary_monitor = MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by( $site_id, 'issub', 0 );
315 $global_settings = MainWP_Uptime_Monitoring_Handle::get_global_monitoring_settings();
316
317 $mo_active = 0;
318 if ( $primary_monitor ) {
319 $mo_active = MainWP_Uptime_Monitoring_Connect::get_apply_setting( 'active', (int) $primary_monitor->active, $global_settings, -1, 0 );
320 }
321
322 if ( $mo_active ) {
323 $ignored_code = MainWP_Uptime_Monitoring_Connect::instance()->get_up_codes( $primary_monitor, $global_settings );
324 } else {
325 $ignored_code = is_array( $global_settings ) && isset( $global_settings['up_status_codes'] ) ? $global_settings['up_status_codes'] : '';
326 }
327 } else {
328 $ignored_code = ! empty( $global_settings['up_status_codes'] ) ? $global_settings['up_status_codes'] : '';
329 }
330
331 if ( ! empty( $ignored_code ) ) {
332 $ignored_code = explode( ',', $ignored_code );
333 foreach ( $ignored_code as $code ) {
334 $code = trim( $code );
335 if ( (int) $value === (int) $code ) {
336 return true;
337 }
338 }
339 }
340 return false;
341 }
342
343 /**
344 * Method check website status.
345 *
346 * Check if the Website returns and http errors.
347 *
348 * @param object $website Child Site information.
349 * @param bool $chk_http_site Check site http response.
350 *
351 * @return mixed False|try visit result.
352 *
353 * @uses \MainWP\Dashboard\MainWP_Utility::is_domain_valid()
354 */
355 public static function check_website_status( $website, $chk_http_site = false ) { //phpcs:ignore -- NOSONAR - complexity.
356
357 if ( is_object( $website ) && isset( $website->id ) ) {
358 $primary_monitor = MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by( $website->id, 'issub', 0 );
359 if ( $primary_monitor ) {
360 // return compatible uptime status here.
361 return MainWP_Uptime_Monitoring_Handle::check_website_uptime_monitoring_status(
362 $primary_monitor,
363 array(
364 'ignore_compatible_save' => 1,
365 'check_http_site' => $chk_http_site,
366 )
367 ); // to ignore save compatible uptime status.
368 }
369 }
370
371 $http_user = null;
372 $http_pass = null;
373 $sslVersion = null;
374 $verifyCertificate = null;
375 $forceUseIPv4 = null;
376 if ( is_object( $website ) && isset( $website->url ) ) {
377 $url = $website->url;
378 $verifyCertificate = isset( $website->verify_certificate ) ? (int) $website->verify_certificate : null;
379 $forceUseIPv4 = $website->force_use_ipv4;
380 // MWP-1548: decrypt at the boundary so HTTP Basic Auth gets
381 // the plaintext credentials. Legacy plaintext rows pass
382 // through unchanged via the helper's fallback.
383 $http_user = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
384 $http_pass = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
385 $sslVersion = $website->ssl_version;
386 } else {
387 $url = $website;
388 }
389
390 if ( ! MainWP_Utility::is_domain_valid( $url ) ) {
391 return false;
392 }
393
394 $ssl_verifyhost = false;
395
396 if ( 1 === $verifyCertificate ) {
397 $ssl_verifyhost = true;
398 } elseif ( 2 === $verifyCertificate || null === $verifyCertificate ) {
399 if ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
400 $ssl_verifyhost = true;
401 }
402 }
403
404 $noBody = false;
405 return static::try_visit( $url, $ssl_verifyhost, $http_user, $http_pass, $sslVersion, $forceUseIPv4, $noBody );
406 }
407
408 /**
409 * Method get_post_data_authed()
410 *
411 * Get authorized $_POST data & build query.
412 *
413 * @param mixed $website Array of Child Site Info.
414 * @param mixed $what What we are posting.
415 * @param null $params Post parameters.
416 * @param array $others Other data.
417 *
418 * @return mixed null|http_build_query()
419 */
420 public static function get_post_data_authed( &$website, $what, $params = null, $others = array() ) { //phpcs:ignore -- NOSONAR - complex method.
421
422 if ( ! is_array( $others ) ) {
423 $others = array();
424 }
425
426 $verify_signature = ! empty( $others['verify_signature'] ) ? true : false;
427 $http_user_plain = ! empty( $others['http_user_plain'] ) ? $others['http_user_plain'] : '';
428 $http_pass_plain = ! empty( $others['http_pass_plain'] ) ? $others['http_pass_plain'] : '';
429
430 if ( $website && '' !== $what ) {
431 $data = array();
432 $data['user'] = $website->adminname;
433 $data['function'] = $what;
434 $data['nonce'] = wp_rand( 0, 9999 );
435 $data['mainwpver'] = MainWP_System::$version;
436
437 $params_filter = apply_filters( 'mainwp_pre_fetch_authed_data', false, $params, $what, $website, $verify_signature );
438 if ( is_array( $params_filter ) && ! empty( $params_filter ) ) {
439 $data = array_merge( $data, $params_filter );
440 }
441
442 if ( null !== $params ) {
443 $data = array_merge( $data, $params );
444 }
445
446 $alg = false;
447 $sign_success = null;
448 $use_seclib = false;
449
450 $data = apply_filters( 'mainwp_get_post_data_authed', $data, $website, $what, $params, $verify_signature );
451
452 $alt_user = '';
453
454 /**
455 * Current user global.
456 *
457 * @global string
458 */
459 global $current_user;
460
461 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 ) {
462 /**
463 * Filter: mainwp_alter_login_user
464 *
465 * Filters users accounts so it allows you user to jump to child site under alternative administrator account.
466 *
467 * @param int $website->id Child site ID.
468 * @param int $current_user->ID User ID.
469 *
470 * @since Unknown
471 */
472 $alt_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
473
474 }
475
476 $child_support_adv_sign = 1 === (int) MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
477
478 $alg = false;
479 $sign_value = $what . $data['nonce']; // Legacy signature data.
480
481 if ( ! $verify_signature || ! $child_support_adv_sign ) {
482 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
483 $sign_success = MainWP_Connect_Lib::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
484 $use_seclib = true;
485 } elseif ( function_exists( 'openssl_verify' ) ) {
486 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
487 $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
488 if ( false !== $alg ) {
489 $data['sign_algo'] = $alg;
490 }
491 }
492
493 if ( null !== $sign_success && empty( $sign_success ) ) {
494 $sign_error = '';
495 while ( $msg = openssl_error_string() ) {
496 if ( is_string( $msg ) ) {
497 $sign_error .= $msg;
498 }
499 }
500 $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
501 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 );
502 }
503 } else {
504 $signature = 'useadvancedmainwpsignature';
505 }
506
507 $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
508
509 if ( $verify_signature ) {
510 $ts = time();
511 $data_sign_v2 = array(
512 'base_function' => $what,
513 'nonce' => $data['nonce'],
514 'expires' => $ts + 60,
515 'user' => $website->adminname,
516 'req_id' => wp_generate_uuid4(),
517 );
518
519 if ( ! empty( $alt_user ) ) {
520 $data_sign_v2['alt_user'] = rawurlencode( $alt_user );
521 }
522
523 if ( 'process_premium_updates' === $what ) {
524 $add_sign_params = array( 'premium_perform', 'premium_type', 'list' );
525 foreach ( $add_sign_params as $_name ) {
526 if ( isset( $params[ $_name ] ) ) {
527 $data_sign_v2[ $_name ] = $params[ $_name ];
528 }
529 }
530 }
531
532 $sign_success_v2 = null;
533
534 $sign_value_v2 = wp_json_encode( $data_sign_v2 );
535
536 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
537 $use_seclib = true;
538 $sign_success_v2 = MainWP_Connect_Lib::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
539 } elseif ( function_exists( 'openssl_verify' ) ) {
540 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
541 $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
542 if ( false !== $alg ) {
543 $data['sign_algo'] = $alg;
544 }
545 }
546
547 $data['data_signature'] = $sign_value_v2;
548 $data['mainwpsignature_adv'] = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
549
550 if ( null !== $sign_success_v2 && empty( $sign_success_v2 ) ) {
551 $sign_error = '';
552 while ( $msg = openssl_error_string() ) {
553 if ( is_string( $msg ) ) {
554 $sign_error .= $msg;
555 }
556 }
557 $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
558 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT ADV SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . '] :: [pkey start =' . $pk_info . '...]', false );
559 }
560 }
561
562 if ( $use_seclib ) {
563 $data['verifylib'] = 1;
564 }
565
566 if ( ! empty( $alt_user ) ) {
567 $data['alt_user'] = rawurlencode( $alt_user );
568 }
569
570 /** This filter is documented in ../widgets/widget-mainwp-recent-posts.php */
571 $recent_number = apply_filters( 'mainwp_recent_posts_pages_number', 5 );
572 if ( 5 !== $recent_number ) {
573 $data['recent_number'] = $recent_number;
574 }
575
576 $scan_dir = apply_filters( 'mainwp_stats_scan_dir', false, $website );
577 if ( ! empty( $scan_dir ) ) {
578 $data['scan_dir'] = 1;
579 }
580
581 if ( 'process_premium_updates' === $what ) {
582 if ( ! empty( $http_user_plain ) && ! empty( $http_pass_plain ) ) {
583 // For post data.
584 $data['wp_http_user'] = $http_user_plain;
585 $data['wp_http_pass'] = $http_pass_plain;
586 }
587 $data['sslVerify'] = $website->verify_certificate ? 1 : 0;
588 }
589 return http_build_query( $data, '', '&' );
590 }
591
592 return null;
593 }
594
595 /**
596 * Method get_renew_post_data_authed()
597 *
598 * Get authorized $_POST data & build query for renew connection action only.
599 *
600 * @param mixed $website Array of Child Site Info.
601 * @param mixed $what What we are posting.
602 * @param array $others Other data.
603 *
604 * @return mixed null|http_build_query()
605 */
606 private static function get_renew_post_data_authed( &$website, $what, $others = array() ) { // phpcs:ignore -- NOSONAR - complex.
607
608 if ( ! is_array( $others ) ) {
609 $others = array();
610 }
611
612 $verify_signature = ! empty( $others['verify_signature'] ) ? true : false;
613
614 if ( $website && '' !== $what ) {
615 $compat_what = 'disconnect'; // to compatible, renew will call disconnect.
616 $data = array();
617 $data['user'] = $website->adminname;
618 $data['function'] = $compat_what;
619 $data['nonce'] = wp_rand( 0, 9999 );
620
621 $sign_value = $compat_what . $data['nonce']; // compatible format.
622
623 $alg = false;
624 $sign_success = null;
625 $use_seclib = false;
626
627 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
628 // to disconnect.
629 $sign_success = MainWP_Connect_Lib::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
630 $use_seclib = true;
631 } elseif ( function_exists( 'openssl_verify' ) ) {
632 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
633 $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
634 if ( empty( $sign_success ) ) { // error from openssl, openssl_sign().
635 $alg = defined( 'OPENSSL_ALGO_SHA1' ) ? OPENSSL_ALGO_SHA1 : false; // to set default SHA1, to disconnect.
636 MainWP_Logger::instance()->debug_for_website( $website, 'get_renew_post_data_authed', '[' . $website->url . '] :: [openssl_sign:failed] :: Set sign_algo=SHA1' );
637 $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
638 }
639
640 if ( false !== $alg ) {
641 $data['sign_algo'] = $alg;
642 }
643 }
644
645 $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
646
647 if ( null !== $sign_success && empty( $sign_success ) ) {
648 $sign_error = '';
649 while ( $msg = openssl_error_string() ) {
650 if ( is_string( $msg ) ) {
651 $sign_error .= $msg;
652 }
653 }
654 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 );
655 }
656
657 if ( $verify_signature ) {
658 $ts = time();
659 $data_sign_v2 = array(
660 'base_function' => $compat_what,
661 'nonce' => $data['nonce'],
662 'expires' => $ts + 60,
663 'user' => $website->adminname,
664 'req_id' => wp_generate_uuid4(),
665 );
666 $sign_value_v2 = wp_json_encode( $data_sign_v2 );
667
668 $sign_success_v2 = null;
669
670 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
671 // to disconnect.
672 $sign_success_v2 = MainWP_Connect_Lib::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
673 } elseif ( function_exists( 'openssl_verify' ) ) {
674 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
675 $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
676 if ( empty( $sign_success_v2 ) ) { // error from openssl, openssl_sign().
677 $alg = defined( 'OPENSSL_ALGO_SHA1' ) ? OPENSSL_ALGO_SHA1 : false; // to set default SHA1, to disconnect.
678 MainWP_Logger::instance()->debug_for_website( $website, 'get_renew_post_data_authed', '[' . $website->url . '] :: [openssl_sign:failed] :: Set sign_algo=SHA1' );
679 $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
680 }
681 if ( false !== $alg ) {
682 $data['sign_algo'] = $alg;
683 }
684 }
685
686 $data['data_signature'] = $sign_value_v2;
687 $data['mainwpsignature_adv'] = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
688
689 if ( null !== $sign_success_v2 && empty( $sign_success_v2 ) ) {
690 $sign_error = '';
691 while ( $msg = openssl_error_string() ) {
692 if ( is_string( $msg ) ) {
693 $sign_error .= $msg;
694 }
695 }
696 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT ADV SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
697 }
698 }
699
700 if ( $use_seclib ) {
701 $data['verifylib'] = 1;
702 }
703
704 return http_build_query( $data, '', '&' );
705 }
706 return null;
707 }
708
709
710 /**
711 * Method get_get_data_authed()
712 *
713 * Get authorized $_GET data & build query.
714 *
715 * @param mixed $website Child Site data.
716 * @param mixed $paramValue OpenSSL parameter.
717 * @param string $paramName Parameter name.
718 * @param bool $asArray true|false Default is false.
719 * @param array $other_params other params.
720 * @param string $custom_url Optional. Override the target base URL for browser-bound
721 * requests ( see MainWP_Site_Url_Corrector::browser_target_url() ).
722 * Default null keeps the stored URL � server-side callers
723 * ( backups, premium updates ) must not pass this.
724 *
725 * @return string $url
726 */
727 public static function get_get_data_authed( $website, $paramValue, $paramName = 'where', $asArray = false, $other_params = array(), $custom_url = null ) { //phpcs:ignore -- NOSONAR - complex method.
728 $params = array();
729 if ( $website && '' !== $paramValue ) {
730
731 $sign_success = null;
732 $alg = false;
733 $use_seclib = false;
734 $nonce = wp_rand( 0, 9999 );
735
736 /**
737 * Current user global.
738 *
739 * @global string
740 */
741 global $current_user;
742
743 $alt_user = '';
744 if ( ( ( ! defined( 'DOING_CRON' ) || false === DOING_CRON ) && ( ! defined( 'WP_CLI' ) || false === WP_CLI ) ) && $current_user && $current_user->ID ) {
745 /** This filter is documented in ../class/class-mainwp-connect.php */
746 $alt_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
747 }
748
749 $verify_signature = is_array( $other_params ) && ! empty( $other_params['verify_signature'] );
750
751 $child_support_adv_sign = 1 === (int) MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
752
753 if ( ! $verify_signature || ! $child_support_adv_sign ) {
754 $sign_value = $paramValue . $nonce; // compatible format.
755
756 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
757 $sign_success = MainWP_Connect_Lib::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
758 $use_seclib = true;
759 } elseif ( function_exists( 'openssl_verify' ) ) {
760 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
761 $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
762 }
763
764 $signature = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
765
766 if ( null !== $sign_success && empty( $sign_success ) ) {
767 $sign_error = '';
768 while ( $msg = openssl_error_string() ) {
769 if ( is_string( $msg ) ) {
770 $sign_error .= $msg;
771 }
772 }
773 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 );
774 }
775 } else {
776 $signature = base64_encode( 'useadvancedmainwpsignature' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
777 }
778
779 $params = array(
780 'login_required' => 1,
781 'user' => rawurlencode( $website->adminname ),
782 'mainwpsignature' => rawurlencode( $signature ),
783 'nonce' => $nonce,
784 $paramName => rawurlencode( $paramValue ),
785 );
786
787 if ( false !== $alg ) {
788 $params['sign_algo'] = $alg;
789 }
790
791 if ( $verify_signature ) {
792 $ts = time();
793 $data_sign_v2 = array(
794 'base_function' => $paramValue,
795 'where' => rawurlencode( $paramName ),
796 'nonce' => $nonce,
797 'expires' => $ts + 60,
798 'user' => $website->adminname,
799 'req_id' => wp_generate_uuid4(),
800 );
801
802 if ( ! empty( $alt_user ) ) {
803 $data_sign_v2['alt_user'] = rawurlencode( $alt_user );
804 }
805
806 $sign_success_v2 = null;
807
808 $sign_value_v2 = wp_json_encode( $data_sign_v2 );
809
810 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
811 $sign_success_v2 = MainWP_Connect_Lib::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
812 $use_seclib = true;
813 } elseif ( function_exists( 'openssl_verify' ) ) {
814 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
815 $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
816 if ( false !== $alg ) {
817 $params['sign_algo'] = $alg;
818 }
819 }
820
821 $signature_v2 = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
822
823 $params['data_signature'] = rawurlencode( $sign_value_v2 );
824 $params['mainwpsignature_adv'] = rawurlencode( $signature_v2 );
825
826 if ( null !== $sign_success_v2 && empty( $sign_success_v2 ) ) {
827 $sign_error = '';
828 while ( $msg = openssl_error_string() ) {
829 if ( is_string( $msg ) ) {
830 $sign_error .= $msg;
831 }
832 }
833 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT ADV SIGN', 'FAILED :: [login_required=1] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
834 }
835 }
836
837 if ( ! empty( $alt_user ) ) {
838 $params['alt_user'] = rawurlencode( $alt_user );
839 }
840
841 if ( is_array( $other_params ) ) {
842 foreach ( $other_params as $name => $value ) {
843 if ( is_string( $name ) && ! empty( $name ) && is_scalar( $value ) ) {
844 $params[ sanitize_text_field( wp_unslash( $name ) ) ] = rawurlencode( sanitize_text_field( wp_unslash( $value ) ) );
845 }
846 }
847 }
848
849 if ( ! empty( $params['login_required'] ) && ! empty( $params['where'] ) ) {
850 $open_params = apply_filters( 'mainwp_open_site_login_required_params', false, $params, $website );
851 if ( is_array( $open_params ) && ! empty( $open_params ) ) {
852 $where_params = '';
853 foreach ( $open_params as $key => $value ) {
854 $where_params .= rawurlencode( sanitize_text_field( wp_unslash( $key ) ) ) . '=' . rawurlencode( sanitize_text_field( wp_unslash( $value ) ) ) . '&';
855 }
856 if ( ! empty( $where_params ) ) {
857 $params['where_params'] = rawurlencode( rtrim( $where_params, '&' ) );
858 }
859 }
860 }
861
862 if ( ! empty( $use_seclib ) ) {
863 $params['verifylib'] = 1;
864 }
865 }
866
867 if ( $asArray ) {
868 return $params;
869 }
870
871 if ( null !== $custom_url && '' !== $custom_url ) {
872 $url = $custom_url;
873 } else {
874 $url = ( isset( $website->url ) && '' !== $website->url ? $website->url : $website->siteurl );
875 }
876 $url .= ( substr( $url, - 1 ) !== '/' ? '/' : '' );
877 $url .= '?';
878
879 foreach ( $params as $key => $value ) {
880 $url .= $key . '=' . $value . '&';
881 }
882 return rtrim( $url, '&' );
883 }
884
885 /**
886 * Method connect_sign()
887 *
888 * Sign connect.
889 *
890 * @param string $data Data sign.
891 * @param string $signature signature.
892 * @param string $privkey Private key.
893 * @param mixed $algorithm signature algorithm.
894 * @param int $site_id site id.
895 *
896 * @return bool Success or not.
897 */
898 public static function connect_sign( $data, &$signature, $privkey, $algorithm, $site_id ) {
899 $de_privkey = MainWP_Encrypt_Data_Lib::instance()->decrypt_privkey( $privkey, $site_id );
900
901 if ( empty( $de_privkey ) ) {
902 MainWP_Logger::instance()->debug( 'Error: Failed to decrypt the priv key.' );
903 }
904
905 if ( empty( $de_privkey ) ) {
906 $de_privkey = $privkey; // compatible.
907 }
908 if ( false === $algorithm ) {
909 return openssl_sign( $data, $signature, $de_privkey ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
910 } else {
911 return openssl_sign( $data, $signature, $de_privkey, $algorithm ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
912 }
913 }
914
915 /**
916 * Method get_post_data_not_authed()
917 *
918 * Get not authorized $_POST data.
919 *
920 * @param mixed $url Child site URL.
921 * @param mixed $admin Admin Username.
922 * @param mixed $what What function to perform.
923 * @param null $params Function parameters.
924 *
925 * @return mixed null|http_build_query()
926 */
927 public static function get_post_data_not_authed( $url, $admin, $what, $params = null ) {
928 if ( '' !== $url && '' !== $admin && '' !== $what ) {
929 $data = array();
930 $data['user'] = $admin;
931 $data['function'] = $what;
932 $data['mainwpver'] = MainWP_System::$version;
933
934 if ( null !== $params ) {
935 $data = array_merge( $data, $params );
936 }
937
938 return http_build_query( $data, '', '&' );
939 }
940
941 return null;
942 }
943
944 /**
945 * Format a privacy-safe diagnostic for an unexpected Child response.
946 *
947 * @param mixed $website Website information.
948 * @param mixed $data Raw response data.
949 *
950 * @return string Redacted diagnostic message.
951 */
952 private static function format_unexpected_response_log( $website, $data ) {
953 $site_id = 0;
954 if ( is_object( $website ) && property_exists( $website, 'id' ) && 0 < (int) $website->id ) {
955 $site_id = (int) $website->id;
956 }
957
958 $response_bytes = is_string( $data ) ? strlen( $data ) : 0;
959
960 return 'curl_multi_getcontent :: unexpected response :: [siteid=' . $site_id . '] :: [response_bytes=' . $response_bytes . ']';
961 }
962
963 /**
964 * Method fetch_urls_authed()
965 *
966 * Fetches data from child sites if authenticated.
967 *
968 * @param object $websites Websites information.
969 * @param string $what Action to perform.
970 * @param array $params Request parameters.
971 * @param mixed $handler Request handler.
972 * @param mixed $output Request output.
973 * @param mixed $whatPage Request URL. Default /admin-ajax.php.
974 * @param array $others Request additional information.
975 *
976 * @return bool true|false
977 *
978 * @uses \MainWP\Dashboard\MainWP_System::$version
979 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_mainwp_dir()
980 */
981 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.
982
983 if ( ! is_array( $websites ) || empty( $websites ) ) {
984 return false;
985 }
986
987 if ( ! is_array( $params ) ) {
988 $params = array();
989 }
990
991 $sleep_int = (int) get_option( 'mainwp_chunksleepinterval', 5 );
992 $chunkSize = (int) get_option( 'mainwp_chunksitesnumber', 10 );
993
994 $chunkSize = apply_filters( 'mainwp_fetch_urls_chunk_size', $chunkSize );
995 if ( count( $websites ) > $chunkSize ) {
996 $total = count( $websites );
997 $loops = ceil( $total / $chunkSize );
998 for ( $i = 0; $i < $loops; $i++ ) {
999 $newSites = array_slice( $websites, $i * $chunkSize, $chunkSize, true );
1000 static::fetch_urls_authed( $newSites, $what, $params, $handler, $output, $whatPage, $others );
1001 sleep( $sleep_int );
1002 }
1003
1004 return false;
1005 }
1006
1007 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1008 $mh = curl_multi_init();
1009
1010 /**
1011 * Filter: mainwp_fetch_url_site_timeout
1012 *
1013 * Filters the request timeout ( CURLOPT_TIMEOUT + PHP time limit ) used
1014 * for child site requests. Defaults to 20 hours to accommodate the
1015 * longest operations ( upgrades, backups ); short-lived callers such as
1016 * the URL-correction verify probe bound it much tighter.
1017 *
1018 * @param int $timeout Timeout in seconds. Default 72000 ( 20 hours ).
1019 * Values below 1 are ignored ( 0 would disable the
1020 * cURL timeout entirely ) and fall back to the default.
1021 *
1022 * @since 6.2
1023 */
1024 $timeout = (int) apply_filters( 'mainwp_fetch_url_site_timeout', 20 * 60 * 60 );
1025 if ( $timeout <= 0 ) {
1026 $timeout = 20 * 60 * 60;
1027 }
1028
1029 $disabled_functions = ini_get( 'disable_functions' );
1030 $handleToWebsite = array();
1031 $requestUrls = array();
1032 $requestHandles = array();
1033
1034 $dirs = MainWP_System_Utility::get_mainwp_dir();
1035 $cookieDir = $dirs[0] . 'cookies';
1036
1037 static::init_cookiesdir( $cookieDir );
1038
1039 $_org_params = null;
1040
1041 foreach ( $websites as $website ) {
1042
1043 if ( MainWP_Demo_Handle::get_instance()->is_demo_website( $website ) ) {
1044 MainWP_Demo_Handle::get_instance()->handle_fetch_urls_demo( $data, $website, $output, $what, $params );
1045 continue;
1046 }
1047
1048 $url = $website->url;
1049 if ( '/' !== substr( $url, - 1 ) ) {
1050 $url .= '/';
1051 }
1052
1053 if ( false === strpos( $url, 'wp-admin' ) ) {
1054 $url .= 'wp-admin/';
1055 }
1056
1057 if ( null !== $whatPage ) {
1058 $url .= $whatPage;
1059 } else {
1060 $url .= 'admin-ajax.php';
1061 }
1062
1063 $http_user = null;
1064 $http_pass = null;
1065
1066 if ( property_exists( $website, 'http_user' ) ) {
1067 $http_user = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
1068 }
1069 if ( property_exists( $website, 'http_pass' ) ) {
1070 $http_pass = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
1071 }
1072
1073 if ( isset( $params ) && isset( $params['new_post'] ) ) {
1074
1075 if ( null === $_org_params ) {
1076 $_org_params = $params;
1077 }
1078
1079 /**
1080 * Filter is being replaced with mainwp_pre_posting_posts.
1081 *
1082 * @deprecated
1083 */
1084 $params = apply_filters_deprecated(
1085 'mainwp-pre-posting-posts',
1086 array(
1087 ( is_array( $params ) ? $params : array() ),
1088 (object) array(
1089 'id' => $website->id,
1090 'url' => $website->url,
1091 'name' => $website->name,
1092 ),
1093 ),
1094 '4.0.7.2', // NOSONAR - not IP.
1095 'mainwp_pre_posting_posts'
1096 );
1097
1098 /**
1099 * Filter: mainwp_pre_posting_posts
1100 *
1101 * Prepares parameters for the authenticated cURL post.
1102 *
1103 * @since 4.1
1104 */
1105 $params = apply_filters(
1106 'mainwp_pre_posting_posts',
1107 ( is_array( $params ) ? $params : array() ),
1108 (object) array(
1109 'id' => $website->id,
1110 'url' => $website->url,
1111 'name' => $website->name,
1112 )
1113 );
1114 }
1115
1116 $ch = curl_init();
1117
1118 $proxy = new \WP_HTTP_Proxy();
1119 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1120 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1121 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1122 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
1123
1124 if ( $proxy->use_authentication() ) {
1125 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1126 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1127 }
1128 }
1129
1130 if ( ( null !== $website ) && ( ( property_exists( $website, 'wpe' ) && 1 !== $website->wpe ) || ( isset( $others['upgrade'] ) && ( true === $others['upgrade'] ) ) ) ) {
1131 // to fix.
1132 if ( defined( 'LOGGED_IN_SALT' ) && defined( 'NONCE_SALT' ) ) {
1133 $cookie_salt = sha1( sha1( 'mainwp' . LOGGED_IN_SALT . $website->id ) . NONCE_SALT . 'WP_Cookie' ); // NOSONAR - safe for salt file name.
1134 } else {
1135 // MWP-1558: misconfigured WP installs (no salts) previously used unsalted SHA1, which is enumerable. Fall back to the per-install MainWP filename secret instead.
1136 $cookie_salt = MainWP_System_Utility::get_private_filename( 'cookies', $website->id, 'WP_Cookie' );
1137 }
1138 $cookieFile = $cookieDir . '/' . $cookie_salt;
1139 if ( ! file_exists( $cookieFile ) ) {
1140 @file_put_contents( $cookieFile, '' );
1141 }
1142
1143 if ( file_exists( $cookieFile ) ) {
1144 @chmod( $cookieFile, 0644 ); // NOSONAR - correct file permissions, owner: rwe, group & others: r.
1145 curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookieFile );
1146 curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookieFile );
1147 }
1148 }
1149
1150 curl_setopt( $ch, CURLOPT_URL, $url );
1151 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1152 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
1153 curl_setopt( $ch, CURLOPT_POST, true );
1154
1155 $postdata = static::get_post_data_authed( $website, $what, $params );
1156 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
1157 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
1158 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1159 curl_setopt( $ch, CURLOPT_REFERER, get_option( 'siteurl' ) );
1160 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1161 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
1162 $http_pass = stripslashes( $http_pass );
1163 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
1164 }
1165
1166 $ssl_verifyhost = false;
1167 $verifyCertificate = isset( $website->verify_certificate ) ? (int) $website->verify_certificate : null;
1168 if ( null !== $verifyCertificate ) {
1169 if ( 1 === $verifyCertificate ) {
1170 $ssl_verifyhost = true;
1171 } elseif ( 2 === $verifyCertificate ) {
1172 if ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
1173 $ssl_verifyhost = true;
1174 }
1175 }
1176 } elseif ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
1177 $ssl_verifyhost = true;
1178 }
1179
1180 if ( $ssl_verifyhost ) {
1181 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
1182 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
1183 } else {
1184 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR.
1185 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR.
1186 }
1187
1188 curl_setopt( $ch, CURLOPT_SSLVERSION, $website->ssl_version );
1189
1190 if ( is_object( $website ) && property_exists( $website, 'id' ) ) {
1191 $http_version = apply_filters( 'mainwp_curl_http_version', false, $website->id );
1192 if ( false !== $http_version ) {
1193 curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
1194 }
1195
1196 $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, $website->id, $website->url );
1197 if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
1198 curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
1199 curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
1200 }
1201 }
1202
1203 curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1204 MainWP_System_Utility::set_time_limit( $timeout );
1205
1206 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
1207 @curl_multi_add_handle( $mh, $ch );
1208 }
1209
1210 $handleToWebsite[ static::get_resource_id( $ch ) ] = $website;
1211 $requestUrls[ static::get_resource_id( $ch ) ] = $website->url;
1212 $requestHandles[ static::get_resource_id( $ch ) ] = $ch;
1213
1214 if ( null !== $_org_params ) {
1215 $params = $_org_params;
1216 }
1217 }
1218
1219 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
1220 $lastRun = 0;
1221 $retry_added = false;
1222 do {
1223 $retry_added = false;
1224 if ( 20 < time() - $lastRun ) {
1225 MainWP_System_Utility::set_time_limit( $timeout );
1226 $lastRun = time();
1227 }
1228
1229 curl_multi_exec( $mh, $running );
1230 curl_multi_select( $mh );
1231 while ( $info = curl_multi_info_read( $mh ) ) {
1232 $data = curl_multi_getcontent( $info['handle'] );
1233 $contains = ( 0 < preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) );
1234 curl_multi_remove_handle( $mh, $info['handle'] );
1235
1236 $rid = static::get_resource_id( $info['handle'] );
1237 if ( ! $contains && isset( $requestUrls[ $rid ] ) ) {
1238 curl_setopt( $info['handle'], CURLOPT_URL, $requestUrls[ $rid ] );
1239 curl_setopt( $info['handle'], CURLOPT_FRESH_CONNECT, true );
1240 curl_setopt( $info['handle'], CURLOPT_FORBID_REUSE, true );
1241 $add_retry = curl_multi_add_handle( $mh, $info['handle'] );
1242 if ( CURLM_OK === $add_retry ) {
1243 $mrc = curl_multi_exec( $mh, $running );
1244
1245 if ( CURLM_OK === $mrc ) {
1246 $retry_added = true;
1247 unset( $requestUrls[ $rid ] );
1248 continue; // libcurl updates $running automatically.
1249 }
1250
1251 curl_multi_remove_handle( $mh, $info['handle'] );
1252 }
1253 }
1254
1255 if ( ! $contains ) {
1256 $log_website = isset( $handleToWebsite[ $rid ] ) ? $handleToWebsite[ $rid ] : null;
1257 MainWP_Logger::instance()->debug( static::format_unexpected_response_log( $log_website, $data ) );
1258 }
1259
1260 if ( null !== $handler ) {
1261 $site = &$handleToWebsite[ static::get_resource_id( $info['handle'] ) ];
1262 call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
1263 }
1264
1265 unset( $handleToWebsite[ static::get_resource_id( $info['handle'] ) ] );
1266 if ( static::is_valid_curl_handle( $info['handle'] ) ) {
1267 curl_close( $info['handle'] );
1268 }
1269 unset( $info['handle'] );
1270 }
1271 usleep( 10000 );
1272 } while ( $running > 0 || $retry_added );
1273
1274 if ( static::is_valid_curl_handle( $mh ) ) {
1275 curl_multi_close( $mh );
1276 }
1277 } else {
1278 foreach ( $requestHandles as $ch ) {
1279 $data = curl_exec( $ch );
1280
1281 if ( null !== $handler ) {
1282 $site = &$handleToWebsite[ static::get_resource_id( $ch ) ];
1283 call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
1284 }
1285 }
1286 }
1287
1288 return true;
1289 }
1290
1291 /**
1292 * Credits WordPress org.
1293 *
1294 * Get the correct "Expect" header for the given request data.
1295 *
1296 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
1297 * @return string The "Expect" header.
1298 */
1299 public static function get_expect_header( $data ) {
1300 if ( ! is_array( $data ) ) {
1301 return strlen( (string) $data ) >= 1048576 ? '100-Continue' : '';
1302 }
1303
1304 $bytesize = 0;
1305 $iterator = new \RecursiveIteratorIterator( new \RecursiveArrayIterator( $data ) );
1306
1307 foreach ( $iterator as $datum ) {
1308 $bytesize += strlen( (string) $datum );
1309
1310 if ( $bytesize >= 1048576 ) {
1311 return '100-Continue';
1312 }
1313 }
1314
1315 return '';
1316 }
1317
1318 /**
1319 * Method get_resource_id()
1320 *
1321 * Get resource id.
1322 *
1323 * @param mixed $res The given resource.
1324 *
1325 * @return $result Resource ID only.
1326 */
1327 public static function get_resource_id( $res ) {
1328 $result = false;
1329 if ( is_a( $res, 'CurlHandle' ) ) {
1330 $result = spl_object_hash( $res );
1331 } elseif ( is_resource( $res ) ) {
1332 $resourceString = (string) $res;
1333 $exploded = explode( '#', $resourceString );
1334 $result = array_pop( $exploded );
1335 }
1336 return $result;
1337 }
1338
1339 /**
1340 * Method get_lock_identifier().
1341 *
1342 * Get lock identifier.
1343 *
1344 * @param mixed $pLockName Provided Lock Name.
1345 *
1346 * @return mixed false|sem_get()|@fopen
1347 */
1348 public static function get_lock_identifier( $pLockName ) {
1349 if ( ( null === $pLockName ) || ( false === $pLockName ) ) {
1350 return false;
1351 }
1352
1353 if ( function_exists( 'sem_get' ) ) {
1354 return sem_get( $pLockName );
1355 } else {
1356 $fh = @fopen( sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'lock' . $pLockName . '.txt', 'w+' );
1357 if ( ! $fh ) {
1358 return false;
1359 }
1360
1361 return $fh;
1362 }
1363 }
1364
1365 /**
1366 * Method lock()
1367 *
1368 * Use sem_acquire or @flock to lock the $identifier.
1369 *
1370 * @param mixed $identifier Identifier.
1371 *
1372 * @return mixed false|sem_acquire()|@flock
1373 */
1374 public static function lock( $identifier ) {
1375 if ( ( null === $identifier ) || ( false === $identifier ) ) {
1376 return false;
1377 }
1378
1379 if ( function_exists( 'sem_acquire' ) ) {
1380 return sem_acquire( $identifier );
1381 } else {
1382 if ( ! is_resource( $identifier ) ) {
1383 return false; // to fix.
1384 }
1385 for ( $i = 0; $i < 3; $i++ ) {
1386 if ( @flock( $identifier, LOCK_EX ) ) {
1387 return $identifier;
1388 } else {
1389 sleep( 1 );
1390 }
1391 }
1392 return false;
1393 }
1394 }
1395
1396 /**
1397 * Method release()
1398 *
1399 * Use sem_release or @flock, @fclose to unlock $identifier.
1400 *
1401 * @param mixed $identifier Identifier.
1402 *
1403 * @return mixed false|sem_release()|@flock
1404 */
1405 public static function release( $identifier ) {
1406 if ( ( null === $identifier ) || ( false === $identifier ) ) {
1407 return false;
1408 }
1409
1410 if ( function_exists( 'sem_release' ) ) {
1411 return sem_release( $identifier );
1412 } else {
1413 if ( ! is_resource( $identifier ) ) {
1414 return false; // to fix.
1415 }
1416 @flock( $identifier, LOCK_UN );
1417 @fclose( $identifier );
1418 }
1419
1420 return false;
1421 }
1422
1423 /**
1424 * Method fetch_url_authed()
1425 *
1426 * Updates the child site via authenticated request.
1427 *
1428 * @param object $website Website information.
1429 * @param string $what Function to perform.
1430 * @param null $params Function parameters.
1431 * @param bool $checkConstraints Whether or not to check constraints.
1432 * @param bool $pForceFetch Whether or not to force the fetch.
1433 * @param bool $pRetryFailed Whether or not to retry the fetch process.
1434 * @param null $rawResponse Raw response.
1435 * @param bool $verify_signature Wether verify signature data.
1436 *
1437 * @return mixed $information
1438 *
1439 * @uses \MainWP\Dashboard\MainWP_Monitoring_Handler::handle_check_website()
1440 * @uses \MainWP\Dashboard\MainWP_Premium_Update::maybe_request_premium_updates()
1441 * @uses \MainWP\Dashboard\MainWP_Sync::sync_information_array()
1442 */
1443 public static function fetch_url_authed( // phpcs:ignore -- NOSONAR - complex.
1444 &$website,
1445 $what,
1446 $params = null,
1447 $checkConstraints = false,
1448 $pForceFetch = false,
1449 $pRetryFailed = true,
1450 $rawResponse = null,
1451 $verify_signature = false
1452 ) {
1453 unset( $pForceFetch );
1454
1455 // to support demo data.
1456 if ( MainWP_Demo_Handle::get_instance()->is_demo_website( $website ) ) {
1457 return MainWP_Demo_Handle::get_instance()->handle_action_demo( $website, $what );
1458 }
1459
1460 /**
1461 * Filter to mock fetch_url_authed response before any HTTP/signing occurs.
1462 *
1463 * This filter fires early, before OpenSSL signing or HTTP requests, allowing
1464 * tests to bypass child site communication entirely.
1465 *
1466 * SECURITY WARNING - TEST ONLY:
1467 * This filter ONLY fires when ALL of the following conditions are met:
1468 * 1. MAINWP_TESTING_MODE constant is defined and true
1469 * 2. A PHPUnit test harness constant is present (WP_TESTS_DOMAIN, PHPUNIT_COMPOSER_INSTALL, or WP_TESTS_DIR)
1470 *
1471 * This triple-check prevents malicious code from defining MAINWP_TESTING_MODE
1472 * in production to spoof child site responses.
1473 *
1474 * IMPORTANT: MAINWP_TESTING_MODE must ONLY be defined in the PHPUnit bootstrap
1475 * file (tests/bootstrap.php). Defining it in production code, wp-config.php, or
1476 * plugin files would create a security vulnerability allowing response spoofing.
1477 *
1478 * @since 5.4
1479 *
1480 * @param mixed $pre_result Return non-false to short-circuit and return this value.
1481 * @param object $website Website object being communicated with.
1482 * @param string $what Action being performed (e.g., 'plugin_action').
1483 * @param array $params Request parameters.
1484 * @return mixed Array to return early, false to proceed normally.
1485 */
1486 $is_phpunit_env = defined( 'WP_TESTS_DOMAIN' ) || defined( 'PHPUNIT_COMPOSER_INSTALL' ) || ( defined( 'WP_TESTS_DIR' ) && WP_TESTS_DIR );
1487 if ( defined( 'MAINWP_TESTING_MODE' ) && MAINWP_TESTING_MODE && $is_phpunit_env ) {
1488 $pre_result = apply_filters( 'mainwp_fetch_url_authed_pre', false, $website, $what, $params, $verify_signature );
1489 if ( false !== $pre_result ) {
1490 return $pre_result;
1491 }
1492 }
1493
1494 if ( ! is_array( $params ) ) {
1495 $params = array();
1496 }
1497
1498 $others = array(
1499 'force_use_ipv4' => $website->force_use_ipv4,
1500 'upgrade' => ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ),
1501 );
1502
1503 if ( isset( $rawResponse ) && $rawResponse ) {
1504 $others['raw_response'] = 'yes';
1505 }
1506
1507 $params['optimize'] = ( ( 1 === (int) get_option( 'mainwp_optimize', 1 ) ) ? 1 : 0 );
1508
1509 $updating_website = false;
1510 $type = '';
1511 $list = '';
1512
1513 $premium_update = 'process_premium_updates' === $what && ! empty( $params['premium_perform'] ) && 'premium_update' === $params['premium_perform'] ? true : false;
1514
1515 if ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what || $premium_update ) {
1516 $updating_website = true;
1517 if ( $premium_update ) {
1518 $type = $params['premium_type'];
1519 $list = $params['list'];
1520 } elseif ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1521 $type = $params['type'];
1522 $list = $params['list'];
1523 } else {
1524 $type = 'wp';
1525 $list = '';
1526 }
1527 }
1528
1529 if ( $updating_website ) {
1530 /**
1531 * Action: mainwp_website_before_updated
1532 *
1533 * Fires before the child site update process.
1534 *
1535 * @param object $website Object containing child site info.
1536 * @param string $type Type parameter.
1537 * @param string $list List parameter.
1538 *
1539 * @since Unknown
1540 */
1541 do_action( 'mainwp_website_before_updated', $website, $type, $list );
1542 }
1543
1544 $information = array();
1545 $output = array();
1546
1547 if ( 'stats' === $what || ( 'upgradeplugintheme' === $what && isset( $params['type'] ) ) ) {
1548 $request_update = MainWP_Premium_Update::maybe_request_premium_updates( $website, $what, $params, $output_result );
1549 if ( $request_update ) {
1550 // Return the information here.
1551 return $output_result;
1552 }
1553 }
1554
1555 // MWP-1548: decrypt http_user / http_pass before they hit the
1556 // outbound HTTP Basic Auth header. Legacy plaintext rows pass
1557 // through unchanged.
1558 $http_user_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
1559 $http_pass_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
1560
1561 if ( 'renew' === $what ) {
1562 $postdata = static::get_renew_post_data_authed(
1563 $website,
1564 $what,
1565 array(
1566 'verify_signature' => $verify_signature,
1567 )
1568 );
1569 } else {
1570 $postdata = static::get_post_data_authed(
1571 $website,
1572 $what,
1573 $params,
1574 array(
1575 'verify_signature' => $verify_signature,
1576 'http_user_plain' => $http_user_plain,
1577 'http_pass_plain' => $http_pass_plain,
1578 )
1579 );
1580
1581 }
1582
1583 $others['function'] = $what;
1584
1585 $information = static::fetch_url( $website, $website->url, $postdata, $checkConstraints, $website->verify_certificate, $pRetryFailed, $http_user_plain, $http_pass_plain, $website->ssl_version, $others, $output );
1586
1587 if ( ! empty( $output ) ) {
1588 if ( ! is_array( $information ) ) {
1589 $information = array();
1590 }
1591 $information['fetch_url_output'] = $output;
1592 }
1593 /**
1594 * Fires immediately after fetch url action.
1595 *
1596 * @param object $website website.
1597 * @param array $information information result data.
1598 * @param string $what action.
1599 * @param array $params params input array.
1600 * @param array $others others input array.
1601 *
1602 * @since 4.5.1.1
1603 */
1604 do_action( 'mainwp_fetch_url_authed', $website, $information, $what, $params, $others );
1605
1606 if ( is_array( $information ) && isset( $information['sync'] ) && ! empty( $information['sync'] ) ) {
1607 MainWP_Sync::sync_information_array( $website, $information['sync'] );
1608 unset( $information['sync'] );
1609 }
1610
1611 if ( $updating_website ) {
1612 /**
1613 * Action: mainwp_website_updated
1614 *
1615 * Fires after the child site update process.
1616 *
1617 * @param object $website Object containing child site info.
1618 * @param string $type Type parameter.
1619 * @param string $list List parameter.
1620 * @param array $information Array containing the information fetched from the child site.
1621 *
1622 * @since Unknown
1623 */
1624 do_action( 'mainwp_website_updated', $website, $type, $list, $information );
1625 if ( 1 === (int) get_option( 'mainwp_check_http_response', 0 ) ) {
1626 MainWP_Monitoring_Handler::handle_check_website( $website, true );
1627 }
1628 }
1629
1630 return $information;
1631 }
1632
1633 /**
1634 * Method fetch_url_not_authed()
1635 *
1636 * Fetch not authorized URL.
1637 *
1638 * @param string $url URL to fetch from.
1639 * @param string $admin Admin name.
1640 * @param string $what Function to perform.
1641 * @param null $params Function parameters.
1642 * @param bool $pForceFetch true|false Whether or not to force the fetch.
1643 * @param null $verifyCertificate Verify the SSL Certificate.
1644 * @param null $http_user htaccess username.
1645 * @param null $http_pass htaccess password.
1646 * @param integer $sslVersion SSL version to check for.
1647 * @param array $others Other functions to perform.
1648 * @param array $output Output values.
1649 *
1650 * @return mixed static::fetch_url() Fetch URL.
1651 */
1652 public static function fetch_url_not_authed( // phpcs:ignore -- NOSONAR - compatible.
1653 $url,
1654 $admin,
1655 $what,
1656 $params = null,
1657 $pForceFetch = false,
1658 $verifyCertificate = null,
1659 $http_user = null,
1660 $http_pass = null,
1661 $sslVersion = 0,
1662 $others = array(),
1663 &$output = array()
1664 ) {
1665 unset( $pForceFetch );
1666
1667 if ( empty( $params ) ) {
1668 $params = array();
1669 }
1670
1671 $postdata = static::get_post_data_not_authed( $url, $admin, $what, $params );
1672 $website = null;
1673
1674 $others['function'] = $what;
1675 return static::fetch_url( $website, $url, $postdata, false, $verifyCertificate, true, $http_user, $http_pass, $sslVersion, $others, $output );
1676 }
1677
1678 /**
1679 * Method fetch_url()
1680 *
1681 * Fetch URL.
1682 *
1683 * @param object $website Child Site info.
1684 * @param string $url URL to fetch from.
1685 * @param mixed $postdata Post data to fetch.
1686 * @param bool $checkConstraints true|false Whether or not to check constraints.
1687 * @param null $verifyCertificate Verify SSL Certificate.
1688 * @param bool $pRetryFailed ture|false Whether or not the Retry has failed.
1689 * @param null $http_user htaccess username.
1690 * @param null $http_pass htaccess password.
1691 * @param integer $sslVersion SSL version.
1692 * @param array $others Other functions to perform.
1693 * @param array $output Output values.
1694 *
1695 * @throws \Exception Exception message.
1696 *
1697 * @return mixed static::fetch_url_site()
1698 */
1699 public static function fetch_url( // phpcs:ignore -- NOSONAR - complex.
1700 &$website,
1701 $url,
1702 $postdata,
1703 $checkConstraints = false,
1704 $verifyCertificate = null,
1705 $pRetryFailed = true,
1706 $http_user = null,
1707 $http_pass = null,
1708 $sslVersion = 0,
1709 $others = array(),
1710 &$output = array()
1711 ) {
1712
1713 $start = time();
1714
1715 try {
1716 $tmpUrl = $url;
1717 if ( '/' !== substr( $tmpUrl, - 1 ) ) {
1718 $tmpUrl .= '/';
1719 }
1720
1721 if ( false === strpos( $url, 'wp-admin' ) ) {
1722 $tmpUrl .= 'wp-admin/admin-ajax.php';
1723 }
1724
1725 return static::fetch_url_site( $website, $tmpUrl, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1726 } catch ( \Exception $e ) {
1727 if ( ! $pRetryFailed || ( 30 < ( time() - $start ) ) ) {
1728 throw $e;
1729 }
1730
1731 try {
1732 return static::fetch_url_site( $website, $url, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1733 } catch ( \Exception $ex ) {
1734 throw $e;
1735 }
1736 }
1737 }
1738
1739 /**
1740 * Method fetch_url_site()
1741 *
1742 * M Fetch URL.
1743 *
1744 * @param object $website Child Site info.
1745 * @param string $url URL to fetch from.
1746 * @param mixed $postdata Post data to fetch.
1747 * @param bool $checkConstraints true|false Whether or not to check constraints.
1748 * @param null $verifyCertificate Verify SSL Certificate.
1749 * @param null $http_user htaccess username.
1750 * @param null $http_pass htaccess password.
1751 * @param integer $sslVersion SSL version.
1752 * @param array $others Other functions to perform.
1753 * @param array $output Output values.
1754 *
1755 * @return mixed $data, $information.
1756 * @throws MainWP_Exception Exception message.
1757 *
1758 * @uses \MainWP\Dashboard\MainWP_DB_Common::insert_or_update_request_log()
1759 * @uses \MainWP\Dashboard\MainWP_Exception
1760 * @uses \MainWP\Dashboard\MainWP_Logger::debug_for_website()
1761 * @uses \MainWP\Dashboard\MainWP_System::$version
1762 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_mainwp_dir()
1763 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_child_response()
1764 * @uses \MainWP\Dashboard\MainWP_Utility::value_to_string()
1765 * @uses \MainWP\Dashboard\MainWP_Utility::end_session()
1766 */
1767 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.
1768 &$website,
1769 $url,
1770 $postdata,
1771 $checkConstraints = false,
1772 $verifyCertificate = null,
1773 $http_user = null,
1774 $http_pass = null,
1775 $sslVersion = 0,
1776 $others = array(),
1777 &$output = array()
1778 ) {
1779
1780 /**
1781 * Enables data to be returned prior to connecting to the site.
1782 *
1783 * Dev/test override only. Gated behind the MAINWP_DEV_FILTERS_ENABLED
1784 * constant so the filter does not dispatch in production. The filter
1785 * receives plaintext HTTP Basic Auth credentials and the full $website
1786 * DB row (including privkey); enabling it in production would expose
1787 * those values to any 3rd-party plugin hooking the filter.
1788 *
1789 * To enable in a dev/test environment, add to wp-config.php:
1790 * define( 'MAINWP_DEV_FILTERS_ENABLED', true );
1791 *
1792 * @since 5.5
1793 *
1794 * @param mixed false
1795 * @param mixed $website
1796 * @param mixed $url
1797 * @param mixed $postdata
1798 * @param mixed $checkConstraints
1799 * @param mixed $verifyCertificate
1800 * @param mixed $http_user
1801 * @param mixed $http_pass
1802 * @param mixed $sslVersion
1803 * @param mixed $others
1804 * @param mixed $output
1805 */
1806 if ( defined( 'MAINWP_DEV_FILTERS_ENABLED' ) && MAINWP_DEV_FILTERS_ENABLED ) {
1807 $dev_data = apply_filters( 'mainwp_dev_return_data_before_connect_site', false, $website, $url, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1808 if ( false !== $dev_data ) {
1809 return $dev_data;
1810 }
1811 }
1812
1813 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1814
1815 if ( ! empty( $website ) ) {
1816 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Request to [' . $url . '] [' . MainWP_Utility::value_to_string( $postdata, 1 ) . ']' );
1817 }
1818
1819 $identifier = null;
1820 if ( $checkConstraints ) {
1821 static::check_constraints( $identifier, $website );
1822 }
1823
1824 if ( null !== $website ) {
1825 MainWP_DB_Common::instance()->insert_or_update_request_log( $website->id, null, microtime( true ), null );
1826 }
1827
1828 if ( null !== $identifier ) {
1829 static::release( $identifier );
1830 }
1831
1832 $dirs = MainWP_System_Utility::get_mainwp_dir();
1833 $cookieDir = $dirs[0] . 'cookies';
1834
1835 static::init_cookiesdir( $cookieDir );
1836
1837 $fetch_track_id = MainWP_Execution_Helper::execute_call_track( 'start_point', $website, $postdata );
1838
1839 $ch = curl_init();
1840
1841 $proxy = new \WP_HTTP_Proxy();
1842 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1843 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1844 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1845 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
1846
1847 if ( $proxy->use_authentication() ) {
1848 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1849 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1850 }
1851 }
1852
1853 if ( ( null !== $website ) && ( ( property_exists( $website, 'wpe' ) && 1 !== $website->wpe ) || ( isset( $others['upgrade'] ) && ( true === $others['upgrade'] ) ) ) ) {
1854 // to fix.
1855 if ( defined( 'LOGGED_IN_SALT' ) && defined( 'NONCE_SALT' ) ) {
1856 $cookie_salt = sha1( sha1( 'mainwp' . LOGGED_IN_SALT . $website->id ) . NONCE_SALT . 'WP_Cookie' ); // NOSONAR - safe for salt file name.
1857 } else {
1858 // MWP-1558: misconfigured WP installs (no salts) previously used unsalted SHA1, which is enumerable. Fall back to the per-install MainWP filename secret instead.
1859 $cookie_salt = MainWP_System_Utility::get_private_filename( 'cookies', $website->id, 'WP_Cookie' );
1860 }
1861 $cookieFile = $cookieDir . '/' . $cookie_salt;
1862 if ( ! file_exists( $cookieFile ) ) {
1863 @file_put_contents( $cookieFile, '' );
1864 }
1865
1866 if ( file_exists( $cookieFile ) ) {
1867 @chmod( $cookieFile, 0644 ); // NOSONAR - correct file permissions, owner: rwe, group & others: r.
1868 curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookieFile );
1869 curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookieFile );
1870 }
1871 }
1872
1873 curl_setopt( $ch, CURLOPT_URL, $url );
1874 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1875 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
1876 curl_setopt( $ch, CURLOPT_POST, true );
1877 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
1878 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
1879 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1880 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1881
1882 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
1883 $http_pass = stripslashes( $http_pass );
1884 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
1885 }
1886
1887 $ssl_verifyhost = false;
1888 if ( null !== $verifyCertificate ) {
1889 if ( 1 === (int) $verifyCertificate ) {
1890 $ssl_verifyhost = true;
1891 } elseif ( 2 === (int) $verifyCertificate ) {
1892 if ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
1893 $ssl_verifyhost = true;
1894 }
1895 }
1896 } elseif ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) {
1897 $ssl_verifyhost = true;
1898 }
1899
1900 if ( $ssl_verifyhost ) {
1901 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
1902 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
1903 } else {
1904 curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR.
1905 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR.
1906 }
1907
1908 curl_setopt( $ch, CURLOPT_SSLVERSION, $sslVersion );
1909
1910 $http_version = apply_filters( 'mainwp_curl_http_version', false, $website ? $website->id : false, $url );
1911 if ( false !== $http_version ) {
1912 curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
1913 }
1914
1915 $curlopt_resolve = false;
1916
1917 if ( is_object( $website ) && property_exists( $website, 'id' ) ) {
1918 $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, $website->id, $website->url );
1919 }
1920
1921 if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
1922 curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
1923 curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
1924 }
1925
1926 $headers = array( 'X-Requested-With' => 'XMLHttpRequest' );
1927 $headers['Expect'] = static::get_expect_header( $postdata );
1928
1929 $headers = apply_filters( 'mainwp_connect_http_request_headers', $headers, $website );
1930
1931 if ( class_exists( '\WpOrg\Requests\Requests' ) ) {
1932 $headers = \WpOrg\Requests\Requests::flatten( $headers );
1933 } else {
1934 $headers = \Requests::flatten( $headers );
1935 }
1936
1937 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
1938 curl_setopt( $ch, CURLOPT_REFERER, get_option( 'siteurl' ) );
1939
1940 $force_use_ipv4 = false;
1941 $forceUseIPv4 = isset( $others['force_use_ipv4'] ) ? (int) $others['force_use_ipv4'] : null;
1942 if ( null !== $forceUseIPv4 ) {
1943 if ( 1 === $forceUseIPv4 ) {
1944 $force_use_ipv4 = true;
1945 } elseif ( 2 === $forceUseIPv4 ) {
1946 if ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
1947 $force_use_ipv4 = true;
1948 }
1949 }
1950 } elseif ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
1951 $force_use_ipv4 = true;
1952 }
1953
1954 if ( $force_use_ipv4 && defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
1955 curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
1956 }
1957
1958 $what = '';
1959 if ( is_array( $others ) && isset( $others['function'] ) ) {
1960 $what = $others['function'];
1961 }
1962
1963 if ( 'deactivate' === $what ) {
1964 $timeout = 120; // 2 minutes.
1965 } else {
1966 $timeout = 20 * 60 * 60;
1967 }
1968
1969 /** This filter is documented in class/class-mainwp-connect.php */
1970 $timeout = (int) apply_filters( 'mainwp_fetch_url_site_timeout', $timeout, $what );
1971 if ( $timeout <= 0 ) {
1972 $timeout = 20 * 60 * 60; // values below 1 would disable the cURL timeout entirely.
1973 }
1974
1975 curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1976 MainWP_System_Utility::set_time_limit( $timeout );
1977
1978 MainWP_Utility::end_session();
1979
1980 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Executing handlers' );
1981
1982 $disabled_functions = ini_get( 'disable_functions' );
1983 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
1984 $mh = @curl_multi_init();
1985 @curl_multi_add_handle( $mh, $ch );
1986
1987 $lastRun = 0;
1988 $running = null;
1989
1990 do {
1991 if ( 20 < time() - $lastRun ) {
1992 MainWP_System_Utility::set_time_limit( $timeout );
1993 $lastRun = time();
1994 }
1995
1996 do {
1997 $mrc = curl_multi_exec( $mh, $running );
1998 } while ( CURLM_CALL_MULTI_PERFORM === $mrc );
1999
2000 $rc = curl_multi_select( $mh, 1.0 );
2001 if ( -1 === $rc ) {
2002 usleep( 100000 );
2003 }
2004
2005 while ( $info = @curl_multi_info_read( $mh ) ) {
2006 $data = @curl_multi_getcontent( $info['handle'] );
2007 $http_status = @curl_getinfo( $info['handle'], CURLINFO_HTTP_CODE );
2008 $err = @curl_error( $info['handle'] );
2009 $real_url = @curl_getinfo( $info['handle'], CURLINFO_EFFECTIVE_URL );
2010
2011 @curl_multi_remove_handle( $mh, $info['handle'] );
2012 curl_close( $info['handle'] );
2013 }
2014 usleep( 10000 );
2015 } while ( $running > 0 );
2016
2017 if ( static::is_valid_curl_handle( $mh ) ) {
2018 @curl_multi_close( $mh );
2019 }
2020 } else {
2021 $data = @curl_exec( $ch );
2022 $http_status = @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
2023 $err = @curl_error( $ch );
2024 $real_url = @curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
2025 curl_close( $ch );
2026 }
2027
2028 $host = wp_parse_url( $real_url, PHP_URL_HOST );
2029 $ip = gethostbyname( $host );
2030
2031 MainWP_Execution_Helper::execute_call_track( 'end_point', $website, $postdata, $fetch_track_id, 'fetch site' );
2032
2033 if ( null !== $website ) {
2034 MainWP_DB_Common::instance()->insert_or_update_request_log( $website->id, $ip, null, microtime( true ) );
2035 }
2036
2037 $raw_response = isset( $others['raw_response'] ) && 'yes' === $others['raw_response'] ? true : false;
2038
2039 $hidden_data = '[hidden response data]';
2040
2041 if ( ! apply_filters( 'mainwp_hide_raw_connection_response_data', true ) ) {
2042 $hidden_data = $data;
2043 }
2044
2045 if ( ! is_array( $output ) ) {
2046 $output = array();
2047 }
2048
2049 $output['fetch_data'] = $hidden_data;
2050
2051 $output['http_status'] = (int) $http_status;
2052
2053 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'http status: [' . $http_status . '] err: [' . $err . ']' );
2054 if ( '400' === $http_status ) {
2055 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'post data: [' . MainWP_Utility::value_to_string( $postdata, 1 ) . ']' );
2056 }
2057
2058 MainWP_Logger::instance()->log_execution_time( 'fetch_url_site :: [url=' . $url . ']' );
2059
2060 $thr_error = null;
2061
2062 if ( in_array( $what, array( 'installplugintheme', 'upgradeplugintheme', 'upgradetranslation', 'upgrade', 'stats', 'renew', 'reconnect' ), true ) ) {
2063 MainWP_Cache_Helper::invalidate_cache_group( MainWP_Cache_Helper::CGR_UPDATES );
2064 MainWP_Cache_Warm_Helper::invalidate_pages_by_site_actions( $what );
2065 }
2066
2067 if ( 'process_premium_updates' === $what ) {
2068 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'function: [process_premium_updates] response data: [' . MainWP_Utility::value_to_string( $data ) . ']' );
2069 }
2070
2071 if ( ( false === $data ) && empty( $http_status ) ) {
2072 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=0][' . $err . ']' );
2073 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
2074 $output['error_category'] = 'http_error';
2075 } elseif ( empty( $data ) && ! empty( $err ) ) {
2076 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=' . $http_status . '][' . $err . ']' );
2077 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
2078 $output['error_category'] = 'http_error';
2079 $output['error_code'] = 'http_request_failed';
2080 $output['error_message'] = $err;
2081 } elseif ( 0 < preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) ) {
2082 $output['connection_step'] = 'verify_credentials';
2083 $result = $results[1];
2084 $information = MainWP_System_Utility::get_child_response( base64_decode( $result ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
2085 unset( $output['fetch_data'] ); // hide the data.
2086 $pdt = is_string( $postdata ) ? $postdata : '';
2087 $data_log = is_array( $postdata ) ? print_r( $postdata, true ) : $pdt; //phpcs:ignore -- good.
2088 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', '[' . $url . '] postdata [' . $data_log . '] information: [OK]' ); //phpcs:ignore -- ok.
2089
2090 $error_code = is_array( $information ) && isset( $information['error_code'] ) ? sanitize_text_field( wp_unslash( $information['error_code'] ) ) : '';
2091 if ( ! empty( $error_code ) ) {
2092 $output['child_error_code'] = $error_code;
2093 }
2094
2095 if ( is_array( $information ) && $website ) {
2096 // Process to ensure compatibility with old and new Child versions.
2097 $new_sync = isset( $information['support_advanced_sign'] );
2098 $old_sync = ! $new_sync && ( isset( $information['mainwpdir'], $information['uniqueId'] ) || isset( $information['version'], $information['wpversion'], $information['wpe'] ) );
2099 if ( $new_sync || $old_sync ) {
2100 $adv_sign_support = MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
2101 if ( ! empty( $information['support_advanced_sign'] ) && 0 === (int) $adv_sign_support ) {
2102 MainWP_DB::instance()->update_website_option( $website, 'support_advanced_sign', 1 );
2103 } elseif ( empty( $information['support_advanced_sign'] ) && 1 === (int) $adv_sign_support ) {
2104 MainWP_DB::instance()->update_website_option( $website, 'support_advanced_sign', 0 );
2105 }
2106 }
2107 }
2108
2109 if ( 'process_premium_updates' === $what ) {
2110 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'function: [process_premium_updates] decoded data: [' . MainWP_Utility::value_to_string( $information ) . ']' );
2111 }
2112
2113 return $information;
2114 } elseif ( 200 === (int) $http_status && ! empty( $err ) ) {
2115 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
2116 $output['error_category'] = 'http_error';
2117 $output['error_code'] = 'http_request_failed';
2118 $output['error_message'] = $err;
2119 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', '[' . $url . '] error [' . $err . ']' ); //phpcs:ignore -- ok.
2120 } elseif ( $raw_response ) {
2121 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Response: [RAW]' );
2122 return $data;
2123 } else {
2124 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] Error: NOMAINWP [data=' . $hidden_data . ']' );
2125 $detect_wsidchk = is_string( $data ) ? strpos( $data, 'wsidchk' ) : false;
2126 if ( false !== $detect_wsidchk ) {
2127 $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.';
2128 $thr_error = new MainWP_Exception( 'ERROR:' . $err_msg, $url );
2129 } else {
2130 $thr_error = new MainWP_Exception( 'NOMAINWP', $url ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
2131 $err_msg = 'Connection Failed. Please ensure that the MainWP Child plugin is installed and activated on the child site.';
2132 }
2133 $output['error_category'] = 'child_plugin_missing';
2134 $output['error_message'] = $err_msg;
2135 }
2136
2137 if ( null !== $thr_error ) {
2138 $thr_error->set_data( $hidden_data ); // to compatible.
2139 throw $thr_error;
2140 }
2141 }
2142
2143 /**
2144 * Method check_constraints()
2145 *
2146 * Check connection delay constraints.
2147 *
2148 * @param mixed $identifier Lock identifier.
2149 * @param mixed $website Object child site.
2150 *
2151 * @uses \MainWP\Dashboard\MainWP_DB_Common::close_open_requests()
2152 * @uses \MainWP\Dashboard\MainWP_DB::get_wp_ip()
2153 * @uses \MainWP\Dashboard\MainWP_Utility::end_session()
2154 */
2155 private static function check_constraints( &$identifier, $website ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
2156 $semLock = '103218';
2157 $identifier = static::get_lock_identifier( $semLock );
2158 $minimumDelay = ( ( false === get_option( 'mainwp_minimumDelay' ) ) ? 200 : get_option( 'mainwp_minimumDelay' ) );
2159 if ( 0 < $minimumDelay ) {
2160 $minimumDelay = $minimumDelay / 1000;
2161 }
2162 $minimumIPDelay = ( ( false === get_option( 'mainwp_minimumIPDelay' ) ) ? 1000 : get_option( 'mainwp_minimumIPDelay' ) );
2163 if ( 0 < $minimumIPDelay ) {
2164 $minimumIPDelay = $minimumIPDelay / 1000;
2165 }
2166
2167 MainWP_Utility::end_session();
2168 $delay = true;
2169 while ( $delay ) {
2170 static::lock( $identifier );
2171 if ( 0 < $minimumDelay && static::check_constraints_last_request( $identifier, $minimumDelay ) ) {
2172 continue;
2173 }
2174
2175 if ( 0 < $minimumIPDelay && null !== $website ) {
2176 $ip = MainWP_DB::instance()->get_wp_ip( $website->id );
2177 if ( null !== $ip && '' !== $ip && static::check_constraints_last_request( $identifier, $minimumIPDelay, $ip ) ) {
2178 continue;
2179 }
2180 }
2181 $delay = false;
2182 }
2183
2184 $maximumRequests = ( ( false === get_option( 'mainwp_maximumRequests' ) ) ? 4 : get_option( 'mainwp_maximumRequests' ) );
2185 $maximumIPRequests = ( ( false === get_option( 'mainwp_maximumIPRequests' ) ) ? 1 : get_option( 'mainwp_maximumIPRequests' ) );
2186
2187 $first = true;
2188 $delay = true;
2189 while ( $delay ) {
2190 if ( ! $first ) {
2191 static::lock( $identifier );
2192 } else {
2193 $first = false;
2194 }
2195
2196 MainWP_DB_Common::instance()->close_open_requests();
2197
2198 if ( 0 < $maximumRequests && static::check_constraints_open_requests( $identifier, $maximumRequests ) ) {
2199 continue;
2200 }
2201
2202 if ( 0 < $maximumIPRequests && null !== $website ) {
2203 $ip = MainWP_DB::instance()->get_wp_ip( $website->id );
2204 if ( null !== $ip && '' !== $ip && static::check_constraints_open_requests( $identifier, $maximumIPRequests, $ip ) ) {
2205 continue;
2206 }
2207 }
2208 $delay = false;
2209 }
2210 }
2211
2212 /**
2213 * Method check_constraints_last_request().
2214 *
2215 * Check constraints for last requests.
2216 *
2217 * @param mixed $identifier connect identifier.
2218 * @param int $minimumDelay minimum delay.
2219 * @param string|null $ip ip address.
2220 *
2221 * @uses \MainWP\Dashboard\MainWP_DB_Common::get_last_request_timestamp()
2222 */
2223 private static function check_constraints_last_request( $identifier, $minimumDelay, $ip = null ) {
2224 $lastRequest = MainWP_DB_Common::instance()->get_last_request_timestamp( $ip );
2225 if ( $lastRequest > ( ( microtime( true ) ) - $minimumDelay ) ) {
2226 static::release( $identifier );
2227 $sleep = ( $minimumDelay - ( ( microtime( true ) ) - $lastRequest ) ) * 1000 * 1000;
2228 $sleep = max( 0, intval( $sleep ) );
2229 usleep( $sleep );
2230 return true;
2231 }
2232 return false;
2233 }
2234
2235 /**
2236 * Method check_constraints_open_requests().
2237 *
2238 * Check constraints for open requests.
2239 *
2240 * @param mixed $identifier connect identifier.
2241 * @param int $maximumRequests maximum requests.
2242 * @param string|null $ip ip address.
2243 *
2244 * @uses \MainWP\Dashboard\MainWP_DB_Common::get_nrof_open_requests()
2245 */
2246 private static function check_constraints_open_requests( $identifier, $maximumRequests, $ip = null ) {
2247 $nrOfOpenRequests = MainWP_DB_Common::instance()->get_nrof_open_requests( $ip );
2248 if ( $nrOfOpenRequests >= $maximumRequests ) {
2249 static::release( $identifier );
2250 usleep( 200000 );
2251 return true;
2252 }
2253 return false;
2254 }
2255
2256 /**
2257 * Method download_to_file()
2258 *
2259 * Download to file.
2260 *
2261 * @param mixed $url Download URL.
2262 * @param mixed $file File to download to.
2263 * @param bool $size Size of file.
2264 * @param null $http_user htaccess username.
2265 * @param null $http_pass htaccess password.
2266 *
2267 * @throws MainWP_Exception Exception message.
2268 *
2269 * @uses \MainWP\Dashboard\MainWP_Exception
2270 * @uses \MainWP\Dashboard\MainWP_System::$version
2271 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_wp_file_system()
2272 */
2273 public static function download_to_file( $url, $file, $size = false, $http_user = null, $http_pass = null ) { // phpcs:ignore -- NOSONAR - complex.
2274
2275 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
2276
2277 /**
2278 * WordPress files system object.
2279 *
2280 * @global object
2281 */
2282 global $wp_filesystem;
2283
2284 if ( $wp_filesystem->exists( $file ) && ( ( false === $size ) || ( $wp_filesystem->size( $file ) > $size ) ) ) {
2285 $wp_filesystem->delete( $file );
2286 }
2287
2288 if ( ! $wp_filesystem->exists( dirname( $file ) ) ) {
2289 $wp_filesystem->mkdir( dirname( $file ), 0750 ); // MWP-1558: tightened from 0777; downloaded files may contain backup data.
2290 }
2291
2292 if ( ! $wp_filesystem->exists( dirname( $file ) ) ) {
2293 throw new MainWP_Exception( esc_html__( 'MainWP plugin could not create directory in order to download the file.', 'mainwp' ) );
2294 }
2295
2296 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
2297 if ( ! $wp_filesystem->is_writable( @dirname( $file ) ) ) {
2298 throw new MainWP_Exception( esc_html__( 'MainWP upload directory is not writable.', 'mainwp' ) );
2299 }
2300 } elseif ( ! is_writable( @dirname( $file ) ) ) { //phpcs:ignore -- ok.
2301 throw new MainWP_Exception( esc_html__( 'MainWP upload directory is not writable.', 'mainwp' ) );
2302 }
2303
2304 $fp = fopen( $file, 'a' );
2305 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
2306 if ( false !== $size && $wp_filesystem->exists( $file ) ) {
2307 $size = $wp_filesystem->size( $file );
2308 $url .= '&foffset=' . $size;
2309 }
2310 $ch = curl_init( str_replace( ' ', '%20', $url ) );
2311
2312 $proxy = new \WP_HTTP_Proxy();
2313 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
2314 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
2315 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
2316 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
2317
2318 if ( $proxy->use_authentication() ) {
2319 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
2320 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
2321 }
2322 }
2323 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
2324 curl_setopt( $ch, CURLOPT_HEADER, false );
2325 curl_setopt( $ch, CURLOPT_FILE, $fp );
2326
2327 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
2328 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
2329
2330 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
2331 if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
2332 $http_pass = stripslashes( $http_pass );
2333 curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
2334 }
2335 curl_exec( $ch );
2336 if ( static::is_valid_curl_handle( $ch ) ) {
2337 curl_close( $ch );
2338 }
2339 fclose( $fp );
2340 }
2341
2342 /**
2343 * Method init_coockiesdir()
2344 *
2345 * Check for cookies directory and create it if it doesn't already exist,
2346 * set the file permissions and update htaccess.
2347 *
2348 * @param mixed $cookieDir Cookies directory.
2349 *
2350 * @return void
2351 *
2352 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_wp_file_system()
2353 */
2354 public static function init_cookiesdir( $cookieDir ) {
2355
2356 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
2357
2358 /**
2359 * WordPress files system object.
2360 *
2361 * @global object
2362 */
2363 global $wp_filesystem;
2364
2365 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
2366
2367 if ( ! $wp_filesystem->is_dir( $cookieDir ) ) {
2368 $wp_filesystem->mkdir( $cookieDir, 0750 ); // MWP-1558: tightened from 0777; cookies/ holds child wp-admin session cookies.
2369 }
2370
2371 if ( ! file_exists( $cookieDir . '/.htaccess' ) ) {
2372 $file_htaccess = $cookieDir . '/.htaccess';
2373 $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
2374 }
2375
2376 if ( ! file_exists( $cookieDir . '/index.php' ) ) {
2377 $file_index = $cookieDir . '/index.php';
2378 $wp_filesystem->touch( $file_index );
2379 }
2380 } else {
2381
2382 if ( ! file_exists( $cookieDir ) ) {
2383 @mkdir( $cookieDir, 0750, true ); // MWP-1558: tightened from 0777; cookies/ holds child wp-admin session cookies.
2384 }
2385
2386 if ( ! file_exists( $cookieDir . '/.htaccess' ) ) {
2387 $file_htaccess = @fopen( $cookieDir . '/.htaccess', 'w+' );
2388 @fwrite( $file_htaccess, 'deny from all' );
2389 @fclose( $file_htaccess );
2390 }
2391
2392 if ( ! file_exists( $cookieDir . '/index.php' ) ) {
2393 $file_index = @fopen( $cookieDir . '/index.php', 'w+' );
2394 @fclose( $file_index );
2395 }
2396 }
2397 }
2398
2399 /**
2400 * Method get_file_content()
2401 *
2402 * Get contents of file.
2403 *
2404 * @param mixed $url File Location.
2405 *
2406 * @return mixed false|$data
2407 *
2408 * @uses \MainWP\Dashboard\MainWP_System::$version
2409 */
2410 public static function get_file_content( $url ) {
2411 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
2412 $ch = curl_init();
2413
2414 $proxy = new \WP_HTTP_Proxy();
2415 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
2416 curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
2417 curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
2418 curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
2419
2420 if ( $proxy->use_authentication() ) {
2421 curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
2422 curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
2423 }
2424 }
2425
2426 curl_setopt( $ch, CURLOPT_HEADER, 0 );
2427 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
2428 curl_setopt( $ch, CURLOPT_URL, $url );
2429 curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
2430 curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
2431
2432 $data = @curl_exec( $ch );
2433 $httpCode = @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
2434 if ( static::is_valid_curl_handle( $ch ) ) {
2435 curl_close( $ch );
2436 }
2437 if ( 200 === (int) $httpCode ) {
2438 return $data;
2439 } else {
2440 return false;
2441 }
2442 }
2443
2444 /**
2445 * Method is_valid_curl_handle
2446 *
2447 * @param mixed $ch cURL handle to validate.
2448 * @return bool Valid curl handle.
2449 */
2450 public static function is_valid_curl_handle( $ch ) {
2451 return is_resource( $ch )
2452 || ( is_object( $ch )
2453 && class_exists( 'CurlHandle', false )
2454 && $ch instanceof \CurlHandle
2455 );
2456 }
2457
2458 /**
2459 * Method get_favico_url()
2460 *
2461 * Get Child Site favicon URL.
2462 *
2463 * @param mixed $website Child Site info.
2464 *
2465 * @return mixed $faviurl Favicon URL.
2466 *
2467 * @uses \MainWP\Dashboard\MainWP_DB::get_website_option()
2468 * @uses \MainWP\Dashboard\MainWP_System_Utility::get_icons_dir()
2469 * @uses \MainWP\Dashboard\MainWP_Utility::remove_http_prefix()
2470 */
2471 public static function get_favico_url( $website ) {
2472 $favi = MainWP_DB::instance()->get_website_option( $website, 'favi_icon', '' );
2473 $faviurl = '';
2474
2475 if ( ! empty( $favi ) ) {
2476 if ( false !== strpos( $favi, 'favi-' . intval( $website->id ) . '-' ) ) {
2477 $dirs = MainWP_System_Utility::get_icons_dir();
2478 if ( file_exists( $dirs[0] . $favi ) ) {
2479 $faviurl = $dirs[1] . $favi;
2480 } else {
2481 $faviurl = '';
2482 }
2483 } elseif ( ( 0 === strpos( $favi, '//' ) ) || ( 0 === strpos( $favi, 'http' ) ) ) {
2484 $faviurl = $favi;
2485 } else {
2486 $faviurl = $website->url . $favi;
2487 $faviurl = MainWP_Utility::remove_http_prefix( $faviurl );
2488 }
2489 }
2490
2491 if ( empty( $faviurl ) ) {
2492 $faviurl = false;
2493 }
2494
2495 return $faviurl;
2496 }
2497 }
2498