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