| 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 »', '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> <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 |
|