PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
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 +2305 -1636 5.0.3.1 → 6.2.1 View file →
@@ -8,1895 +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 ); // NOSONAR .
93 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR .
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 - $_org_params = null;
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 - foreach ( $websites as $website ) {
611 + if ( ! is_array( $others ) ) {
612 + $others = array();
613 + }
681 614
682 - if ( MainWP_Demo_Handle::get_instance()->is_demo_website( $website ) ) {
683 - MainWP_Demo_Handle::get_instance()->handle_fetch_urls_demo( $data, $website, $output, $what, $params );
684 - continue;
685 - }
615 + $verify_signature = ! empty( $others['verify_signature'] ) ? true : false;
686 616
687 - $url = $website->url;
688 - if ( '/' !== substr( $url, - 1 ) ) {
689 - $url .= '/';
690 - }
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 ) );
691 623
692 - if ( false === strpos( $url, 'wp-admin' ) ) {
693 - $url .= 'wp-admin/';
694 - }
624 + $sign_value = $compat_what . $data['nonce']; // compatible format.
695 625
696 - if ( null !== $whatPage ) {
697 - $url .= $whatPage;
698 - } else {
699 - $url .= 'admin-ajax.php';
700 - }
626 + $alg = false;
627 + $sign_success = null;
628 + $use_seclib = false;
701 629
702 - if ( property_exists( $website, 'http_user' ) ) {
703 - $http_user = $website->http_user;
704 - }
705 - if ( property_exists( $website, 'http_pass' ) ) {
706 - $http_pass = $website->http_pass;
707 - }
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 + }
708 642
709 - if ( isset( $params ) && isset( $params['new_post'] ) ) {
643 + if ( false !== $alg ) {
644 + $data['sign_algo'] = $alg;
645 + }
646 + }
710 647
711 - if ( null === $_org_params ) {
712 - $_org_params = $params;
713 - }
648 + $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
714 649
715 - /**
716 - * Filter is being replaced with mainwp_pre_posting_posts.
717 - *
718 - * @deprecated
719 - */
720 - $params = apply_filters_deprecated(
721 - 'mainwp-pre-posting-posts',
722 - array(
723 - ( is_array( $params ) ? $params : array() ),
724 - (object) array(
725 - 'id' => $website->id,
726 - 'url' => $website->url,
727 - 'name' => $website->name,
728 - ),
729 - ),
730 - '4.0.7.2',
731 - 'mainwp_pre_posting_posts'
732 - );
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 + }
733 659
734 - /**
735 - * Filter: mainwp_pre_posting_posts
736 - *
737 - * Prepares parameters for the authenticated cURL post.
738 - *
739 - * @since 4.1
740 - */
741 - $params = apply_filters(
742 - 'mainwp_pre_posting_posts',
743 - ( is_array( $params ) ? $params : array() ),
744 - (object) array(
745 - 'id' => $website->id,
746 - 'url' => $website->url,
747 - 'name' => $website->name,
748 - )
749 - );
750 - }
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 );
751 670
752 - $ch = curl_init();
671 + $sign_success_v2 = null;
753 672
754 - $proxy = new \WP_HTTP_Proxy();
755 - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
756 - curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
757 - curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
758 - curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
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 + }
759 688
760 - if ( $proxy->use_authentication() ) {
761 - curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
762 - curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
763 - }
764 - }
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.
765 691
766 - if ( ( null !== $website ) && ( ( property_exists( $website, 'wpe' ) && 1 !== $website->wpe ) || ( isset( $others['upgrade'] ) && ( true === $others['upgrade'] ) ) ) ) {
767 - // to fix.
768 - if ( defined( 'LOGGED_IN_SALT' ) && defined( 'NONCE_SALT' ) ) {
769 - $cookie_salt = sha1( sha1( 'mainwp' . LOGGED_IN_SALT . $website->id ) . NONCE_SALT . 'WP_Cookie' );
770 - } else {
771 - $cookie_salt = sha1( sha1( 'mainwp' . $website->id ) . 'WP_Cookie' );
772 - }
773 - $cookieFile = $cookieDir . '/' . $cookie_salt;
774 - if ( ! file_exists( $cookieFile ) ) {
775 - @file_put_contents( $cookieFile, '' );
776 - }
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 + }
777 702
778 - if ( file_exists( $cookieFile ) ) {
779 - @chmod( $cookieFile, 0644 );
780 - curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookieFile );
781 - curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookieFile );
782 - }
783 - }
703 + if ( $use_seclib ) {
704 + $data['verifylib'] = 1;
705 + }
784 706
785 - curl_setopt( $ch, CURLOPT_URL, $url );
786 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
787 - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
788 - curl_setopt( $ch, CURLOPT_POST, true );
707 + return http_build_query( $data, '', '&' );
708 + }
709 + return null;
710 + }
789 711
790 - $postdata = self::get_post_data_authed( $website, $what, $params );
791 - curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
792 - curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
793 - curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
794 - curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
795 - if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
796 - $http_pass = stripslashes( $http_pass );
797 - curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
798 - }
799 712
800 - $ssl_verifyhost = false;
801 - $verifyCertificate = isset( $website->verify_certificate ) ? (int) $website->verify_certificate : null;
802 - if ( null !== $verifyCertificate ) {
803 - if ( 1 === $verifyCertificate ) {
804 - $ssl_verifyhost = true;
805 - } elseif ( 2 === $verifyCertificate ) {
806 - if ( ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) ) {
807 - $ssl_verifyhost = true;
808 - }
809 - }
810 - } elseif ( ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) ) {
811 - $ssl_verifyhost = true;
812 - }
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 ) {
813 733
814 - if ( $ssl_verifyhost ) {
815 - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
816 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
817 - } else {
818 - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR .
819 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR .
820 - }
734 + $sign_success = null;
735 + $alg = false;
736 + $use_seclib = false;
737 + $nonce = bin2hex( random_bytes( 16 ) );
821 738
822 - curl_setopt( $ch, CURLOPT_SSLVERSION, $website->ssl_version );
739 + /**
740 + * Current user global.
741 + *
742 + * @global string
743 + */
744 + global $current_user;
823 745
824 - if ( is_object( $website ) && property_exists( $website, 'id' ) ) {
825 - $http_version = apply_filters( 'mainwp_curl_http_version', false, $website->id );
826 - if ( false !== $http_version ) {
827 - curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
828 - }
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 + }
829 751
830 - $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, $website->id, $website->url );
831 - if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
832 - curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
833 - curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
834 - }
835 - }
752 + $verify_signature = is_array( $other_params ) && ! empty( $other_params['verify_signature'] );
836 753
837 - curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
838 - MainWP_System_Utility::set_time_limit( $timeout );
754 + $child_support_adv_sign = 1 === (int) MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
839 755
840 - if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
841 - @curl_multi_add_handle( $mh, $ch );
842 - }
756 + if ( ! $verify_signature || ! $child_support_adv_sign ) {
757 + $sign_value = $paramValue . $nonce; // compatible format.
843 758
844 - $handleToWebsite[ self::get_resource_id( $ch ) ] = $website;
845 - $requestUrls[ self::get_resource_id( $ch ) ] = $website->url;
846 - $requestHandles[ self::get_resource_id( $ch ) ] = $ch;
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 + }
847 766
848 - if ( null !== $_org_params ) {
849 - $params = $_org_params;
850 - }
851 - }
767 + $signature = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
852 768
853 - if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
854 - $lastRun = 0;
855 - do {
856 - if ( 20 < time() - $lastRun ) {
857 - MainWP_System_Utility::set_time_limit( $timeout );
858 - $lastRun = time();
859 - }
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 + }
860 781
861 - curl_multi_exec( $mh, $running );
862 - while ( $info = curl_multi_info_read( $mh ) ) {
863 - $data = curl_multi_getcontent( $info['handle'] );
864 - $contains = ( 0 < preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) );
865 - curl_multi_remove_handle( $mh, $info['handle'] );
782 + $params = array(
783 + 'login_required' => 1,
784 + 'user' => rawurlencode( $website->adminname ),
785 + 'mainwpsignature' => rawurlencode( $signature ),
786 + 'nonce' => $nonce,
787 + $paramName => rawurlencode( $paramValue ),
788 + );
866 789
867 - if ( ! $contains && isset( $requestUrls[ self::get_resource_id( $info['handle'] ) ] ) ) {
868 - curl_setopt( $info['handle'], CURLOPT_URL, $requestUrls[ self::get_resource_id( $info['handle'] ) ] );
869 - curl_multi_add_handle( $mh, $info['handle'] );
870 - unset( $requestUrls[ self::get_resource_id( $info['handle'] ) ] );
871 - ++$running;
872 - continue;
873 - }
790 + if ( false !== $alg ) {
791 + $params['sign_algo'] = $alg;
792 + }
874 793
875 - if ( null !== $handler ) {
876 - $site = &$handleToWebsite[ self::get_resource_id( $info['handle'] ) ];
877 - call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
878 - }
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 + );
879 804
880 - unset( $handleToWebsite[ self::get_resource_id( $info['handle'] ) ] );
881 - if ( 'resource' === gettype( $info['handle'] ) ) {
882 - curl_close( $info['handle'] );
883 - }
884 - unset( $info['handle'] );
885 - }
886 - usleep( 10000 );
887 - } while ( $running > 0 );
805 + if ( ! empty( $alt_user ) ) {
806 + $data_sign_v2['alt_user'] = rawurlencode( $alt_user );
807 + }
888 808
889 - if ( 'resource' === gettype( $mh ) ) {
890 - curl_multi_close( $mh );
891 - }
892 - } else {
893 - foreach ( $requestHandles as $id => $ch ) {
894 - $data = curl_exec( $ch );
809 + $sign_success_v2 = null;
895 810
896 - if ( null !== $handler ) {
897 - $site = &$handleToWebsite[ self::get_resource_id( $ch ) ];
898 - call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
899 - }
900 - }
901 - }
811 + $sign_value_v2 = wp_json_encode( $data_sign_v2 );
902 812
903 - return true;
904 - }
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 + }
905 823
906 - /**
907 - * Credits WordPress org.
908 - *
909 - * Get the correct "Expect" header for the given request data.
910 - *
911 - * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
912 - * @return string The "Expect" header.
913 - */
914 - protected static function get_expect_header( $data ) {
915 - if ( ! is_array( $data ) ) {
916 - return strlen( (string) $data ) >= 1048576 ? '100-Continue' : '';
917 - }
824 + $signature_v2 = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
918 825
919 - $bytesize = 0;
920 - $iterator = new \RecursiveIteratorIterator( new \RecursiveArrayIterator( $data ) );
826 + $params['data_signature'] = rawurlencode( $sign_value_v2 );
827 + $params['mainwpsignature_adv'] = rawurlencode( $signature_v2 );
921 828
922 - foreach ( $iterator as $datum ) {
923 - $bytesize += strlen( (string) $datum );
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 + }
924 839
925 - if ( $bytesize >= 1048576 ) {
926 - return '100-Continue';
927 - }
928 - }
840 + if ( ! empty( $alt_user ) ) {
841 + $params['alt_user'] = rawurlencode( $alt_user );
842 + }
929 843
930 - return '';
931 - }
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 + }
932 851
933 - /**
934 - * Method get_resource_id()
935 - *
936 - * Get resource id.
937 - *
938 - * @param mixed $res The given resource.
939 - *
940 - * @return $result Resource ID only.
941 - */
942 - public static function get_resource_id( $res ) {
943 - $result = false;
944 - if ( is_a( $res, 'CurlHandle' ) ) {
945 - $result = spl_object_hash( $res );
946 - } elseif ( is_resource( $res ) ) {
947 - $resourceString = (string) $res;
948 - $exploded = explode( '#', $resourceString );
949 - $result = array_pop( $exploded );
950 - }
951 - return $result;
952 - }
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 + }
953 864
954 - /**
955 - * Method get_lock_identifier().
956 - *
957 - * Get lock identifier.
958 - *
959 - * @param mixed $pLockName Provided Lock Name.
960 - *
961 - * @return mixed false|sem_get()|@fopen
962 - */
963 - public static function get_lock_identifier( $pLockName ) {
964 - if ( ( null === $pLockName ) || ( false === $pLockName ) ) {
965 - return false;
966 - }
865 + if ( ! empty( $use_seclib ) ) {
866 + $params['verifylib'] = 1;
867 + }
868 + }
967 869
968 - if ( function_exists( 'sem_get' ) ) {
969 - return sem_get( $pLockName );
970 - } else {
971 - $fh = @fopen( sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'lock' . $pLockName . '.txt', 'w+' );
972 - if ( ! $fh ) {
973 - return false;
974 - }
870 + if ( $asArray ) {
871 + return $params;
872 + }
975 873
976 - return $fh;
977 - }
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 .= '?';
978 881
979 - return false;
980 - }
882 + foreach ( $params as $key => $value ) {
883 + $url .= $key . '=' . $value . '&';
884 + }
885 + return rtrim( $url, '&' );
886 + }
981 887
982 - /**
983 - * Method lock()
984 - *
985 - * Use sem_acquire or @flock to lock the $identifier.
986 - *
987 - * @param mixed $identifier Identifier.
988 - *
989 - * @return mixed false|sem_acquire()|@flock
990 - */
991 - public static function lock( $identifier ) {
992 - if ( ( null === $identifier ) || ( false === $identifier ) ) {
993 - return false;
994 - }
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 );
995 903
996 - if ( function_exists( 'sem_acquire' ) ) {
997 - return sem_acquire( $identifier );
998 - } else {
999 - if ( ! is_resource( $identifier ) ) {
1000 - return false; // to fix.
1001 - }
1002 - for ( $i = 0; $i < 3; $i++ ) {
1003 - if ( @flock( $identifier, LOCK_EX ) ) {
1004 - return $identifier;
1005 - } else {
1006 - sleep( 1 );
1007 - }
1008 - }
904 + if ( empty( $de_privkey ) ) {
905 + MainWP_Logger::instance()->debug( 'Error: Failed to decrypt the priv key.' );
906 + }
1009 907
1010 - return false;
1011 - }
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 + }
1012 917
1013 - return false;
1014 - }
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;
1015 936
1016 - /**
1017 - * Method release()
1018 - *
1019 - * Use sem_release or @flock, @fclose to unlock $identifier.
1020 - *
1021 - * @param mixed $identifier Identifier.
1022 - *
1023 - * @return mixed false|sem_release()|@flock
1024 - */
1025 - public static function release( $identifier ) {
1026 - if ( ( null === $identifier ) || ( false === $identifier ) ) {
1027 - return false;
1028 - }
937 + if ( null !== $params ) {
938 + $data = array_merge( $data, $params );
939 + }
1029 940
1030 - if ( function_exists( 'sem_release' ) ) {
1031 - return sem_release( $identifier );
1032 - } else {
1033 - if ( ! is_resource( $identifier ) ) {
1034 - return false; // to fix.
1035 - }
1036 - @flock( $identifier, LOCK_UN );
1037 - @fclose( $identifier );
1038 - }
941 + return http_build_query( $data, '', '&' );
942 + }
1039 943
1040 - return false;
1041 - }
944 + return null;
945 + }
1042 946
1043 - /**
1044 - * Method fetch_url_authed()
1045 - *
1046 - * Updates the child site via authenticated request.
1047 - *
1048 - * @param object $website Website information.
1049 - * @param string $what Function to perform.
1050 - * @param null $params Function parameters.
1051 - * @param bool $checkConstraints Whether or not to check constraints.
1052 - * @param bool $pForceFetch Whether or not to force the fetch.
1053 - * @param bool $pRetryFailed Whether or not to retry the fetch process.
1054 - * @param null $rawResponse Raw response.
1055 - *
1056 - * @return mixed $information
1057 - *
1058 - * @uses \MainWP\Dashboard\MainWP_Monitoring_Handler::handle_check_website()
1059 - * @uses \MainWP\Dashboard\MainWP_Premium_Update::maybe_request_premium_updates()
1060 - * @uses \MainWP\Dashboard\MainWP_Sync::sync_information_array()
1061 - */
1062 - public static function fetch_url_authed(
1063 - &$website,
1064 - $what,
1065 - $params = null,
1066 - $checkConstraints = false,
1067 - $pForceFetch = false,
1068 - $pRetryFailed = true,
1069 - $rawResponse = null
1070 - ) {
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 + }
1071 960
1072 - // to support demo data.
1073 - if ( MainWP_Demo_Handle::get_instance()->is_demo_website( $website ) ) {
1074 - return MainWP_Demo_Handle::get_instance()->handle_action_demo( $website, $what );
1075 - }
961 + $response_bytes = is_string( $data ) ? strlen( $data ) : 0;
1076 962
1077 - if ( ! is_array( $params ) ) {
1078 - $params = array();
1079 - }
963 + return 'curl_multi_getcontent :: unexpected response :: [siteid=' . $site_id . '] :: [response_bytes=' . $response_bytes . ']';
964 + }
1080 965
1081 - $others = array(
1082 - 'force_use_ipv4' => $website->force_use_ipv4,
1083 - 'upgrade' => ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ),
1084 - );
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.
1085 985
1086 - $request_update = MainWP_Premium_Update::maybe_request_premium_updates( $website, $what, $params );
986 + if ( ! is_array( $websites ) || empty( $websites ) ) {
987 + return false;
988 + }
1087 989
1088 - if ( isset( $rawResponse ) && $rawResponse ) {
1089 - $others['raw_response'] = 'yes';
1090 - }
990 + if ( ! is_array( $params ) ) {
991 + $params = array();
992 + }
1091 993
1092 - $params['optimize'] = ( ( 1 === (int) get_option( 'mainwp_optimize', 1 ) ) ? 1 : 0 );
994 + $sleep_int = (int) get_option( 'mainwp_chunksleepinterval', 5 );
995 + $chunkSize = (int) get_option( 'mainwp_chunksitesnumber', 10 );
1093 996
1094 - $updating_website = false;
1095 - $type = '';
1096 - $list = '';
1097 - if ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ) {
1098 - $updating_website = true;
1099 - if ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1100 - $type = $params['type'];
1101 - $list = $params['list'];
1102 - } else {
1103 - $type = 'wp';
1104 - $list = '';
1105 - }
1106 - }
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 + }
1107 1006
1108 - if ( $updating_website ) {
1109 - /**
1110 - * Action: mainwp_website_before_updated
1111 - *
1112 - * Fires before the child site update process.
1113 - *
1114 - * @param object $website Object containing child site info.
1115 - * @param string $type Type parameter.
1116 - * @param string $list List parameter.
1117 - *
1118 - * @since Unknown
1119 - */
1120 - do_action( 'mainwp_website_before_updated', $website, $type, $list );
1121 - }
1007 + return false;
1008 + }
1122 1009
1123 - if ( 'renew' === $what ) {
1124 - $postdata = self::get_renew_post_data_authed( $website, $what );
1125 - } else {
1126 - $postdata = self::get_post_data_authed( $website, $what, $params );
1010 + $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1011 + $mh = curl_multi_init();
1127 1012
1128 - }
1129 - $others['function'] = $what;
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 + }
1130 1031
1131 - $information = array();
1032 + $disabled_functions = ini_get( 'disable_functions' );
1033 + $handleToWebsite = array();
1034 + $requestUrls = array();
1035 + $requestHandles = array();
1132 1036
1133 - if ( ! $request_update ) {
1134 - $information = self::fetch_url( $website, $website->url, $postdata, $checkConstraints, $website->verify_certificate, $pRetryFailed, $website->http_user, $website->http_pass, $website->ssl_version, $others );
1135 - /**
1136 - * Fires immediately after fetch url action.
1137 - *
1138 - * @param object $website website.
1139 - * @param array $information information result data.
1140 - * @param string $what action.
1141 - * @param array $params params input array.
1142 - * @param array $others others input array.
1143 - *
1144 - * @since 4.5.1.1
1145 - */
1146 - do_action( 'mainwp_fetch_url_authed', $website, $information, $what, $params, $others );
1147 - } else {
1148 - $slug = $params['list'];
1149 - $information['upgrades'] = array( $slug => 1 );
1150 - }
1037 + $dirs = MainWP_System_Utility::get_mainwp_dir();
1038 + $cookieDir = $dirs[0] . 'cookies';
1151 1039
1152 - if ( is_array( $information ) && isset( $information['sync'] ) && ! empty( $information['sync'] ) ) {
1153 - MainWP_Sync::sync_information_array( $website, $information['sync'] );
1154 - unset( $information['sync'] );
1155 - }
1040 + static::init_cookiesdir( $cookieDir );
1156 1041
1157 - if ( $updating_website ) {
1158 - /**
1159 - * Action: mainwp_website_updated
1160 - *
1161 - * Fires after the child site update process.
1162 - *
1163 - * @param object $website Object containing child site info.
1164 - * @param string $type Type parameter.
1165 - * @param string $list List parameter.
1166 - * @param array $information Array containing the information fetched from the child site.
1167 - *
1168 - * @since Unknown
1169 - */
1170 - do_action( 'mainwp_website_updated', $website, $type, $list, $information );
1171 - if ( 1 === (int) get_option( 'mainwp_check_http_response', 0 ) ) {
1172 - MainWP_Monitoring_Handler::handle_check_website( $website );
1173 - }
1174 - }
1042 + $_org_params = null;
1175 1043
1176 - return $information;
1177 - }
1044 + foreach ( $websites as $website ) {
1178 1045
1179 - /**
1180 - * Method fetch_url_not_authed()
1181 - *
1182 - * Fetch not authorized URL.
1183 - *
1184 - * @param string $url URL to fetch from.
1185 - * @param string $admin Admin name.
1186 - * @param string $what Function to perform.
1187 - * @param null $params Function parameters.
1188 - * @param bool $pForceFetch true|false Whether or not to force the fetch.
1189 - * @param null $verifyCertificate Verify the SSL Certificate.
1190 - * @param null $http_user htaccess username.
1191 - * @param null $http_pass htaccess password.
1192 - * @param integer $sslVersion SSL version to check for.
1193 - * @param array $others Other functions to perform.
1194 - * @param array $output Output values.
1195 - *
1196 - * @return mixed self::fetch_url() Fetch URL.
1197 - */
1198 - public static function fetch_url_not_authed(
1199 - $url,
1200 - $admin,
1201 - $what,
1202 - $params = null,
1203 - $pForceFetch = false,
1204 - $verifyCertificate = null,
1205 - $http_user = null,
1206 - $http_pass = null,
1207 - $sslVersion = 0,
1208 - $others = array(),
1209 - &$output = array()
1210 - ) {
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 + }
1211 1050
1212 - if ( empty( $params ) ) {
1213 - $params = array();
1214 - }
1051 + $url = $website->url;
1052 + if ( '/' !== substr( $url, - 1 ) ) {
1053 + $url .= '/';
1054 + }
1215 1055
1216 - $postdata = self::get_post_data_not_authed( $url, $admin, $what, $params );
1217 - $website = null;
1056 + if ( false === strpos( $url, 'wp-admin' ) ) {
1057 + $url .= 'wp-admin/';
1058 + }
1218 1059
1219 - $others['function'] = $what;
1220 - return self::fetch_url( $website, $url, $postdata, false, $verifyCertificate, true, $http_user, $http_pass, $sslVersion, $others, $output );
1221 - }
1060 + if ( null !== $whatPage ) {
1061 + $url .= $whatPage;
1062 + } else {
1063 + $url .= 'admin-ajax.php';
1064 + }
1222 1065
1223 - /**
1224 - * Method fetch_url()
1225 - *
1226 - * Fetch URL.
1227 - *
1228 - * @param object $website Child Site info.
1229 - * @param string $url URL to fetch from.
1230 - * @param mixed $postdata Post data to fetch.
1231 - * @param bool $checkConstraints true|false Whether or not to check constraints.
1232 - * @param null $verifyCertificate Verify SSL Certificate.
1233 - * @param bool $pRetryFailed ture|false Whether or not the Retry has failed.
1234 - * @param null $http_user htaccess username.
1235 - * @param null $http_pass htaccess password.
1236 - * @param integer $sslVersion SSL version.
1237 - * @param array $others Other functions to perform.
1238 - * @param array $output Output values.
1239 - *
1240 - * @throws \Exception Exception message.
1241 - *
1242 - * @return mixed self::fetch_url_site()
1243 - */
1244 - public static function fetch_url(
1245 - &$website,
1246 - $url,
1247 - $postdata,
1248 - $checkConstraints = false,
1249 - $verifyCertificate = null,
1250 - $pRetryFailed = true,
1251 - $http_user = null,
1252 - $http_pass = null,
1253 - $sslVersion = 0,
1254 - $others = array(),
1255 - &$output = array()
1256 - ) {
1066 + $http_user = null;
1067 + $http_pass = null;
1257 1068
1258 - $start = time();
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 + }
1259 1075
1260 - try {
1261 - $tmpUrl = $url;
1262 - if ( '/' !== substr( $tmpUrl, - 1 ) ) {
1263 - $tmpUrl .= '/';
1264 - }
1076 + if ( isset( $params ) && isset( $params['new_post'] ) ) {
1265 1077
1266 - if ( false === strpos( $url, 'wp-admin' ) ) {
1267 - $tmpUrl .= 'wp-admin/admin-ajax.php';
1268 - }
1078 + if ( null === $_org_params ) {
1079 + $_org_params = $params;
1080 + }
1269 1081
1270 - return self::fetch_url_site( $website, $tmpUrl, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1271 - } catch ( \Exception $e ) {
1272 - if ( ! $pRetryFailed || ( 30 < ( time() - $start ) ) ) {
1273 - throw $e;
1274 - }
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 + );
1275 1100
1276 - try {
1277 - return self::fetch_url_site( $website, $url, $postdata, $checkConstraints, $verifyCertificate, $http_user, $http_pass, $sslVersion, $others, $output );
1278 - } catch ( \Exception $ex ) {
1279 - throw $e;
1280 - }
1281 - }
1282 - }
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 + }
1283 1118
1284 - /**
1285 - * Method fetch_url_site()
1286 - *
1287 - * M Fetch URL.
1288 - *
1289 - * @param object $website Child Site info.
1290 - * @param string $url URL to fetch from.
1291 - * @param mixed $postdata Post data to fetch.
1292 - * @param bool $checkConstraints true|false Whether or not to check constraints.
1293 - * @param null $verifyCertificate Verify SSL Certificate.
1294 - * @param null $http_user htaccess username.
1295 - * @param null $http_pass htaccess password.
1296 - * @param integer $sslVersion SSL version.
1297 - * @param array $others Other functions to perform.
1298 - * @param array $output Output values.
1299 - *
1300 - * @return mixed $data, $information.
1301 - * @throws MainWP_Exception Exception message.
1302 - *
1303 - * @uses \MainWP\Dashboard\MainWP_DB_Common::insert_or_update_request_log()
1304 - * @uses \MainWP\Dashboard\MainWP_Exception
1305 - * @uses \MainWP\Dashboard\MainWP_Logger::debug_for_website()
1306 - * @uses \MainWP\Dashboard\MainWP_System::$version
1307 - * @uses \MainWP\Dashboard\MainWP_System_Utility::get_mainwp_dir()
1308 - * @uses \MainWP\Dashboard\MainWP_System_Utility::get_child_response()
1309 - * @uses \MainWP\Dashboard\MainWP_Utility::value_to_string()
1310 - * @uses \MainWP\Dashboard\MainWP_Utility::end_session()
1311 - */
1312 - public static function fetch_url_site( // phpcs:ignore -- complex method. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1313 - &$website,
1314 - $url,
1315 - $postdata,
1316 - $checkConstraints = false,
1317 - $verifyCertificate = null,
1318 - $http_user = null,
1319 - $http_pass = null,
1320 - $sslVersion = 0,
1321 - $others = array(),
1322 - &$output = array()
1323 - ) {
1119 + $ch = curl_init();
1324 1120
1325 - $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
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() );
1326 1126
1327 - if ( ! empty( $website ) ) {
1328 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Request to [' . $url . '] [' . MainWP_Utility::value_to_string( $postdata, 1 ) . ']' );
1329 - }
1127 + if ( $proxy->use_authentication() ) {
1128 + curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1129 + curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1130 + }
1131 + }
1330 1132
1331 - $identifier = null;
1332 - if ( $checkConstraints ) {
1333 - self::check_constraints( $identifier, $website );
1334 - }
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 + }
1335 1145
1336 - if ( null !== $website ) {
1337 - MainWP_DB_Common::instance()->insert_or_update_request_log( $website->id, null, microtime( true ), null );
1338 - }
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 + }
1339 1152
1340 - if ( null !== $identifier ) {
1341 - self::release( $identifier );
1342 - }
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 );
1343 1157
1344 - $dirs = MainWP_System_Utility::get_mainwp_dir();
1345 - $cookieDir = $dirs[0] . 'cookies';
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 - self::init_cookiesdir( $cookieDir );
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 + }
1348 1182
1349 - $ch = curl_init();
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 + }
1350 1190
1351 - $proxy = new \WP_HTTP_Proxy();
1352 - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1353 - curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1354 - curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1355 - curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
1191 + curl_setopt( $ch, CURLOPT_SSLVERSION, $website->ssl_version );
1356 1192
1357 - if ( $proxy->use_authentication() ) {
1358 - curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1359 - curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1360 - }
1361 - }
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 + }
1362 1198
1363 - if ( ( null !== $website ) && ( ( property_exists( $website, 'wpe' ) && 1 !== $website->wpe ) || ( isset( $others['upgrade'] ) && ( true === $others['upgrade'] ) ) ) ) {
1364 - // to fix.
1365 - if ( defined( 'LOGGED_IN_SALT' ) && defined( 'NONCE_SALT' ) ) {
1366 - $cookie_salt = sha1( sha1( 'mainwp' . LOGGED_IN_SALT . $website->id ) . NONCE_SALT . 'WP_Cookie' );
1367 - } else {
1368 - $cookie_salt = sha1( sha1( 'mainwp' . $website->id ) . 'WP_Cookie' );
1369 - }
1370 - $cookieFile = $cookieDir . '/' . $cookie_salt;
1371 - if ( ! file_exists( $cookieFile ) ) {
1372 - @file_put_contents( $cookieFile, '' );
1373 - }
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 + }
1374 1205
1375 - if ( file_exists( $cookieFile ) ) {
1376 - @chmod( $cookieFile, 0644 );
1377 - curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookieFile );
1378 - curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookieFile );
1379 - }
1380 - }
1206 + curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1207 + MainWP_System_Utility::set_time_limit( $timeout );
1381 1208
1382 - curl_setopt( $ch, CURLOPT_URL, $url );
1383 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1384 - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
1385 - curl_setopt( $ch, CURLOPT_POST, true );
1386 - curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
1387 - curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
1388 - curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1389 - curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1209 + if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
1210 + @curl_multi_add_handle( $mh, $ch );
1211 + }
1390 1212
1391 - if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
1392 - $http_pass = stripslashes( $http_pass );
1393 - curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
1394 - }
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;
1395 1216
1396 - $ssl_verifyhost = false;
1397 - if ( null !== $verifyCertificate ) {
1398 - if ( 1 === (int) $verifyCertificate ) {
1399 - $ssl_verifyhost = true;
1400 - } elseif ( 2 === (int) $verifyCertificate ) {
1401 - if ( ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) ) {
1402 - $ssl_verifyhost = true;
1403 - }
1404 - }
1405 - } elseif ( ( ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) ) ) ) {
1406 - $ssl_verifyhost = true;
1407 - }
1217 + if ( null !== $_org_params ) {
1218 + $params = $_org_params;
1219 + }
1220 + }
1408 1221
1409 - if ( $ssl_verifyhost ) {
1410 - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
1411 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
1412 - } else {
1413 - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // NOSONAR .
1414 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // NOSONAR .
1415 - }
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 + }
1416 1231
1417 - curl_setopt( $ch, CURLOPT_SSLVERSION, $sslVersion );
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'] );
1418 1238
1419 - if ( is_object( $website ) && property_exists( $website, 'id' ) ) {
1420 - $http_version = apply_filters( 'mainwp_curl_http_version', false, $website->id );
1421 - if ( false !== $http_version ) {
1422 - curl_setopt( $ch, CURLOPT_HTTP_VERSION, $http_version );
1423 - }
1424 - $curlopt_resolve = apply_filters( 'mainwp_curl_curlopt_resolve', false, $website->id, $website->url );
1425 - if ( is_array( $curlopt_resolve ) && ! empty( $curlopt_resolve ) ) {
1426 - curl_setopt( $ch, CURLOPT_RESOLVE, $curlopt_resolve );
1427 - curl_setopt( $ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false );
1428 - }
1429 - }
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 );
1430 1247
1431 - $headers = array( 'X-Requested-With' => 'XMLHttpRequest' );
1432 - $headers['Expect'] = self::get_expect_header( $postdata );
1248 + if ( CURLM_OK === $mrc ) {
1249 + $retry_added = true;
1250 + unset( $requestUrls[ $rid ] );
1251 + continue; // libcurl updates $running automatically.
1252 + }
1433 1253
1434 - if ( class_exists( '\WpOrg\Requests\Requests' ) ) {
1435 - $headers = \WpOrg\Requests\Requests::flatten( $headers );
1436 - } else {
1437 - $headers = \Requests::flatten( $headers );
1438 - }
1254 + curl_multi_remove_handle( $mh, $info['handle'] );
1255 + }
1256 + }
1439 1257
1440 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
1441 - curl_setopt( $ch, CURLOPT_REFERER, get_option( 'siteurl' ) );
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 + }
1442 1262
1443 - $force_use_ipv4 = false;
1444 - $forceUseIPv4 = isset( $others['force_use_ipv4'] ) ? (int) $others['force_use_ipv4'] : null;
1445 - if ( null !== $forceUseIPv4 ) {
1446 - if ( 1 === $forceUseIPv4 ) {
1447 - $force_use_ipv4 = true;
1448 - } elseif ( 2 === $forceUseIPv4 ) {
1449 - if ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
1450 - $force_use_ipv4 = true;
1451 - }
1452 - }
1453 - } elseif ( 1 === (int) get_option( 'mainwp_forceUseIPv4' ) ) {
1454 - $force_use_ipv4 = true;
1455 - }
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 + }
1456 1267
1457 - if ( $force_use_ipv4 ) {
1458 - if ( defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
1459 - curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
1460 - }
1461 - }
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 );
1462 1276
1463 - $timeout = 20 * 60 * 60;
1464 - curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1465 - MainWP_System_Utility::set_time_limit( $timeout );
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 - MainWP_Utility::end_session();
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 + }
1468 1290
1469 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Executing handlers' );
1291 + return true;
1292 + }
1470 1293
1471 - $disabled_functions = ini_get( 'disable_functions' );
1472 - if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
1473 - $mh = @curl_multi_init();
1474 - @curl_multi_add_handle( $mh, $ch );
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 + }
1475 1306
1476 - $lastRun = 0;
1477 - do {
1478 - if ( 20 < time() - $lastRun ) {
1479 - MainWP_System_Utility::set_time_limit( $timeout );
1480 - $lastRun = time();
1481 - }
1482 - @curl_multi_exec( $mh, $running );
1483 - while ( $info = @curl_multi_info_read( $mh ) ) {
1484 - $data = @curl_multi_getcontent( $info['handle'] );
1307 + $bytesize = 0;
1308 + $iterator = new \RecursiveIteratorIterator( new \RecursiveArrayIterator( $data ) );
1485 1309
1486 - $http_status = @curl_getinfo( $info['handle'], CURLINFO_HTTP_CODE );
1487 - $err = @curl_error( $info['handle'] );
1488 - $real_url = @curl_getinfo( $info['handle'], CURLINFO_EFFECTIVE_URL );
1310 + foreach ( $iterator as $datum ) {
1311 + $bytesize += strlen( (string) $datum );
1489 1312
1490 - @curl_multi_remove_handle( $mh, $info['handle'] );
1491 - }
1492 - usleep( 10000 );
1493 - } while ( $running > 0 );
1494 - if ( 'resource' === gettype( $mh ) ) {
1495 - @curl_multi_close( $mh );
1496 - }
1497 - } else {
1498 - $data = @curl_exec( $ch );
1499 - $http_status = @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
1500 - $err = @curl_error( $ch );
1501 - $real_url = @curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
1502 - }
1313 + if ( $bytesize >= 1048576 ) {
1314 + return '100-Continue';
1315 + }
1316 + }
1503 1317
1504 - $host = wp_parse_url( $real_url, PHP_URL_HOST );
1505 - $ip = gethostbyname( $host );
1318 + return '';
1319 + }
1506 1320
1507 - if ( null !== $website ) {
1508 - MainWP_DB_Common::instance()->insert_or_update_request_log( $website->id, $ip, null, microtime( true ) );
1509 - }
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 + }
1510 1341
1511 - $raw_response = isset( $others['raw_response'] ) && 'yes' === $others['raw_response'] ? true : false;
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 + }
1512 1355
1513 - $output['fetch_data'] = $data;
1514 - $output['http_status'] = (int) $http_status;
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 + }
1515 1363
1516 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'http status: [' . $http_status . '] err: [' . $err . ']' );
1517 - if ( '400' === $http_status ) {
1518 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'post data: [' . MainWP_Utility::value_to_string( $postdata, 1 ) . ']' );
1519 - }
1364 + return $fh;
1365 + }
1366 + }
1520 1367
1521 - MainWP_Logger::instance()->log_execution_time( 'fetch_url_site :: [url=' . $url . ']' );
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 + }
1522 1381
1523 - $thr_error = null;
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 + }
1524 1398
1525 - if ( ( false === $data ) && empty( $http_status ) ) {
1526 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=0][' . $err . ']' );
1527 - $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1528 - } elseif ( empty( $data ) && ! empty( $err ) ) {
1529 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=' . $http_status . '][' . $err . ']' );
1530 - $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1531 - } elseif ( 0 < preg_match( '/<mainwp>(.*)<\/mainwp>/', $data, $results ) ) {
1532 - $result = $results[1];
1533 - $information = MainWP_System_Utility::get_child_response( base64_decode( $result ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
1534 - unset( $output['fetch_data'] ); // hide the data.
1535 - $data_log = is_array( $postdata ) ? print_r( $postdata, true ) : ( is_string( $postdata ) ? $postdata : '' ); //phpcs:ignore -- good.
1536 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', '[' . $url . '] postdata [' . $data_log . '] information: [OK]' ); //phpcs:ignore -- ok.
1537 - return $information;
1538 - } elseif ( 200 === (int) $http_status && ! empty( $err ) ) {
1539 - $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1540 - } elseif ( $raw_response ) {
1541 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Response: [RAW]' );
1542 - return $data;
1543 - } else {
1544 - MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] Error: NOMAINWP [data=' . ( is_string( $data ) ? $data : 'OBJECT' ) . ']' );
1545 - $detect_wsidchk = is_string( $data ) ? strpos( $data, 'wsidchk' ) : false;
1546 - if ( false !== $detect_wsidchk ) {
1547 - $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 );
1548 - } else {
1549 - $thr_error = new MainWP_Exception( 'NOMAINWP', $url ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1550 - }
1551 - }
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 + }
1552 1412
1553 - if ( null !== $thr_error ) {
1554 - $thr_error->set_data( $data );
1555 - throw $thr_error;
1556 - }
1557 - }
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 + }
1558 1422
1559 - /**
1560 - * Method check_constraints()
1561 - *
1562 - * Check connection delay constraints.
1563 - *
1564 - * @param mixed $identifier Lock identifier.
1565 - * @param mixed $website Object child site.
1566 - *
1567 - * @uses \MainWP\Dashboard\MainWP_DB_Common::close_open_requests()
1568 - * @uses \MainWP\Dashboard\MainWP_DB::get_wp_ip()
1569 - * @uses \MainWP\Dashboard\MainWP_Utility::end_session()
1570 - */
1571 - private static function check_constraints( &$identifier, $website ) { // phpcs:ignore -- Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1572 - $semLock = '103218';
1573 - $identifier = self::get_lock_identifier( $semLock );
1574 - $minimumDelay = ( ( false === get_option( 'mainwp_minimumDelay' ) ) ? 200 : get_option( 'mainwp_minimumDelay' ) );
1575 - if ( 0 < $minimumDelay ) {
1576 - $minimumDelay = $minimumDelay / 1000;
1577 - }
1578 - $minimumIPDelay = ( ( false === get_option( 'mainwp_minimumIPDelay' ) ) ? 1000 : get_option( 'mainwp_minimumIPDelay' ) );
1579 - if ( 0 < $minimumIPDelay ) {
1580 - $minimumIPDelay = $minimumIPDelay / 1000;
1581 - }
1423 + return false;
1424 + }
1582 1425
1583 - MainWP_Utility::end_session();
1584 - $delay = true;
1585 - while ( $delay ) {
1586 - self::lock( $identifier );
1587 - if ( 0 < $minimumDelay && self::check_constraints_last_request( $identifier, $minimumDelay ) ) {
1588 - continue;
1589 - }
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 );
1590 1457
1591 - if ( 0 < $minimumIPDelay && null !== $website ) {
1592 - $ip = MainWP_DB::instance()->get_wp_ip( $website->id );
1593 - if ( null !== $ip && '' !== $ip ) {
1594 - if ( self::check_constraints_last_request( $identifier, $minimumIPDelay, $ip ) ) {
1595 - continue;
1596 - }
1597 - }
1598 - }
1599 - $delay = false;
1600 - }
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 + }
1601 1462
1602 - $maximumRequests = ( ( false === get_option( 'mainwp_maximumRequests' ) ) ? 4 : get_option( 'mainwp_maximumRequests' ) );
1603 - $maximumIPRequests = ( ( false === get_option( 'mainwp_maximumIPRequests' ) ) ? 1 : get_option( 'mainwp_maximumIPRequests' ) );
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 + }
1604 1496
1605 - $first = true;
1606 - $delay = true;
1607 - while ( $delay ) {
1608 - if ( ! $first ) {
1609 - self::lock( $identifier );
1610 - } else {
1611 - $first = false;
1612 - }
1497 + if ( ! is_array( $params ) ) {
1498 + $params = array();
1499 + }
1613 1500
1614 - MainWP_DB_Common::instance()->close_open_requests();
1501 + $others = array(
1502 + 'force_use_ipv4' => $website->force_use_ipv4,
1503 + 'upgrade' => ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ),
1504 + );
1615 1505
1616 - if ( 0 < $maximumRequests && self::check_constraints_open_requests( $identifier, $maximumRequests ) ) {
1617 - continue;
1618 - }
1506 + if ( isset( $rawResponse ) && $rawResponse ) {
1507 + $others['raw_response'] = 'yes';
1508 + }
1619 1509
1620 - if ( 0 < $maximumIPRequests && null !== $website ) {
1621 - $ip = MainWP_DB::instance()->get_wp_ip( $website->id );
1622 - if ( null !== $ip && '' !== $ip ) {
1623 - if ( self::check_constraints_open_requests( $identifier, $maximumIPRequests, $ip ) ) {
1624 - continue;
1625 - }
1626 - }
1627 - }
1628 - $delay = false;
1629 - }
1630 - }
1510 + $params['optimize'] = ( ( 1 === (int) get_option( 'mainwp_optimize', 1 ) ) ? 1 : 0 );
1631 1511
1632 - /**
1633 - * Method check_constraints_last_request().
1634 - *
1635 - * Check constraints for last requests.
1636 - *
1637 - * @param mixed $identifier connect identifier.
1638 - * @param int $minimumDelay minimum delay.
1639 - * @param string|null $ip ip address.
1640 - *
1641 - * @uses \MainWP\Dashboard\MainWP_DB_Common::get_last_request_timestamp()
1642 - */
1643 - private static function check_constraints_last_request( $identifier, $minimumDelay, $ip = null ) {
1644 - $lastRequest = MainWP_DB_Common::instance()->get_last_request_timestamp( $ip );
1645 - if ( $lastRequest > ( ( microtime( true ) ) - $minimumDelay ) ) {
1646 - self::release( $identifier );
1647 - $sleep = ( $minimumDelay - ( ( microtime( true ) ) - $lastRequest ) ) * 1000 * 1000;
1648 - $sleep = intval( $sleep );
1649 - usleep( $sleep );
1650 - return true;
1651 - }
1652 - return false;
1653 - }
1512 + $updating_website = false;
1513 + $type = '';
1514 + $list = '';
1654 1515
1655 - /**
1656 - * Method check_constraints_open_requests().
1657 - *
1658 - * Check constraints for open requests.
1659 - *
1660 - * @param mixed $identifier connect identifier.
1661 - * @param int $maximumRequests maximum requests.
1662 - * @param string|null $ip ip address.
1663 - *
1664 - * @uses \MainWP\Dashboard\MainWP_DB_Common::get_nrof_open_requests()
1665 - */
1666 - private static function check_constraints_open_requests( $identifier, $maximumRequests, $ip = null ) {
1667 - $nrOfOpenRequests = MainWP_DB_Common::instance()->get_nrof_open_requests( $ip );
1668 - if ( $nrOfOpenRequests >= $maximumRequests ) {
1669 - self::release( $identifier );
1670 - usleep( 200000 );
1671 - return true;
1672 - }
1673 - return false;
1674 - }
1516 + $premium_update = 'process_premium_updates' === $what && ! empty( $params['premium_perform'] ) && 'premium_update' === $params['premium_perform'] ? true : false;
1675 1517
1676 - /**
1677 - * Method download_to_file()
1678 - *
1679 - * Download to file.
1680 - *
1681 - * @param mixed $url Download URL.
1682 - * @param mixed $file File to download to.
1683 - * @param bool $size Size of file.
1684 - * @param null $http_user htaccess username.
1685 - * @param null $http_pass htaccess password.
1686 - *
1687 - * @throws MainWP_Exception Exception message.
1688 - *
1689 - * @uses \MainWP\Dashboard\MainWP_Exception
1690 - * @uses \MainWP\Dashboard\MainWP_System::$version
1691 - * @uses \MainWP\Dashboard\MainWP_System_Utility::get_wp_file_system()
1692 - */
1693 - public static function download_to_file( $url, $file, $size = false, $http_user = null, $http_pass = null ) {
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 + }
1694 1531
1695 - $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
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 + }
1696 1546
1697 - /**
1698 - * WordPress files system object.
1699 - *
1700 - * @global object
1701 - */
1702 - global $wp_filesystem;
1547 + $information = array();
1548 + $output = array();
1703 1549
1704 - if ( $wp_filesystem->exists( $file ) && ( ( false === $size ) || ( $wp_filesystem->size( $file ) > $size ) ) ) {
1705 - $wp_filesystem->delete( $file );
1706 - }
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 + }
1707 1557
1708 - if ( ! $wp_filesystem->exists( dirname( $file ) ) ) {
1709 - $wp_filesystem->mkdir( dirname( $file ), 0777 );
1710 - }
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 );
1711 1563
1712 - if ( ! $wp_filesystem->exists( dirname( $file ) ) ) {
1713 - throw new MainWP_Exception( esc_html__( 'MainWP plugin could not create directory in order to download the file.', 'mainwp' ) );
1714 - }
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 + );
1715 1583
1716 - if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
1717 - if ( ! $wp_filesystem->is_writable( @dirname( $file ) ) ) {
1718 - throw new MainWP_Exception( esc_html__( 'MainWP upload directory is not writable.', 'mainwp' ) );
1719 - }
1720 - } elseif ( ! is_writable( @dirname( $file ) ) ) { //phpcs:ignore -- ok.
1721 - throw new MainWP_Exception( esc_html__( 'MainWP upload directory is not writable.', 'mainwp' ) );
1722 - }
1584 + }
1723 1585
1724 - $fp = fopen( $file, 'a' );
1725 - $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1726 - if ( false !== $size ) {
1727 - if ( $wp_filesystem->exists( $file ) ) {
1728 - $size = $wp_filesystem->size( $file );
1729 - $url .= '&foffset=' . $size;
1730 - }
1731 - }
1732 - $ch = curl_init( str_replace( ' ', '%20', $url ) );
1586 + $others['function'] = $what;
1733 1587
1734 - $proxy = new \WP_HTTP_Proxy();
1735 - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1736 - curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1737 - curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1738 - curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
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 );
1739 1589
1740 - if ( $proxy->use_authentication() ) {
1741 - curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1742 - curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1743 - }
1744 - }
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 );
1745 1608
1746 - curl_setopt( $ch, CURLOPT_FILE, $fp );
1747 - curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1748 - curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1749 - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
1750 - if ( ! empty( $http_user ) && ! empty( $http_pass ) ) {
1751 - $http_pass = stripslashes( $http_pass );
1752 - curl_setopt( $ch, CURLOPT_USERPWD, "$http_user:$http_pass" );
1753 - }
1754 - curl_exec( $ch );
1755 - if ( 'resource' === gettype( $ch ) ) {
1756 - curl_close( $ch );
1757 - }
1758 - fclose( $fp );
1759 - }
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 + }
1760 1614
1761 - /**
1762 - * Method init_coockiesdir()
1763 - *
1764 - * Check for cookies directory and create it if it doesn't already exist,
1765 - * set the file permissions and update htaccess.
1766 - *
1767 - * @param mixed $cookieDir Cookies directory.
1768 - *
1769 - * @return void
1770 - *
1771 - * @uses \MainWP\Dashboard\MainWP_System_Utility::get_wp_file_system()
1772 - */
1773 - public static function init_cookiesdir( $cookieDir ) {
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 + }
1774 1620
1775 - $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
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 + }
1776 1639
1777 - /**
1778 - * WordPress files system object.
1779 - *
1780 - * @global object
1781 - */
1782 - global $wp_filesystem;
1640 + return $information;
1641 + }
1783 1642
1784 - if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
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 + ) {
1785 1675
1786 - if ( ! $wp_filesystem->is_dir( $cookieDir ) ) {
1787 - $wp_filesystem->mkdir( $cookieDir, 0777 );
1788 - }
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 );
1789 1714
1790 - if ( ! file_exists( $cookieDir . '/.htaccess' ) ) {
1791 - $file_htaccess = $cookieDir . '/.htaccess';
1792 - $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
1793 - }
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 + );
1794 1733
1795 - if ( ! file_exists( $cookieDir . '/index.php' ) ) {
1796 - $file_index = $cookieDir . '/index.php';
1797 - $wp_filesystem->touch( $file_index );
1798 - }
1799 - } else {
1734 + if ( false !== $pre_result ) {
1735 + return $pre_result;
1736 + }
1737 + }
1800 1738
1801 - if ( ! file_exists( $cookieDir ) ) {
1802 - @mkdir( $cookieDir, 0777, true );
1803 - }
1739 + unset( $pForceFetch );
1804 1740
1805 - if ( ! file_exists( $cookieDir . '/.htaccess' ) ) {
1806 - $file_htaccess = @fopen( $cookieDir . '/.htaccess', 'w+' );
1807 - @fwrite( $file_htaccess, 'deny from all' );
1808 - @fclose( $file_htaccess );
1809 - }
1741 + if ( empty( $params ) ) {
1742 + $params = array();
1743 + }
1810 1744
1811 - if ( ! file_exists( $cookieDir . '/index.php' ) ) {
1812 - $file_index = @fopen( $cookieDir . '/index.php', 'w+' );
1813 - @fclose( $file_index );
1814 - }
1815 - }
1816 - }
1745 + $postdata = static::get_post_data_not_authed( $url, $admin, $what, $params );
1746 + $website = null;
1817 1747
1818 - /**
1819 - * Method get_file_content()
1820 - *
1821 - * Get contents of file.
1822 - *
1823 - * @param mixed $url File Location.
1824 - *
1825 - * @return mixed false|$data
1826 - *
1827 - * @uses \MainWP\Dashboard\MainWP_System::$version
1828 - */
1829 - public static function get_file_content( $url ) {
1830 - $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
1831 - $ch = curl_init();
1748 + $others['function'] = $what;
1749 + return static::fetch_url( $website, $url, $postdata, false, $verifyCertificate, true, $http_user, $http_pass, $sslVersion, $others, $output );
1750 + }
1832 1751
1833 - $proxy = new \WP_HTTP_Proxy();
1834 - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1835 - curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1836 - curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
1837 - curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
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 + ) {
1838 1786
1839 - if ( $proxy->use_authentication() ) {
1840 - curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1841 - curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1842 - }
1843 - }
1787 + $start = time();
1844 1788
1845 - curl_setopt( $ch, CURLOPT_HEADER, 0 );
1846 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
1847 - curl_setopt( $ch, CURLOPT_URL, $url );
1848 - curl_setopt( $ch, CURLOPT_USERAGENT, $agent );
1849 - curl_setopt( $ch, CURLOPT_ENCODING, 'none' );
1789 + try {
1790 + $tmpUrl = $url;
1791 + if ( '/' !== substr( $tmpUrl, - 1 ) ) {
1792 + $tmpUrl .= '/';
1793 + }
1850 1794
1851 - $data = @curl_exec( $ch );
1852 - $httpCode = @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
1853 - if ( 'resource' === gettype( $ch ) ) {
1854 - curl_close( $ch );
1855 - }
1856 - if ( 200 === (int) $httpCode ) {
1857 - return $data;
1858 - } else {
1859 - return false;
1860 - }
1861 - }
1795 + if ( false === strpos( $url, 'wp-admin' ) ) {
1796 + $tmpUrl .= 'wp-admin/admin-ajax.php';
1797 + }
1862 1798
1863 - /**
1864 - * Method get_favico_url()
1865 - *
1866 - * Get Child Site favicon URL.
1867 - *
1868 - * @param mixed $website Child Site info.
1869 - *
1870 - * @return mixed $faviurl Favicon URL.
1871 - *
1872 - * @uses \MainWP\Dashboard\MainWP_DB::get_website_option()
1873 - * @uses \MainWP\Dashboard\MainWP_System_Utility::get_icons_dir()
1874 - * @uses \MainWP\Dashboard\MainWP_Utility::remove_http_prefix()
1875 - */
1876 - public static function get_favico_url( $website ) {
1877 - $favi = MainWP_DB::instance()->get_website_option( $website, 'favi_icon', '' );
1878 - $faviurl = '';
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 + }
1879 1804
1880 - if ( ! empty( $favi ) ) {
1881 - if ( false !== strpos( $favi, 'favi-' . intval( $website->id ) . '-' ) ) {
1882 - $dirs = MainWP_System_Utility::get_icons_dir();
1883 - if ( file_exists( $dirs[0] . $favi ) ) {
1884 - $faviurl = $dirs[1] . $favi;
1885 - } else {
1886 - $faviurl = '';
1887 - }
1888 - } elseif ( ( 0 === strpos( $favi, '//' ) ) || ( 0 === strpos( $favi, 'http' ) ) ) {
1889 - $faviurl = $favi;
1890 - } else {
1891 - $faviurl = $website->url . $favi;
1892 - $faviurl = MainWP_Utility::remove_http_prefix( $faviurl );
1893 - }
1894 - }
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 + }
1895 1812
1896 - if ( empty( $faviurl ) ) {
1897 - $faviurl = false;
1898 - }
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 + ) {
1899 1853
1900 - return $faviurl;
1901 - }
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 + }
1902 2571 }