| 1 |
<?php |
| 2 |
/** |
| 3 |
* Ebook Store - Mailchimp settings tab. |
| 4 |
* |
| 5 |
* PHP 7.4 compatible. |
| 6 |
* |
| 7 |
* @package EbookStore |
| 8 |
*/ |
| 9 |
|
| 10 |
if ( ! defined( 'ABSPATH' ) ) { |
| 11 |
exit; |
| 12 |
} |
| 13 |
|
| 14 |
settings_fields( 'ebook-settings-group-mailchimp' ); |
| 15 |
do_settings_sections( 'ebook-settings-group-mailchimp' ); |
| 16 |
|
| 17 |
$api_key = trim( (string) get_option( 'mailchimp_api_key', '' ) ); |
| 18 |
$selected_audience = (string) get_option( 'mailchimp_lists', '' ); |
| 19 |
$mailchimp_enabled = (string) get_option( 'mailchimp_integration_enabled', $selected_audience !== '' ? '1' : '' ); |
| 20 |
|
| 21 |
$has_api_key = ( $api_key !== '' ); |
| 22 |
$server_prefix = $has_api_key ? ebook_store_get_mailchimp_server_prefix( $api_key ) : ''; |
| 23 |
$key_looks_sane = ( $server_prefix !== '' ); |
| 24 |
|
| 25 |
// The audience lookup only runs when there is something to look up with. It is |
| 26 |
// the same call the tab has always made on render; no new remote request. |
| 27 |
$audiences = ( $has_api_key && $key_looks_sane ) ? ebook_store_get_mailchimp_lists() : false; |
| 28 |
|
| 29 |
$audience_list = is_array( $audiences ) ? $audiences : array(); |
| 30 |
$audience_count = count( $audience_list ); |
| 31 |
$lookup_failed = ( $has_api_key && $key_looks_sane && ! is_array( $audiences ) ); |
| 32 |
|
| 33 |
// Every option registered in this tab's option group has to be submitted by the |
| 34 |
// form. WordPress writes NULL over any registered option that is missing from |
| 35 |
// the POST, so a control that is not rendered in the current state must be |
| 36 |
// preserved by a hidden input or the saved value is silently wiped on save. |
| 37 |
$rendered_audience_input = false; |
| 38 |
$rendered_enabled_input = false; |
| 39 |
|
| 40 |
$general_tab_url = function_exists( 'ebook_store_get_settings_tab_url' ) |
| 41 |
? ebook_store_get_settings_tab_url( 'General' ) |
| 42 |
: admin_url( 'options-general.php?page=ebook_options.php' ); |
| 43 |
|
| 44 |
/** |
| 45 |
* Hero status card: three plain-language lines about where this setup stands. |
| 46 |
*/ |
| 47 |
$mailchimp_status_card = function () use ( $has_api_key, $key_looks_sane, $lookup_failed, $audience_count, $selected_audience, $mailchimp_enabled ) { |
| 48 |
if ( ! $has_api_key ) { |
| 49 |
$key_state = 'muted'; |
| 50 |
$key_detail = __( 'No API key saved yet. That is the first step.', 'ebook-store' ); |
| 51 |
} elseif ( ! $key_looks_sane ) { |
| 52 |
$key_state = 'error'; |
| 53 |
$key_detail = __( 'The saved key has no server suffix, so Mailchimp cannot be contacted.', 'ebook-store' ); |
| 54 |
} elseif ( $lookup_failed ) { |
| 55 |
$key_state = 'error'; |
| 56 |
$key_detail = __( 'Mailchimp refused this key. Copy it again from your Mailchimp account.', 'ebook-store' ); |
| 57 |
} else { |
| 58 |
$key_state = 'ok'; |
| 59 |
$key_detail = __( 'Saved and accepted by Mailchimp.', 'ebook-store' ); |
| 60 |
} |
| 61 |
|
| 62 |
if ( ! $has_api_key || ! $key_looks_sane || $lookup_failed ) { |
| 63 |
$list_state = 'muted'; |
| 64 |
$list_detail = __( 'Your audiences load once a working API key is saved.', 'ebook-store' ); |
| 65 |
} elseif ( $audience_count === 0 ) { |
| 66 |
$list_state = 'warn'; |
| 67 |
$list_detail = __( 'This Mailchimp account has no audiences yet. Create one in Mailchimp first.', 'ebook-store' ); |
| 68 |
} else { |
| 69 |
$list_state = 'ok'; |
| 70 |
$list_detail = sprintf( |
| 71 |
/* translators: %s: number of Mailchimp audiences found, already formatted for the locale. */ |
| 72 |
_n( '%s audience found in this account.', '%s audiences found in this account.', $audience_count, 'ebook-store' ), |
| 73 |
number_format_i18n( $audience_count ) |
| 74 |
); |
| 75 |
} |
| 76 |
|
| 77 |
if ( $mailchimp_enabled === '1' && $selected_audience !== '' ) { |
| 78 |
$sync_state = 'ok'; |
| 79 |
$sync_detail = __( 'Buyers are added to the chosen audience after they pay.', 'ebook-store' ); |
| 80 |
} elseif ( $mailchimp_enabled === '1' ) { |
| 81 |
$sync_state = 'warn'; |
| 82 |
$sync_detail = __( 'Switched on, but no audience is chosen, so nobody is being added.', 'ebook-store' ); |
| 83 |
} else { |
| 84 |
$sync_state = 'muted'; |
| 85 |
$sync_detail = __( 'Switched off. Buyers are not added to Mailchimp.', 'ebook-store' ); |
| 86 |
} |
| 87 |
?> |
| 88 |
<aside class="ebook-settings-status-card"> |
| 89 |
<h4><?php echo esc_html__( 'Current status', 'ebook-store' ); ?></h4> |
| 90 |
<ul class="ebook-settings-status-list"> |
| 91 |
<?php |
| 92 |
ebook_store_status_row( __( 'API key', 'ebook-store' ), $key_state, $key_detail ); |
| 93 |
ebook_store_status_row( __( 'Audiences', 'ebook-store' ), $list_state, $list_detail ); |
| 94 |
ebook_store_status_row( __( 'Adding buyers automatically', 'ebook-store' ), $sync_state, $sync_detail ); |
| 95 |
?> |
| 96 |
</ul> |
| 97 |
</aside> |
| 98 |
<?php |
| 99 |
}; |
| 100 |
|
| 101 |
ebook_store_render_hero( |
| 102 |
array( |
| 103 |
'eyebrow' => __( 'Mailchimp', 'ebook-store' ), |
| 104 |
'title' => __( 'Add your buyers to a Mailchimp audience', 'ebook-store' ), |
| 105 |
'intro' => __( 'When someone pays for a book, Ebook Store can send their email address to one of your Mailchimp audiences, so you can keep in touch with your readers. Setting this up takes two steps: save your API key, then pick the audience.', 'ebook-store' ), |
| 106 |
'aside' => $mailchimp_status_card, |
| 107 |
) |
| 108 |
); |
| 109 |
|
| 110 |
ebook_store_pro_banner( __( 'Mailchimp', 'ebook-store' ) ); |
| 111 |
|
| 112 |
/* --------------------------------------------------------------------------- |
| 113 |
* Step 1 - the API key. Always shown. |
| 114 |
* ------------------------------------------------------------------------- */ |
| 115 |
ebook_store_section_open( |
| 116 |
array( |
| 117 |
'eyebrow' => __( 'Step 1', 'ebook-store' ), |
| 118 |
'title' => __( 'Connect your Mailchimp account', 'ebook-store' ), |
| 119 |
'intro' => __( 'The API key is the password that lets Ebook Store talk to your Mailchimp account. Use a key from the same Mailchimp account that holds the audience you want your buyers to join.', 'ebook-store' ), |
| 120 |
'id' => 'ebook-mailchimp-connection', |
| 121 |
'pro' => true, |
| 122 |
) |
| 123 |
); |
| 124 |
|
| 125 |
ebook_store_steps( |
| 126 |
__( 'Where to find your API key', 'ebook-store' ), |
| 127 |
array( |
| 128 |
__( 'Sign in to Mailchimp and open your account menu, then Account & billing.', 'ebook-store' ), |
| 129 |
__( 'Go to Extras, then API keys.', 'ebook-store' ), |
| 130 |
__( 'Choose Create A Key, give it a name such as “Ebook Store”, and copy the key it shows you.', 'ebook-store' ), |
| 131 |
__( 'Paste the whole key into the box below and press Save Changes.', 'ebook-store' ), |
| 132 |
) |
| 133 |
); |
| 134 |
|
| 135 |
ebook_store_field( |
| 136 |
array( |
| 137 |
'type' => 'text', |
| 138 |
'name' => 'mailchimp_api_key', |
| 139 |
'id' => 'mailchimp_api_key', |
| 140 |
'label' => __( 'Mailchimp API key', 'ebook-store' ), |
| 141 |
'value' => $api_key, |
| 142 |
'placeholder' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us21', |
| 143 |
'mono' => true, |
| 144 |
'wide' => true, |
| 145 |
'pro' => true, |
| 146 |
'help' => wp_kses_post( |
| 147 |
sprintf( |
| 148 |
/* translators: 1: example server suffix, e.g. -us21; 2: the letters "us" followed by two digits. */ |
| 149 |
__( 'Paste the key exactly as Mailchimp shows it, including the ending such as %1$s. That ending (%2$s) tells Ebook Store which Mailchimp server your account lives on, so a key without it will not work. If the key is wrong or was deleted in Mailchimp, buyers simply are not added to your audience; nothing else on your shop breaks.', 'ebook-store' ), |
| 150 |
'<code>-us21</code>', |
| 151 |
'<code>-usXX</code>' |
| 152 |
) |
| 153 |
), |
| 154 |
) |
| 155 |
); |
| 156 |
|
| 157 |
if ( ! $has_api_key ) { |
| 158 |
ebook_store_callout( |
| 159 |
__( 'Paste your API key above and press Save Changes. This page will then reload with the list of audiences from your Mailchimp account, and you can choose which one your buyers should join.', 'ebook-store' ), |
| 160 |
'info', |
| 161 |
__( 'Save your API key first:', 'ebook-store' ) |
| 162 |
); |
| 163 |
} elseif ( ! $key_looks_sane ) { |
| 164 |
ebook_store_callout( |
| 165 |
wp_kses_post( |
| 166 |
sprintf( |
| 167 |
/* translators: %s: example server suffix, e.g. -us21. */ |
| 168 |
__( 'The key you saved has no server ending such as %s, so it is probably incomplete. Copy the whole key from Mailchimp again and save it once more.', 'ebook-store' ), |
| 169 |
'<code>-us21</code>' |
| 170 |
) |
| 171 |
), |
| 172 |
'warning', |
| 173 |
__( 'This key looks incomplete:', 'ebook-store' ) |
| 174 |
); |
| 175 |
} elseif ( $lookup_failed ) { |
| 176 |
ebook_store_callout( |
| 177 |
__( 'Your key was saved, but Mailchimp did not accept it when we asked for your audiences. The key may have been deleted or revoked in Mailchimp, or your server may be blocked from reaching Mailchimp. Create a fresh key in Mailchimp and save it here.', 'ebook-store' ), |
| 178 |
'warning', |
| 179 |
__( 'Mailchimp did not answer with your audiences:', 'ebook-store' ) |
| 180 |
); |
| 181 |
} |
| 182 |
|
| 183 |
if ( ! ebook_store_has_pro_license() ) { |
| 184 |
ebook_store_callout( |
| 185 |
wp_kses_post( |
| 186 |
sprintf( |
| 187 |
/* translators: %s: link to the General settings tab. */ |
| 188 |
__( 'These boxes stay locked until a Pro license key is saved on the <a href="%s">General tab</a>.', 'ebook-store' ), |
| 189 |
esc_url( $general_tab_url ) |
| 190 |
) |
| 191 |
), |
| 192 |
'soft', |
| 193 |
__( 'Pro feature:', 'ebook-store' ) |
| 194 |
); |
| 195 |
} |
| 196 |
|
| 197 |
ebook_store_section_close(); |
| 198 |
|
| 199 |
/* --------------------------------------------------------------------------- |
| 200 |
* Step 2 - audience and switch. Only rendered once a key exists, so nobody is |
| 201 |
* shown an empty dropdown that looks like "your account has no audiences". |
| 202 |
* ------------------------------------------------------------------------- */ |
| 203 |
if ( $has_api_key ) { |
| 204 |
ebook_store_section_open( |
| 205 |
array( |
| 206 |
'eyebrow' => __( 'Step 2', 'ebook-store' ), |
| 207 |
'title' => __( 'Choose the audience and switch it on', 'ebook-store' ), |
| 208 |
'intro' => __( 'Pick the Mailchimp audience your buyers should be added to, then turn the automatic adding on. Both are needed: an audience on its own does nothing until the switch is on.', 'ebook-store' ), |
| 209 |
'id' => 'ebook-mailchimp-audience', |
| 210 |
'pro' => true, |
| 211 |
) |
| 212 |
); |
| 213 |
|
| 214 |
if ( $audience_count > 0 ) { |
| 215 |
$audience_options = array( |
| 216 |
'' => __( 'Do not add buyers to any audience', 'ebook-store' ), |
| 217 |
); |
| 218 |
|
| 219 |
foreach ( $audience_list as $audience ) { |
| 220 |
$audience_id = isset( $audience->id ) ? (string) $audience->id : ''; |
| 221 |
if ( $audience_id === '' ) { |
| 222 |
continue; |
| 223 |
} |
| 224 |
$audience_name = ( isset( $audience->name ) && $audience->name !== '' ) |
| 225 |
? (string) $audience->name |
| 226 |
: $audience_id; |
| 227 |
|
| 228 |
$audience_options[ $audience_id ] = $audience_name; |
| 229 |
} |
| 230 |
|
| 231 |
// A previously chosen audience that no longer exists in Mailchimp would |
| 232 |
// otherwise disappear silently and look like "nothing was selected". |
| 233 |
$missing_selection = ( $selected_audience !== '' && ! isset( $audience_options[ $selected_audience ] ) ); |
| 234 |
if ( $missing_selection ) { |
| 235 |
$audience_options[ $selected_audience ] = sprintf( |
| 236 |
/* translators: %s: the saved Mailchimp audience id. */ |
| 237 |
__( 'Saved audience %s (no longer in this account)', 'ebook-store' ), |
| 238 |
$selected_audience |
| 239 |
); |
| 240 |
} |
| 241 |
|
| 242 |
ebook_store_field( |
| 243 |
array( |
| 244 |
'type' => 'select', |
| 245 |
'name' => 'mailchimp_lists', |
| 246 |
'id' => 'mailchimp_lists', |
| 247 |
'label' => __( 'Audience buyers are added to', 'ebook-store' ), |
| 248 |
'value' => $selected_audience, |
| 249 |
'options' => $audience_options, |
| 250 |
'wide' => true, |
| 251 |
'pro' => true, |
| 252 |
'help' => __( 'This list comes straight from your Mailchimp account. If you have just created a new audience in Mailchimp, save this page once and it will appear here.', 'ebook-store' ), |
| 253 |
'note' => $missing_selection |
| 254 |
? __( 'The audience saved here was not returned by Mailchimp. It may have been deleted or renamed. Choose one of your current audiences instead.', 'ebook-store' ) |
| 255 |
: '', |
| 256 |
'note_tone' => 'warning', |
| 257 |
) |
| 258 |
); |
| 259 |
|
| 260 |
$rendered_audience_input = true; |
| 261 |
|
| 262 |
ebook_store_toggle_grid_open(); |
| 263 |
ebook_store_toggle( |
| 264 |
array( |
| 265 |
'name' => 'mailchimp_integration_enabled', |
| 266 |
'id' => 'mailchimp_integration_enabled', |
| 267 |
'title' => __( 'Add buyers to Mailchimp after they pay', 'ebook-store' ), |
| 268 |
'description' => __( 'Only paid orders are sent. Someone who is already in the audience is updated rather than added twice.', 'ebook-store' ), |
| 269 |
'checked' => $mailchimp_enabled, |
| 270 |
'pro' => true, |
| 271 |
) |
| 272 |
); |
| 273 |
ebook_store_toggle_grid_close(); |
| 274 |
|
| 275 |
if ( $mailchimp_enabled === '1' && $selected_audience === '' ) { |
| 276 |
ebook_store_callout( |
| 277 |
__( 'Adding buyers is switched on, but no audience is chosen, so nothing is sent to Mailchimp. Pick an audience above and save.', 'ebook-store' ), |
| 278 |
'warning', |
| 279 |
__( 'One step missing:', 'ebook-store' ) |
| 280 |
); |
| 281 |
} |
| 282 |
} elseif ( $key_looks_sane && ! $lookup_failed ) { |
| 283 |
ebook_store_callout( |
| 284 |
__( 'Ebook Store reached your Mailchimp account, but there are no audiences in it yet. Create an audience in Mailchimp, then save this page again to pick it here.', 'ebook-store' ), |
| 285 |
'warning', |
| 286 |
__( 'No audiences in this account:', 'ebook-store' ) |
| 287 |
); |
| 288 |
|
| 289 |
ebook_store_toggle_grid_open(); |
| 290 |
ebook_store_toggle( |
| 291 |
array( |
| 292 |
'name' => 'mailchimp_integration_enabled', |
| 293 |
'id' => 'mailchimp_integration_enabled', |
| 294 |
'title' => __( 'Add buyers to Mailchimp after they pay', 'ebook-store' ), |
| 295 |
'description' => __( 'Nothing is sent until an audience exists and is chosen above.', 'ebook-store' ), |
| 296 |
'checked' => $mailchimp_enabled, |
| 297 |
'pro' => true, |
| 298 |
) |
| 299 |
); |
| 300 |
ebook_store_toggle_grid_close(); |
| 301 |
} else { |
| 302 |
ebook_store_callout( |
| 303 |
__( 'Your audiences cannot be loaded until the API key above works, so there is nothing to choose yet. You can still switch the feature off here.', 'ebook-store' ), |
| 304 |
'soft', |
| 305 |
__( 'Waiting for a working API key:', 'ebook-store' ) |
| 306 |
); |
| 307 |
|
| 308 |
ebook_store_toggle_grid_open(); |
| 309 |
ebook_store_toggle( |
| 310 |
array( |
| 311 |
'name' => 'mailchimp_integration_enabled', |
| 312 |
'id' => 'mailchimp_integration_enabled', |
| 313 |
'title' => __( 'Add buyers to Mailchimp after they pay', 'ebook-store' ), |
| 314 |
'description' => __( 'Leave this off while the API key is not working.', 'ebook-store' ), |
| 315 |
'checked' => $mailchimp_enabled, |
| 316 |
'pro' => true, |
| 317 |
) |
| 318 |
); |
| 319 |
ebook_store_toggle_grid_close(); |
| 320 |
} |
| 321 |
|
| 322 |
// All three branches above render the enable toggle, and that helper emits |
| 323 |
// its own paired hidden input for the unchecked state. |
| 324 |
$rendered_enabled_input = true; |
| 325 |
|
| 326 |
ebook_store_section_close(); |
| 327 |
} |
| 328 |
|
| 329 |
/* --------------------------------------------------------------------------- |
| 330 |
* Keep the settings this tab owns but did not draw a control for. |
| 331 |
* |
| 332 |
* Without a working API key there is no audience dropdown and no toggle; with a |
| 333 |
* key that returns no audiences there is no dropdown. Those options still |
| 334 |
* belong to this option group, so they have to be posted back unchanged or |
| 335 |
* WordPress blanks them, quietly switching the integration off and losing the |
| 336 |
* chosen audience. |
| 337 |
* ------------------------------------------------------------------------- */ |
| 338 |
if ( ! $rendered_audience_input || ! $rendered_enabled_input ) { |
| 339 |
?> |
| 340 |
<div class="ebook-settings-preserved-values" hidden="hidden"> |
| 341 |
<?php if ( ! $rendered_audience_input ) { ?> |
| 342 |
<input type="hidden" name="mailchimp_lists" value="<?php echo esc_attr( $selected_audience ); ?>" /> |
| 343 |
<?php } ?> |
| 344 |
<?php if ( ! $rendered_enabled_input ) { ?> |
| 345 |
<input type="hidden" name="mailchimp_integration_enabled" value="<?php echo esc_attr( $mailchimp_enabled === '1' ? '1' : '' ); ?>" /> |
| 346 |
<?php } ?> |
| 347 |
</div> |
| 348 |
<?php |
| 349 |
} |
| 350 |
|
| 351 |
/* --------------------------------------------------------------------------- |
| 352 |
* What to expect, in plain words. |
| 353 |
* ------------------------------------------------------------------------- */ |
| 354 |
ebook_store_section_open( |
| 355 |
array( |
| 356 |
'eyebrow' => __( 'Good to know', 'ebook-store' ), |
| 357 |
'title' => __( 'What happens after a sale', 'ebook-store' ), |
| 358 |
'intro' => __( 'A short summary of how Ebook Store behaves once this is connected.', 'ebook-store' ), |
| 359 |
'id' => 'ebook-mailchimp-behaviour', |
| 360 |
) |
| 361 |
); |
| 362 |
?> |
| 363 |
<ul class="ebook-settings-checklist"> |
| 364 |
<li><?php echo esc_html__( 'Buyers are only sent to Mailchimp when the switch is on and an audience is chosen.', 'ebook-store' ); ?></li> |
| 365 |
<li><?php echo esc_html__( 'Only the email address from a completed, paid order is sent.', 'ebook-store' ); ?></li> |
| 366 |
<li><?php echo esc_html__( 'Someone who is already in the audience is updated, not added a second time.', 'ebook-store' ); ?></li> |
| 367 |
<li><?php echo esc_html__( 'If Mailchimp is unreachable, the sale and the download still go through as normal.', 'ebook-store' ); ?></li> |
| 368 |
<li><?php echo esc_html__( 'No special hosting option such as allow_url_fopen is needed for this to work.', 'ebook-store' ); ?></li> |
| 369 |
</ul> |
| 370 |
<?php |
| 371 |
ebook_store_callout( |
| 372 |
__( 'Mailchimp expects people to have agreed to hear from you. Say on your checkout or book page that buyers will be added to your mailing list.', 'ebook-store' ), |
| 373 |
'soft', |
| 374 |
__( 'Consent:', 'ebook-store' ) |
| 375 |
); |
| 376 |
ebook_store_section_close(); |
| 377 |
|