PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.1.0
Defender Security – Malware Scanner, Login Security & Firewall v6.1.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / class-hub-connector.php

class-hub-connector.php in Defender Security – Malware Scanner, Login Security & Firewall 6.1.0, at src/component/class-hub-connector.php

805 lines 26.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Hub Connector class.
4 *
5 * @package WP_Defender\Component
6 */
7
8 namespace WP_Defender\Component;
9
10 use WPMUDEV\Hub\Connector;
11 use Calotes\Base\Component;
12 use WP_Defender\Behavior\WPMUDEV;
13 use WP_Defender\Traits\Defender_Dashboard_Client;
14 use WPMUDEV\Hub\Connector\API;
15
16 /**
17 * Handles the functionality related to the Hub Connector module.
18 *
19 * @since 4.10.0
20 */
21 class Hub_Connector extends Component {
22 use Defender_Dashboard_Client;
23
24 /**
25 * The identifier for the WP Defender plugin in the Hub.
26 *
27 * @const string
28 */
29 public const PLUGIN_IDENTIFIER = 'defender';
30
31 /**
32 * The action name used for the Hub connection.
33 *
34 * @const string
35 */
36 public const CONNECTION_ACTION = 'hub_connection';
37
38 /**
39 * Temporary flag used while the first Hub sync is being checked.
40 *
41 * @const string
42 */
43 public const SYNCING_TRANSIENT_KEY = 'wpdef_hc_site_syncing';
44
45 /**
46 * Checks if the Hub connector should render its UI.
47 *
48 * Verifies the nonce from the global-ip page to determine if the Hub connector should render its UI.
49 *
50 * @return bool The nonce value on success, false on failure.
51 */
52 public static function should_render() {
53 // Team selection callbacks need the library's own UI — let them through.
54 if ( self::is_team_selection_callback() ) {
55 return ! self::get_hcm_status() && wp_verify_nonce( defender_get_data_from_request( '_def_nonce', 'g' ), self::CONNECTION_ACTION );
56 }
57
58 $page_action = sanitize_text_field( wp_unslash( $_GET['page_action'] ?? '' ) );
59 $is_hub_callback = ! empty( $_GET['hub_connector_callback'] );
60
61 if ( self::CONNECTION_ACTION === $page_action || $is_hub_callback ) {
62 return false;
63 }
64
65 return ! self::get_hcm_status() && wp_verify_nonce( defender_get_data_from_request( '_def_nonce', 'g' ), self::CONNECTION_ACTION );
66 }
67
68 /**
69 * Initialize the Hub Connector module and set its options.
70 *
71 * The `extra/hub-connector/connector.php` file is required, and the options are set for the Hub Connector module.
72 *
73 * @return void
74 */
75 public function init() {
76 require_once defender_path( 'extra/hub-connector/connector.php' );
77 $options = array(
78 'screens' => array(
79 'toplevel_page_wp-defender',
80 'toplevel_page_wp-defender-network',
81 'defender_page_wdf-ip-lockout',
82 'defender_page_wdf-ip-lockout-network',
83 'defender-pro_page_wdf-ip-lockout',
84 'defender-pro_page_wdf-ip-lockout-network',
85 ),
86 'extra_args' => array(
87 'register' => array(
88 'connect_ref' => self::PLUGIN_IDENTIFIER,
89 'utm_medium' => 'plugin',
90 'utm_source' => self::PLUGIN_IDENTIFIER,
91 ),
92 ),
93 );
94 // Get advanced params.
95 $page = defender_get_data_from_request( 'page', 'g' );
96 $view = defender_get_data_from_request( 'view', 'g' );
97
98 $result = $this->maybe_summary_box_trigger( $view );
99 // Get advanced params.
100 $res = $this->get_utm_tags( $page, $result['view'], $result['is_summary'] );
101 if ( '' !== $res['utm_campaign'] ) {
102 $options['extra_args']['register']['utm_campaign'] = $res['utm_campaign'];
103 }
104 if ( '' !== $res['utm_content'] ) {
105 $options['extra_args']['register']['utm_content'] = $res['utm_content'];
106 }
107
108 Connector::get()->set_options( self::PLUGIN_IDENTIFIER, $options );
109 }
110
111 /**
112 * Checks if the current request is a Hub team-selection callback.
113 *
114 * @return bool
115 */
116 public static function is_team_selection_callback(): bool {
117 return ! empty( $_REQUEST['hub_connector_callback'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
118 && ! empty( $_REQUEST['user_apikey'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
119 && 1 === (int) ( $_REQUEST['is_multi_auth'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
120 }
121
122 /**
123 * Gets the label for the button to connect the site to the Hub.
124 *
125 * @return string The label for the button.
126 */
127 public function get_button_label(): string {
128 if ( $this->is_dash_installed() && ! $this->is_dash_activated() ) {
129 return __( 'Activate WPMU DEV Dashboard', 'defender-security' );
130 }
131 if ( $this->is_dash_activated() ) {
132 return __( 'Log in to WPMU DEV', 'defender-security' );
133 }
134 return __( 'Connect site for Full protection', 'defender-security' );
135 }
136
137 /**
138 * Modify text string vars.
139 *
140 * @param array $texts Vars.
141 * @param string $plugin Plugin identifier.
142 *
143 * @return array
144 */
145 public static function customize_text_vars( $texts, $plugin ): array {
146 if ( self::PLUGIN_IDENTIFIER === $plugin ) {
147 $feature_name = self::get_feature_name();
148 $texts['create_account_desc'] = sprintf(
149 /* translators: 1. Feature name. 2. Opened tag. 3. Closed tag. */
150 esc_html__( 'Create a free account to connect your site to WPMU DEV and activate %1$s. %2$sIt`s fast, seamless, and free%3$s.', 'defender-security' ),
151 '<strong>' . __( 'Defender - ', 'defender-security' ) . $feature_name . '</strong>',
152 '<i>',
153 '</i>'
154 );
155 $texts['login_desc'] = sprintf(
156 /* translators: %s: Feature name. */
157 esc_html__( 'Log in with your WPMU DEV account credentials to activate %s.', 'defender-security' ),
158 $feature_name
159 );
160 }
161
162 return $texts;
163 }
164
165 /**
166 * Get feature's name.
167 *
168 * @return string
169 */
170 public static function get_feature_name(): string {
171 $feature_name = defender_get_data_from_request( 'module_name', 'g' );
172 if ( ! is_null( $feature_name ) && '' !== $feature_name ) {
173 return $feature_name;
174 }
175
176 $view = defender_get_data_from_request( 'view', 'g' );
177 switch ( $view ) {
178 case 'blocklist':
179 return __( 'Custom IP Allow/Block list', 'defender-security' );
180 default:
181 return __( 'AntiBot Global Firewall', 'defender-security' );
182 }
183 }
184
185 /**
186 * Get UTM tags.
187 *
188 * @param string $page The page to load.
189 * @param string $view The view to load.
190 * @param bool $is_summary Is this from the Summary section? Default false.
191 *
192 * @return array
193 */
194 private function get_utm_tags( string $page = '', string $view = '', bool $is_summary = false ): array {
195 $utm_campaign = '';
196 $utm_content = '';
197 if ( '' !== $page ) {
198 switch ( $page ) {
199 // There are buttons on notice or widget on the Dashboard page.
200 case 'wp-defender':
201 $utm_content = 'hub-connector';
202 $utm_campaign = ( 'dashboard' === $view )
203 ? 'defender_dashboard_firewall_antibot'
204 : 'defender_onboarding_antibot';
205 break;
206 case 'wdf-ip-lockout':
207 $utm_content = 'hub-connector';
208 if ( $is_summary ) {
209 $utm_campaign = 'defender_firewall_antibot_summary';
210 } else {
211 $utm_campaign = ( 'blocklist' === $view )
212 ? 'defender_firewall_centralip'
213 : 'defender_firewall_antibot';
214 }
215 break;
216 default:
217 break;
218 }
219 }
220
221 return array(
222 'utm_campaign' => $utm_campaign,
223 'utm_content' => $utm_content,
224 );
225 }
226
227 /**
228 * Update data if a trigger is the Summary section.
229 *
230 * @param string $view The view to load.
231 *
232 * @return array
233 */
234 private function maybe_summary_box_trigger( string $view ): array {
235 return array(
236 'view' => 'summary-box' === $view ? 'global-ip' : $view,
237 'is_summary' => 'summary-box' === $view,
238 );
239 }
240
241 /**
242 * Retrieve the Hub connector URL.
243 *
244 * @param string $page Optional. The page to load. Default is empty string.
245 * @param string $view Optional. The view to load. Default is empty string.
246 * @param string $utm_campaign Optional. The UTM campaign parameter. Default is empty string.
247 * @param string $utm_content Optional. The UTM content parameter. Default is empty string.
248 * @param string $module_slug Optional. Module slug for post-connection auto-enable. Default is empty string.
249 *
250 * @return string
251 */
252 public function get_url( string $page = '', string $view = '', string $utm_campaign = '', string $utm_content = '', string $module_slug = '' ): string {
253 $args = self::get_connection_args( '' !== $page ? $page : 'wp-defender', $module_slug );
254
255 if ( '' !== $view ) {
256 $args['view'] = $view;
257 }
258
259 $result = $this->maybe_summary_box_trigger( $view );
260 // Get advanced params.
261 $res = $this->get_utm_tags( $page, $result['view'], $result['is_summary'] );
262
263 if ( '' !== $utm_campaign ) {
264 $args['utm_campaign'] = $utm_campaign;
265 } elseif ( '' !== $res['utm_campaign'] ) {
266 $args['utm_campaign'] = $res['utm_campaign'];
267 }
268
269 if ( '' !== $utm_content ) {
270 $args['utm_content'] = $utm_content;
271 } elseif ( '' !== $res['utm_content'] ) {
272 $args['utm_content'] = $res['utm_content'];
273 }
274
275 return add_query_arg( $args, self::get_admin_url() );
276 }
277
278 /**
279 * Checks if Hub Connector is logged in.
280 *
281 * @return bool
282 */
283 public static function is_logged_in(): bool {
284 if ( ! class_exists( '\WPMUDEV\Hub\Connector\API' ) ) {
285 return false;
286 }
287
288 $api = API::get();
289
290 return $api && method_exists( $api, 'is_logged_in' ) && $api->is_logged_in();
291 }
292
293 /**
294 * Sync site data with Hub.
295 *
296 * @return bool|\WP_Error
297 */
298 public function sync() {
299 if ( ! class_exists( '\WPMUDEV\Hub\Connector\API' ) ) {
300 return false;
301 }
302
303 $api = API::get();
304
305 if ( $api && method_exists( $api, 'sync_site' ) ) {
306 $sync = $api->sync_site();
307 if ( is_wp_error( $sync ) ) {
308 return $sync;
309 }
310 }
311
312 return true;
313 }
314
315 /**
316 * Verify the auth nonce from request.
317 *
318 * @return bool
319 */
320 public static function verify_auth_nonce(): bool {
321 return (bool) wp_verify_nonce(
322 sanitize_text_field( wp_unslash( $_REQUEST['auth_nonce'] ?? '' ) ),
323 'auth_nonce'
324 );
325 }
326
327 /**
328 * Check if the site is in the temporary post-auth Hub syncing state.
329 *
330 * @return bool
331 */
332 public static function is_syncing(): bool {
333 // If already logged in or a login error occurred, the auth flow is settled —
334 // clear any stale syncing transient and report not syncing.
335 if ( self::is_logged_in() || self::has_error_in_login() ) {
336 delete_site_transient( self::SYNCING_TRANSIENT_KEY );
337
338 return false;
339 }
340
341 $page_action = sanitize_text_field( wp_unslash( $_REQUEST['page_action'] ?? '' ) );
342 $syncing = current_user_can( 'manage_options' )
343 && self::verify_auth_nonce()
344 && self::CONNECTION_ACTION === $page_action
345 && ! empty( $_REQUEST['set_apikey'] );
346
347 if ( $syncing ) {
348 set_site_transient( self::SYNCING_TRANSIENT_KEY, true, MINUTE_IN_SECONDS );
349 }
350
351 return (bool) get_site_transient( self::SYNCING_TRANSIENT_KEY );
352 }
353
354 /**
355 * Check if there is an error in login response from HUB.
356 *
357 * @return bool
358 */
359 public static function has_error_in_login() {
360 $page_action = sanitize_text_field( wp_unslash( $_GET['page_action'] ?? '' ) );
361 $api_error = sanitize_text_field( wp_unslash( $_GET['api_error'] ?? 0 ) );
362 $is_hub_callback = ! empty( $_GET['hub_connector_callback'] );
363
364 if ( self::CONNECTION_ACTION === $page_action ) {
365 return ! empty( $api_error );
366 }
367
368 if ( ! $is_hub_callback || ! self::verify_auth_nonce() ) {
369 return false;
370 }
371
372 return ! empty( $api_error );
373 }
374
375 /**
376 * Get authentication error messages. Copied from HC.
377 *
378 * Based on the error code, prepare different error messages.
379 *
380 * @return string
381 */
382 public static function get_auth_error(): string {
383 /**
384 * Nonce is verified in `process_auth_callback` before this method called.
385 *
386 * @see self::process_auth_callback()
387 */
388 // phpcs:disable WordPress.Security.NonceVerification.Recommended
389
390 $error = '';
391 $reset_url = class_exists( '\WPMUDEV\Hub\Connector\Data' )
392 ? \WPMUDEV\Hub\Connector\Data::get()->server_url( 'forgot-password' )
393 : 'https://wpmudev.com/forgot-password';
394 $skip_trial_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub/account/?skip_trial' );
395 $trial_info_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'docs/getting-started/how-free-trials-work/' );
396 $websites_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub2/' );
397 $security_info_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'manuals/hub-security/' );
398 $support_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub/support/' );
399 $account_details_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub2/account/details/' );
400
401 if ( isset( $_GET['api_error'] ) ) {
402 // Get errors.
403 $api_error = sanitize_key( wp_unslash( $_GET['api_error'] ) );
404 $auth_error = sanitize_key( wp_unslash( $_GET['auth_error'] ?? '' ) );
405
406 if ( 1 === (int) $api_error || 'auth' === $api_error ) {
407 switch ( $auth_error ) {
408 case 'google_linked':
409 $error = sprintf(
410 // translators: %s Account detail URL.
411 __(
412 'You are currently using your Google account as your preferred login method. If you wish to login with your WPMU DEV email & password instead, please change the <strong>Login Method</strong> in <a href="%s" target="_blank">your WPMU DEV account</a>.',
413 'defender-security'
414 ),
415 $account_details_url
416 );
417 break;
418 case 'google_unlinked':
419 $error = sprintf(
420 // translators: %s Account detail URL.
421 __(
422 'You are currently using your WPMU DEV email & password as your preferred login method. If you wish to login with your Google account instead, please change the <strong>Login Method</strong> in <a href="%s" target="_blank">your WPMU DEV account</a>.',
423 'defender-security'
424 ),
425 $account_details_url
426 );
427 break;
428 case 'reauth_google':
429 $error = sprintf(
430 // translators: %1$s Account detail URL, %2$s Reset URL.
431 __(
432 'Due to security improvements, you will need to re-link your Google account in the Hub. Please log in with your WPMU DEV email & password for now, then set up your preferred <strong>Login Method</strong> in <a href="%1$s" target="_blank">your WPMU DEV account</a>. Forgot your password? You can <a href="%2$s" target="_blank">reset it here</a>.',
433 'defender-security'
434 ),
435 $account_details_url,
436 $reset_url
437 );
438 break;
439 default:
440 // Invalid credentials.
441 $error = sprintf(
442 '%s<br><a href="%s" target="_blank"><strong>%s</strong></a>',
443 esc_html__( 'Your login details were incorrect. Please make sure you\'re using your WPMU DEV email and password and try again.', 'defender-security' ),
444 $reset_url,
445 esc_html__( 'Forgot your password?', 'defender-security' )
446 );
447 break;
448 }
449 } else {
450 switch ( $api_error ) {
451 case 'in_trial':
452 $error = sprintf(
453 '%s<br><a href="%s" target="_blank">%s</a>',
454 sprintf(
455 // translators: %1$s Rest URL, %2$s Upgrade URL, %3$s Trial URL.
456 __(
457 'This domain has previously been registered with us by the user %1$s. To use WPMU DEV on this domain, you can either log in with the original account (you can <a target="_blank" href="%2$s">reset your password</a>) or <a target="_blank" href="%3$s">upgrade your trial</a> to a full membership. Trial accounts can\'t use previously registered domains - <a target="_blank" href="%4$s">here\'s why</a>.',
458 'defender-security'
459 ),
460 '<strong style="word-break: break-all;">' . esc_html( $_GET['display_name'] ) . '</strong>', // phpcs:ignore
461 $reset_url,
462 $skip_trial_url,
463 $trial_info_url
464 ),
465 $support_url,
466 __( 'Contact support if you need further assistance &raquo;', 'defender-security' )
467 );
468 break;
469 case 'already_registered':
470 $error = sprintf(
471 // translators: %1$d Account name, %2$s Security info, %3$s Hub URL, %4$s Support URL.
472 __(
473 'This site is currently registered to %1$s. For <a target="_blank" href="%2$s">security reasons</a> they will need to go to the <a target="_blank" href="%3$s">WPMU DEV Hub</a> and remove this domain before you can log in. If you do not have access to that account, and have no way of contacting that user, please <a target="_blank" href="%4$s">contact support for assistance</a>.',
474 'defender-security'
475 ),
476 '<strong style="word-break: break-all;">' . esc_html( $_GET['display_name'] ) . '</strong>', // phpcs:ignore.
477 $security_info_url,
478 $websites_url,
479 $support_url
480 );
481 break;
482 case 'banned_account':
483 $error = sprintf(
484 // translators: %s Support URL.
485 __( 'This domain cannot be registered to your WPMU DEV account.<br><a href="%s">Contact Accounts & Billing if you need further assistance »</a>', 'defender-security' ),
486 \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub2/#ask-question' )
487 );
488 break;
489 case 'expired_membership':
490 $error = sprintf(
491 // translators: %1$s Hub Account URL, %2$s: Switch to Free URL.
492 __(
493 'Login failed — your WPMU DEV membership has expired. Renew now to regain full access, or switch to our free plan to continue managing all your site in the Hub.<br/><br/><a class="sui-button sui-button-blue" href="%1$s" target="_blank">Renew Membership</a>&nbsp;<a class="sui-button sui-button-ghost" href="%2$s" target="_blank">Switch to Free</a>',
494 'defender-security'
495 ),
496 \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub2/account/' ),
497 \WPMUDEV\Hub\Connector\Data::get()->server_url( 'hub2/?switch-free=1' )
498 );
499 break;
500 case 'invalid_nonce':
501 case 'invalid_double_submit_cookie':
502 case 'invalid_google_creds':
503 case '':
504 $error = __( 'Google login failed. Please try again.', 'defender-security' );
505 break;
506 default:
507 // Use the actual error message passed through from the sync poll if available.
508 if ( ! empty( $_GET['api_error_msg'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
509 $error = sanitize_text_field( wp_unslash( $_GET['api_error_msg'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
510 } else {
511 $error = __( 'Unknown error. Please update the WPMU DEV Dashboard plugin and try again.', 'defender-security' );
512 }
513 break;
514 }
515 }
516 } elseif ( ! empty( $_REQUEST['connection_error'] ) ) {
517 // Variable `$connection_error` is set by the UI function `render_dashboard`.
518 $error = sprintf(
519 '%s<br>%s<br><em>%s</em>',
520 __( 'Your server had a problem connecting to WPMU DEV. Please try again.', 'defender-security' ),
521 __( 'If this problem continues, please contact your host with this error message and ask:', 'defender-security' ),
522 sprintf(
523 // translators: url to API.
524 __( '"Is PHP on my server properly configured to be able to contact %s with a POST HTTP request via fsockopen or CURL?"', 'defender-security' ),
525 \WPMUDEV\Hub\Connector\Data::get()->server_url()
526 )
527 );
528 } elseif ( ! empty( $_REQUEST['invalid_key'] ) ) {
529 // Invalid API key.
530 $error = __( 'Your API Key was invalid. Please try again.', 'defender-security' );
531 }
532
533 /**
534 * Filter to modify auth error text.
535 *
536 * @since 1.0.0
537 *
538 * @param string $error Error message.
539 * @param string $plugin Plugin identifier.
540 */
541 return apply_filters( 'wpmudev_hub_connector_get_auth_error', $error, self::PLUGIN_IDENTIFIER );
542 // phpcs:enable WordPress.Security.NonceVerification.Recommended
543 }
544
545 /**
546 * Get Hub Google login URL (https://wpmudev.com/api/dashboard/v2/google-auth).
547 *
548 * @return string
549 */
550 public static function get_hub_google_login_url(): string {
551 return \WPMUDEV\Hub\Connector\Data::get()->server_url( 'api/dashboard/v2/google-auth' );
552 }
553
554 /**
555 * Get connection URL for Hub.
556 *
557 * @param string $target_page The target page to connect to.
558 * @param string $utm_campaign The UTM campaign to append to the URL.
559 *
560 * @return string The connection URL.
561 */
562 public static function get_connect_site_url( string $target_page = 'wp-defender', string $utm_campaign = '' ): string {
563 $args = array();
564
565 if ( self::should_redirect_to_dashboard() ) {
566 $args['page'] = 'wpmudev';
567 } else {
568 $args = self::get_connection_args( $target_page );
569 }
570
571 if ( ! empty( $utm_campaign ) ) {
572 $args['utm_campaign'] = sanitize_text_field( $utm_campaign );
573 }
574
575 $admin_url = self::get_admin_url();
576
577 return add_query_arg( $args, $admin_url );
578 }
579
580 /**
581 * Get the Hub Connector callback URL inside Defender.
582 *
583 * @param string $target_page The target page to connect to.
584 * @param string $utm_campaign The UTM campaign to append to the URL.
585 *
586 * @return string
587 */
588 public static function get_hub_connector_callback_url( string $target_page = 'wp-defender', string $utm_campaign = '' ): string {
589 $args = self::get_connection_args( $target_page );
590
591 if ( ! empty( $utm_campaign ) ) {
592 $args['utm_campaign'] = sanitize_text_field( $utm_campaign );
593 }
594
595 return add_query_arg( $args, self::get_admin_url() );
596 }
597
598 /**
599 * Check if should redirect to WPMUDEV Dashboard.
600 *
601 * @return bool
602 */
603 private static function should_redirect_to_dashboard(): bool {
604 return ! self::is_wpmudev_dashboard_connected() && class_exists( '\WPMUDEV_Dashboard' );
605 }
606
607 /**
608 * Check if WPMUDEV Dashboard is connected.
609 *
610 * @return bool
611 */
612 public static function is_wpmudev_dashboard_connected(): bool {
613 if ( ! class_exists( '\WPMUDEV_Dashboard' ) ) {
614 return false;
615 }
616
617 $dashboard_api = \WPMUDEV_Dashboard::$api ?? null;
618
619 return is_object( $dashboard_api ) &&
620 method_exists( $dashboard_api, 'get_membership_status' ) &&
621 method_exists( $dashboard_api, 'has_key' ) &&
622 $dashboard_api->has_key();
623 }
624
625 /**
626 * Get connection arguments for URL.
627 *
628 * @param string $target_page The target page.
629 * @param string $module_slug Optional module slug to include for post-connection auto-enable.
630 * @return array
631 */
632 private static function get_connection_args( string $target_page, string $module_slug = '' ): array {
633 $args = array(
634 'page' => sanitize_text_field( $target_page ),
635 '_def_nonce' => wp_create_nonce( self::CONNECTION_ACTION ),
636 'page_action' => self::CONNECTION_ACTION,
637 'hub_connector_callback' => 1,
638 );
639
640 if ( '' !== $module_slug ) {
641 $args['module_slug'] = sanitize_text_field( $module_slug );
642 }
643
644 return $args;
645 }
646
647 /**
648 * Get appropriate admin URL.
649 *
650 * @return string
651 */
652 private static function get_admin_url(): string {
653 return is_network_admin() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' );
654 }
655
656 /**
657 * Get the site domain.
658 *
659 * @return string
660 */
661 public static function get_site_domain(): string {
662 return \WPMUDEV\Hub\Connector\Data::get()->network_site_url();
663 }
664
665 /**
666 * Get Hub Login Auth URL (https://wpmudev.com/api/dashboard/v2/site-authenticate).
667 *
668 * @return string
669 */
670 public static function get_hub_site_login_auth_url(): string {
671 return \WPMUDEV\Hub\Connector\API::get()->rest_url( 'site-authenticate' );
672 }
673
674 /**
675 * Hub member profile for React UIs (same API as other WPMU DEV plugins: Data::profile_data).
676 *
677 * @return array<string, string>|null
678 */
679 public static function get_profile_data_for_ui(): ?array {
680 if ( ! class_exists( '\WPMUDEV\Hub\Connector\Data' ) ) {
681 return null;
682 }
683
684 $api_logged_in = self::is_logged_in();
685 $dashboard_connected = self::is_wpmudev_dashboard_connected();
686 $is_pro = wd_di()->get( WPMUDEV::class )->is_pro();
687
688 if ( ! $api_logged_in && ! $dashboard_connected && ! $is_pro ) {
689 return null;
690 }
691
692 if (
693 $dashboard_connected &&
694 ! $api_logged_in &&
695 is_object( \WPMUDEV_Dashboard::$settings ) &&
696 method_exists( \WPMUDEV_Dashboard::$settings, 'get' )
697 ) {
698 $dashboard_profile = \WPMUDEV_Dashboard::$settings->get( 'profile_data' );
699 $raw = $dashboard_profile['profile'] ?? array();
700 } else {
701 $raw = \WPMUDEV\Hub\Connector\Data::get()->profile_data( true );
702 }
703
704 if ( ! is_array( $raw ) || empty( $raw['user_name'] ) ) {
705 return null;
706 }
707
708 $display_name = isset( $raw['name'] ) ? (string) $raw['name'] : '';
709 $user_name = isset( $raw['user_name'] ) ? (string) $raw['user_name'] : '';
710 $avatar = isset( $raw['avatar'] ) ? (string) $raw['avatar'] : '';
711
712 $initial = '' !== $display_name ? strtoupper( substr( $display_name, 0, 1 ) ) : '';
713
714 return array(
715 'initials' => $initial,
716 'avatar' => $avatar,
717 'profileBackgroundColor' => '' !== $avatar ? '#f8f8f8' : '#0059ff',
718 'profileFontColor' => '#ffffff',
719 'userName' => $user_name,
720 'email' => is_email( $user_name ) ? $user_name : '',
721 'displayName' => $display_name,
722 );
723 }
724
725 /**
726 * Get Hub register URL with site connection parameters.
727 *
728 * @param string $current_page The current page.
729 * @param string $post_sync_redirect Optional. URL to redirect to after sync completes. Default is empty string.
730 *
731 * @return string
732 */
733 public static function get_hub_register_url( string $current_page = 'wp-defender', string $post_sync_redirect = '' ): string {
734 // Fallback base URL when the Hub Connector library is not loaded.
735 $base_url = 'https://wpmudev.com/register';
736
737 if ( class_exists( '\WPMUDEV\Hub\Connector\Data' ) ) {
738 $base_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'register' );
739 }
740
741 // Get the hub connection URL.
742 $hub_connect_url = self::get_hub_connector_callback_url( $current_page );
743
744 // Prepare redirect URL with callback and auth nonce.
745 $auth_nonce = wp_create_nonce( 'auth_nonce' );
746 $callback_args = array(
747 'hub_connector_callback' => 1,
748 'auth_nonce' => $auth_nonce,
749 );
750 // rawurlencode() needed so the nested URL doesn't bleed into the outer query string.
751 if ( '' !== $post_sync_redirect ) {
752 $callback_args['post_sync_redirect'] = rawurlencode( esc_url_raw( $post_sync_redirect ) );
753 }
754 $redirect_url = add_query_arg( $callback_args, $hub_connect_url );
755
756 // URL arguments for registration URL.
757 // Do NOT REMOVE rawurlencode() site_connect_url — it is required.
758 $register_args = array(
759 'signup' => 'site-connect',
760 'site_connect_url' => rawurlencode( $redirect_url ),
761 'utm_medium' => 'plugin',
762 'utm_source' => self::PLUGIN_IDENTIFIER,
763 'utm_campaign' => 'defender_firewall_antibot',
764 'utm_content' => 'hub-connector',
765 );
766
767 return add_query_arg( $register_args, $base_url );
768 }
769
770 /**
771 * Get Hub forgot password URL.
772 *
773 * @return string
774 */
775 public static function get_hub_forgot_password_url(): string {
776 $base_url = 'https://wpmudev.com/forgot-password';
777
778 if ( class_exists( '\WPMUDEV\Hub\Connector\Data' ) ) {
779 $base_url = \WPMUDEV\Hub\Connector\Data::get()->server_url( 'forgot-password' );
780 }
781
782 $hub_connect_url = self::get_hub_connector_callback_url( 'wp-defender', 'defender_forgot_password' );
783 $auth_nonce = wp_create_nonce( 'auth_nonce' );
784 $redirect_url = add_query_arg(
785 array(
786 'hub_connector_callback' => 1,
787 'auth_nonce' => $auth_nonce,
788 ),
789 $hub_connect_url
790 );
791
792 // Do NOT rawurlencode() site_connect_url — add_query_arg() encodes values internally; double-encoding breaks the callback URL on the Hub side.
793 return add_query_arg(
794 array(
795 'site_connect_url' => $redirect_url,
796 'utm_medium' => 'plugin',
797 'utm_source' => self::PLUGIN_IDENTIFIER,
798 'utm_campaign' => 'defender_forgot_password',
799 'utm_content' => 'hub-connector',
800 ),
801 $base_url
802 );
803 }
804 }
805