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