| 1 |
<?php |
| 2 |
/** |
| 3 |
* MLSImport account status: WHY the last SaaS token request failed. |
| 4 |
* |
| 5 |
* The SaaS 'token' endpoint answers three ways: |
| 6 |
* - HTTP 200 -> the password was right and the account is active (token); |
| 7 |
* - HTTP 401 -> wrong password or unknown username/email ("Invalid credentials"); |
| 8 |
* - HTTP 403 -> the password was right but the account has NO active |
| 9 |
* subscription (the portal's is_active flag is not "yes"). |
| 10 |
* |
| 11 |
* Before this module the plugin reduced all three to "got a token or not" |
| 12 |
* and every screen blamed the password, so customers without a subscription |
| 13 |
* opened tickets about a password that was correct. This file owns the one |
| 14 |
* option that remembers the reason and the one message builder every |
| 15 |
* "not connected" surface prints, so the wording lives in a single place. |
| 16 |
* All login failures use the same prominent notice with a heading and message. |
| 17 |
* Only subscription failures add a plans button. Both account screens use it. |
| 18 |
* |
| 19 |
* Public interface: |
| 20 |
* mlsimport_account_status_record( $answer ) record the token reply reason |
| 21 |
* mlsimport_account_status() 'no_subscription' | 'invalid_credentials' | '' |
| 22 |
* mlsimport_account_not_connected_html() the warning box for the reason |
| 23 |
* |
| 24 |
* @package Mlsimport |
| 25 |
* @subpackage Mlsimport/includes |
| 26 |
*/ |
| 27 |
|
| 28 |
if ( ! defined( 'ABSPATH' ) ) { |
| 29 |
exit; |
| 30 |
} |
| 31 |
|
| 32 |
/** Option holding the reason of the last failed token request. */ |
| 33 |
define( 'MLSIMPORT_ACCOUNT_STATUS_OPTION', 'mlsimport_account_status' ); |
| 34 |
|
| 35 |
/** Portal page where a customer without a subscription can buy one. */ |
| 36 |
define( 'MLSIMPORT_ACCOUNT_SUBSCRIBE_URL', 'https://mlsimport.com/mls-import-plugin-pricing/' ); |
| 37 |
|
| 38 |
/** |
| 39 |
* Record the outcome of a SaaS token request. |
| 40 |
* |
| 41 |
* Accepts the reply as returned by ThemeImport::globalApiRequestSaas(): the |
| 42 |
* decoded body on HTTP 200 (success => true) or the failure descriptor |
| 43 |
* (success => false, error_code => HTTP status) on anything else. Only the |
| 44 |
* two definitive server verdicts are remembered: |
| 45 |
* |
| 46 |
* 1. success true -> the reason is cleared (account is fine); |
| 47 |
* 2. error_code 403 -> 'no_subscription'; |
| 48 |
* 3. error_code 401 -> 'invalid_credentials'; |
| 49 |
* 4. anything else (transport error, 5xx, malformed) -> left untouched, |
| 50 |
* because a hiccup says nothing about the account. |
| 51 |
* |
| 52 |
* @param mixed $answer The token endpoint reply. |
| 53 |
* @return void |
| 54 |
*/ |
| 55 |
function mlsimport_account_status_record( $answer ) { |
| 56 |
// Step 1: a successful login wipes any previous failure reason. |
| 57 |
if ( is_array( $answer ) && ! empty( $answer['success'] ) ) { |
| 58 |
delete_option( MLSIMPORT_ACCOUNT_STATUS_OPTION ); |
| 59 |
return; |
| 60 |
} |
| 61 |
|
| 62 |
// Step 2: map the HTTP status the server answered with to a reason. |
| 63 |
$code = is_array( $answer ) && isset( $answer['error_code'] ) ? (int) $answer['error_code'] : 0; |
| 64 |
if ( 403 === $code ) { |
| 65 |
update_option( MLSIMPORT_ACCOUNT_STATUS_OPTION, 'no_subscription', false ); |
| 66 |
} elseif ( 401 === $code ) { |
| 67 |
update_option( MLSIMPORT_ACCOUNT_STATUS_OPTION, 'invalid_credentials', false ); |
| 68 |
} |
| 69 |
// Step 3: any other outcome is not a verdict about the account; keep the |
| 70 |
// last known reason so the screens do not flip on a transient failure. |
| 71 |
} |
| 72 |
|
| 73 |
/** |
| 74 |
* The reason of the last failed token request. |
| 75 |
* |
| 76 |
* @return string 'no_subscription', 'invalid_credentials' or '' when unknown. |
| 77 |
*/ |
| 78 |
function mlsimport_account_status() { |
| 79 |
return (string) get_option( MLSIMPORT_ACCOUNT_STATUS_OPTION, '' ); |
| 80 |
} |
| 81 |
|
| 82 |
/** |
| 83 |
* The "not connected" sentence for the current account status, plain text. |
| 84 |
* |
| 85 |
* Used where only text can be shown (the Connections screen sign-in error |
| 86 |
* line, the AJAX 'message' field). The login hint names both supported account |
| 87 |
* identifiers. No markup, no link. |
| 88 |
* |
| 89 |
* @return string Translated sentence. |
| 90 |
*/ |
| 91 |
function mlsimport_account_not_connected_message() { |
| 92 |
// Step 1: no subscription -> say so, point at the portal. |
| 93 |
if ( 'no_subscription' === mlsimport_account_status() ) { |
| 94 |
return esc_html__( 'Your MLSImport account was found, but it has no active subscription. Please subscribe at mlsimport.com to connect the plugin.', 'mlsimport' ); |
| 95 |
} |
| 96 |
|
| 97 |
// Step 2: suggest either accepted account identifier for a failed login. |
| 98 |
return esc_html__( 'You are not connected to MLSImport - Please check your username or email and password.', 'mlsimport' ); |
| 99 |
} |
| 100 |
|
| 101 |
/** |
| 102 |
* The "not connected" warning box for the current account status. |
| 103 |
* |
| 104 |
* Every surface that used to print the generic "check your Username and |
| 105 |
* Password" box calls this instead, so a customer whose password is right |
| 106 |
* but who has no subscription is sent to the portal rather than to the |
| 107 |
* password field. The generic wording stays for every other reason. |
| 108 |
* |
| 109 |
* Every failure uses the same heading, explanatory paragraph and notice layout. |
| 110 |
* Subscription failures additionally include a prominent plans action. |
| 111 |
* The action's WordPress button class excludes it from the admin's text-link |
| 112 |
* color override, preserving its white label on the dark button background. |
| 113 |
* |
| 114 |
* @return string Escaped HTML of one <div class="mlsimport_warning">. |
| 115 |
*/ |
| 116 |
function mlsimport_account_not_connected_html() { |
| 117 |
// Step 1: share one layout; the account verdict changes only the content. |
| 118 |
$is_unsubscribed = 'no_subscription' === mlsimport_account_status(); |
| 119 |
$title = $is_unsubscribed |
| 120 |
? esc_html__( 'No active subscription', 'mlsimport' ) |
| 121 |
: esc_html__( 'Unable to connect', 'mlsimport' ); |
| 122 |
$html = '<div class="mlsimport_warning mlsimport-account-notice' |
| 123 |
. ( $is_unsubscribed ? ' mlsimport-account-subscription' : '' ) . '" role="alert">' |
| 124 |
. '<strong class="mlsimport-account-notice-title">' . $title . '</strong>' |
| 125 |
. '<p>' . mlsimport_account_not_connected_message() . '</p>'; |
| 126 |
|
| 127 |
// Step 2: offer a purchase action only after confirmed subscription failure. |
| 128 |
if ( $is_unsubscribed ) { |
| 129 |
$html .= '<a class="button mlsimport-account-subscription-action" href="' . esc_url( MLSIMPORT_ACCOUNT_SUBSCRIBE_URL ) . '" target="_blank" rel="noopener">' |
| 130 |
. esc_html__( 'View plans', 'mlsimport' ) |
| 131 |
. '</a>'; |
| 132 |
} |
| 133 |
return $html . '</div>'; |
| 134 |
} |
| 135 |
|