. This file must therefore never emit table
* markup; it prints blocks through the shared settings-UI helpers.
*
* PHP 7.4 compatible.
*
* @package EbookStore
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
settings_fields( 'ebook-settings-group-general' );
do_settings_sections( 'ebook-settings-group-general' );
/*
* ---------------------------------------------------------------------------
* Saved values.
* ---------------------------------------------------------------------------
* $op is the QSWPOptions defaults object created by ebook_options.php. It is
* read defensively so this tab still renders if it is ever included on its own.
*/
$default_locale = isset( $op->ebook_store_locale ) ? (string) $op->ebook_store_locale : '';
$default_expiration = isset( $op->link_expiration ) ? (string) $op->link_expiration : '1 year';
$default_email_flag = isset( $op->email_delivery ) ? (string) $op->email_delivery : '';
$default_download_cap = isset( $op->downloads_limit ) ? (string) $op->downloads_limit : '5';
$default_vat_percent = isset( $op->vat_percent ) ? (string) $op->vat_percent : '0';
$default_form_scale = isset( $op->form_scale ) ? (string) $op->form_scale : '1';
$license_key = function_exists( 'ebook_store_get_license_key' )
? ebook_store_get_license_key()
: trim( (string) get_option( 'ebook_store_license_key', '' ) );
$license_summary = function_exists( 'ebook_store_get_license_summary' )
? ebook_store_get_license_summary()
: array();
$license_summary = wp_parse_args(
is_array( $license_summary ) ? $license_summary : array(),
array(
'label' => __( 'Free version', 'ebook-store' ),
'tone' => 'muted',
'message' => __( 'Enter your license key to unlock the Pro features.', 'ebook-store' ),
)
);
$selected_locale = (string) get_option( 'ebook_store_locale', $default_locale );
$link_expiration = trim( (string) get_option( 'link_expiration', $default_expiration ) );
$link_expiration = $link_expiration !== '' ? $link_expiration : '1 year';
$downloads_limit = function_exists( 'ebook_store_get_download_limit' )
? (string) ebook_store_get_download_limit()
: (string) get_option( 'downloads_limit', $default_download_cap );
$email_delivery_enabled = (string) get_option( 'email_delivery', $default_email_flag );
$attach_files_enabled = (string) get_option( 'attach_files', '' );
$vat_percent = function_exists( 'ebook_store_get_vat_percent' )
? (string) ebook_store_get_vat_percent()
: (string) get_option( 'vat_percent', $default_vat_percent );
$current_currency = function_exists( 'ebook_store_get_store_currency' )
? ebook_store_get_store_currency()
: (string) get_option( 'paypal_currency', 'USD' );
$form_scale = trim( (string) get_option( 'form_scale', $default_form_scale ) );
$form_scale = $form_scale !== '' ? $form_scale : '1';
$default_form_template = function_exists( 'ebook_store_resolve_form_template' )
? ebook_store_resolve_form_template( (string) get_option( 'ebook_store_form_template', '2026' ) )
: (string) get_option( 'ebook_store_form_template', '2026' );
/*
* ---------------------------------------------------------------------------
* Option lists.
* ---------------------------------------------------------------------------
*/
$form_templates = function_exists( 'ebook_store_get_form_templates' ) ? ebook_store_get_form_templates() : array();
$template_choices = array();
foreach ( (array) $form_templates as $template_key => $template_config ) {
$template_choices[ $template_key ] = is_array( $template_config ) && isset( $template_config['label'] )
? $template_config['label']
: (string) $template_key;
}
if ( empty( $template_choices ) ) {
$template_choices = array( '2026' => __( '2026', 'ebook-store' ) );
}
// The full label list is used here (not only installed language packs) so an
// already-saved language never disappears from the list after an update.
if ( ! isset( $languages ) || ! is_array( $languages ) || empty( $languages ) ) {
$languages = function_exists( 'ebook_store_get_active_languages' )
? ebook_store_get_active_languages()
: array( 0 => __( 'Default', 'ebook-store' ) );
}
$common_currencies = array(
'USD' => __( 'US Dollar', 'ebook-store' ),
'EUR' => __( 'Euro', 'ebook-store' ),
'GBP' => __( 'British Pound', 'ebook-store' ),
'AUD' => __( 'Australian Dollar', 'ebook-store' ),
'CAD' => __( 'Canadian Dollar', 'ebook-store' ),
'JPY' => __( 'Japanese Yen', 'ebook-store' ),
'NZD' => __( 'New Zealand Dollar', 'ebook-store' ),
'CHF' => __( 'Swiss Franc', 'ebook-store' ),
'HKD' => __( 'Hong Kong Dollar', 'ebook-store' ),
'SGD' => __( 'Singapore Dollar', 'ebook-store' ),
'SEK' => __( 'Swedish Krona', 'ebook-store' ),
'DKK' => __( 'Danish Krone', 'ebook-store' ),
'NOK' => __( 'Norwegian Krone', 'ebook-store' ),
'MXN' => __( 'Mexican Peso', 'ebook-store' ),
'PLN' => __( 'Polish Zloty', 'ebook-store' ),
'BRL' => __( 'Brazilian Real', 'ebook-store' ),
);
$additional_currencies = array(
'CZK' => __( 'Czech Koruna', 'ebook-store' ),
'ILS' => __( 'Israeli New Sheqel', 'ebook-store' ),
'HUF' => __( 'Hungarian Forint', 'ebook-store' ),
'MYR' => __( 'Malaysian Ringgit', 'ebook-store' ),
'PHP' => __( 'Philippine Peso', 'ebook-store' ),
'TWD' => __( 'Taiwan New Dollar', 'ebook-store' ),
'THB' => __( 'Thai Baht', 'ebook-store' ),
'INR' => __( 'Indian Rupee', 'ebook-store' ),
'TRY' => __( 'Turkish Lira', 'ebook-store' ),
'ZAR' => __( 'South African Rand', 'ebook-store' ),
'NGN' => __( 'Nigerian Naira', 'ebook-store' ),
'RUB' => __( 'Russian Ruble', 'ebook-store' ),
'RON' => __( 'Romanian Leu', 'ebook-store' ),
);
if ( $current_currency !== '' && ! isset( $common_currencies[ $current_currency ] ) && ! isset( $additional_currencies[ $current_currency ] ) ) {
$additional_currencies[ $current_currency ] = $current_currency;
}
$currency_groups = array();
foreach ( $common_currencies as $code => $label ) {
/* translators: 1: currency name, 2: three-letter currency code. */
$currency_groups[ __( 'Common currencies', 'ebook-store' ) ][ $code ] = sprintf( __( '%1$s (%2$s)', 'ebook-store' ), $label, $code );
}
foreach ( $additional_currencies as $code => $label ) {
/* translators: 1: currency name, 2: three-letter currency code. */
$currency_groups[ __( 'Additional currencies', 'ebook-store' ) ][ $code ] = sprintf( __( '%1$s (%2$s)', 'ebook-store' ), $label, $code );
}
/*
* ---------------------------------------------------------------------------
* Live preview of the download-link expiry.
* ---------------------------------------------------------------------------
* The sanitiser accepts any non-empty text, and functions.php feeds the value
* to strtotime( '+' . $value ). If that fails the comparison is made against
* false, so every download link is treated as already expired. Showing the
* resulting date here is the only way a shop owner can spot a typo.
*/
$expiration_expression = ltrim( $link_expiration, '+ ' );
$expiration_timestamp = $expiration_expression !== '' ? strtotime( '+' . $expiration_expression, time() ) : false;
$expiration_is_valid = ( is_int( $expiration_timestamp ) && $expiration_timestamp > time() );
if ( $expiration_is_valid ) {
$expiration_note = sprintf(
/* translators: %s: formatted date, e.g. 19 July 2027. */
__( 'A link bought today would keep working until %s.', 'ebook-store' ),
'' . esc_html( date_i18n( get_option( 'date_format' ), $expiration_timestamp ) ) . ''
);
$expiration_note_tone = 'success';
} else {
$expiration_note = sprintf(
/* translators: %s: the value the shop owner typed, shown in code style. */
__( 'WordPress cannot read %s as a length of time, so every download link would be treated as already expired and buyers would see an error instead of their file. Use wording such as 1 year, 6 months, 30 days or 48 hours.', 'ebook-store' ),
'' . esc_html( $link_expiration ) . ''
);
$expiration_note_tone = 'error';
}
/*
* ---------------------------------------------------------------------------
* Cross-tab links.
* ---------------------------------------------------------------------------
*/
if ( function_exists( 'ebook_store_get_settings_tab_url' ) ) {
$templates_tab_url = ebook_store_get_settings_tab_url( 'Templates' );
$paypal_tab_url = ebook_store_get_settings_tab_url( 'PayPal' );
} else {
$templates_tab_url = admin_url( 'options-general.php?page=ebook_options.php&tab=Templates' );
$paypal_tab_url = admin_url( 'options-general.php?page=ebook_options.php&tab=PayPal' );
}
$license_recheck_url = wp_nonce_url(
admin_url( 'admin-post.php?action=ebook_store_license_recheck' ),
'ebook_store_license_recheck'
);
/*
* ---------------------------------------------------------------------------
* Hero.
* ---------------------------------------------------------------------------
*/
$currency_label = isset( $common_currencies[ $current_currency ] )
? $common_currencies[ $current_currency ]
: ( isset( $additional_currencies[ $current_currency ] ) ? $additional_currencies[ $current_currency ] : $current_currency );
$hero_aside = function () use ( $license_summary, $email_delivery_enabled, $attach_files_enabled, $expiration_is_valid, $link_expiration, $current_currency, $currency_label, $vat_percent ) {
$vat_number = (float) $vat_percent;
?>
__( 'General settings', 'ebook-store' ),
'title' => __( 'The basics every store needs', 'ebook-store' ),
'intro' => __( 'Your license, the language buyers see, how long download links last, what happens by email after a sale, and the currency and tax you charge.', 'ebook-store' ),
'aside' => $hero_aside,
)
);
ebook_store_pro_banner( __( 'The store language selector', 'ebook-store' ) );
/*
* ---------------------------------------------------------------------------
* Section: license.
* ---------------------------------------------------------------------------
*/
ebook_store_section_open(
array(
'eyebrow' => __( 'Setup', 'ebook-store' ),
'title' => __( 'License & Pro features', 'ebook-store' ),
'intro' => __( 'A license unlocks the Pro features and keeps them working after updates. The free features on this page work either way.', 'ebook-store' ),
'id' => 'ebook-settings-license',
)
);
ebook_store_split_open();
?>
%2$s
',
esc_attr( $checked_tone ),
esc_html( $checked_text )
);
}
ebook_store_split_close();
ebook_store_section_close();
/*
* ---------------------------------------------------------------------------
* Card 2: Storefront & delivery.
* ---------------------------------------------------------------------------
* Three former sections (language / download access / order emails) merged into
* one dense card, kept navigable by three labelled bands so no row is half-empty
* and the wayfinding a shop owner scans for ("how long do links last?") survives.
*/
ebook_store_section_open(
array(
'eyebrow' => __( 'Storefront', 'ebook-store' ),
'title' => __( 'Storefront & delivery', 'ebook-store' ),
'intro' => __( 'The language buyers see, how their download links behave, and what they receive by email after paying.', 'ebook-store' ),
'id' => 'ebook-settings-storefront',
)
);
ebook_store_subhead( __( 'Storefront', 'ebook-store' ) );
ebook_store_grid_open( 2 );
ebook_store_field(
array(
'type' => 'select',
'name' => 'ebook_store_locale',
'id' => 'ebook_store_locale',
'label' => __( 'Store display language', 'ebook-store' ),
'value' => $selected_locale,
'options' => $languages,
'pro' => true,
'help' => __( 'Leave on Default so the store follows your WordPress language. Pick a specific language only to pin the store while the rest of the site differs.', 'ebook-store' ),
'hint' => __( 'Default means the store speaks whatever language WordPress itself is set to, which is what almost every shop wants. This does not translate your own ebook titles or descriptions.', 'ebook-store' ),
)
);
ebook_store_field(
array(
'type' => 'select',
'name' => 'ebook_store_form_template',
'id' => 'ebook_store_form_template',
'label' => __( 'Default order form design', 'ebook-store' ),
'value' => $default_form_template,
'options' => $template_choices,
'help' => sprintf(
/* translators: %s: link to the Templates settings tab. */
__( 'Used by new ebooks and by shortcodes with no design chosen. 2026 is recommended. Wording lives in the %s.', 'ebook-store' ),
'' . esc_html__( 'Templates tab', 'ebook-store' ) . ''
),
'hint' => __( 'Existing ebooks keep whatever design you already picked for them; this only sets the starting point for new titles.', 'ebook-store' ),
)
);
ebook_store_grid_close();
ebook_store_subhead( __( 'Downloads', 'ebook-store' ) );
ebook_store_grid_open( 2 );
ebook_store_field(
array(
'type' => 'text',
'name' => 'link_expiration',
'id' => 'link_expiration',
'label' => __( 'Download links stop working after', 'ebook-store' ),
'value' => $link_expiration,
'placeholder' => __( '1 year', 'ebook-store' ),
'help' => __( 'Counted from the order. Write a plain length of time: 1 year, 6 months, 30 days, 48 hours. Empty saves 1 year.', 'ebook-store' ),
'note' => $expiration_note,
'note_tone' => $expiration_note_tone,
)
);
ebook_store_field(
array(
'type' => 'number',
'name' => 'downloads_limit',
'id' => 'downloads_limit',
'label' => __( 'Downloads allowed per order', 'ebook-store' ),
'value' => $downloads_limit,
'placeholder' => '5',
'min' => 1,
'step' => 1,
'help' => __( 'Retries for an interrupted download or a second device. Five is a sensible start; once used up the link stops working even before it expires. Empty saves 5.', 'ebook-store' ),
)
);
ebook_store_grid_close();
ebook_store_subhead( __( 'After payment', 'ebook-store' ) );
ebook_store_toggle_grid_open();
ebook_store_toggle(
array(
'name' => 'email_delivery',
'id' => 'email_delivery',
'title' => __( 'Email the order confirmation and download links', 'ebook-store' ),
'description' => __( 'Strongly recommended. Without it, a buyer who closes the thank-you page has no way back to their files.', 'ebook-store' ),
'checked' => $email_delivery_enabled,
)
);
ebook_store_toggle(
array(
'name' => 'attach_files',
'id' => 'attach_files',
'title' => __( 'Also attach the ebook file to that email', 'ebook-store' ),
'description' => __( 'Convenient for small files. Large attachments make emails slow, and many mail providers reject anything over a few megabytes.', 'ebook-store' ),
'checked' => $attach_files_enabled,
)
);
ebook_store_toggle_grid_close();
ebook_store_callout(
__( 'WordPress sends mail through your web server by default, and shop emails sent that way often land in spam. Installing an SMTP plugin and sending through a real mailbox is the single best way to make sure buyers actually receive their downloads.', 'ebook-store' ),
'warning',
__( 'Deliverability:', 'ebook-store' )
);
ebook_store_callout(
sprintf(
/* translators: %s: link to the Templates settings tab. */
__( 'The subject line and wording of this email are edited in the %s. Attachments only go out when the confirmation email above is switched on.', 'ebook-store' ),
'' . esc_html__( 'Templates tab', 'ebook-store' ) . ''
),
'info',
__( 'Where the wording lives:', 'ebook-store' )
);
ebook_store_advanced_open();
ebook_store_field(
array(
'type' => 'number',
'name' => 'form_scale',
'id' => 'form_scale',
'label' => __( 'Order form size', 'ebook-store' ),
'value' => $form_scale,
'placeholder' => '1',
'min' => '0.1',
'step' => '0.05',
'help' => __( 'Scales the whole order form on your site. 1 is normal, 0.8 smaller, 1.2 larger.', 'ebook-store' ),
'hint' => __( 'Useful when the form does not quite fit your theme\'s column width. Leave it at 1 unless you have a sizing problem.', 'ebook-store' ),
)
);
ebook_store_advanced_close();
ebook_store_section_close();
/*
* ---------------------------------------------------------------------------
* Section: tax and currency.
* ---------------------------------------------------------------------------
*/
ebook_store_section_open(
array(
'eyebrow' => __( 'Pricing', 'ebook-store' ),
'title' => __( 'Tax & currency', 'ebook-store' ),
'intro' => __( 'The money side: which currency buyers are charged in, and whether tax is added on top of your prices.', 'ebook-store' ),
'id' => 'ebook-settings-pricing',
)
);
ebook_store_grid_open( 2 );
ebook_store_field(
array(
'type' => 'number',
'name' => 'vat_percent',
'id' => 'vat_percent',
'label' => __( 'Tax added at checkout (%)', 'ebook-store' ),
'value' => $vat_percent,
'placeholder' => '0',
'min' => 0,
'step' => '0.01',
'help' => __( 'Enter 0 if prices already include tax or you charge none. Enter 20 to add twenty percent at checkout. One rate only — no per-country rates. Negative values save as 0.', 'ebook-store' ),
)
);
ebook_store_field(
array(
'type' => 'select',
'name' => 'paypal_currency',
'id' => 'paypal_currency',
'label' => __( 'Store currency', 'ebook-store' ),
'value' => $current_currency,
'optgroups' => $currency_groups,
'help' => __( 'Every price is charged in this currency, whichever payment method the buyer uses. A currency your provider rejects makes checkout fail, so check before switching.', 'ebook-store' ),
'hint' => sprintf(
/* translators: 1: legacy option name in code style, 2: link to the PayPal settings tab. */
__( 'For historical reasons this is stored under the name %1$s, but it is not PayPal-only. The payment methods themselves are set up in %2$s and the other gateway tabs.', 'ebook-store' ),
'paypal_currency',
'' . esc_html__( 'the PayPal tab', 'ebook-store' ) . ''
),
)
);
ebook_store_grid_close();
$sample_net = 10.0;
$vat_number = max( 0, (float) $vat_percent );
$sample_gross = $sample_net * ( 1 + $vat_number / 100 );
if ( $vat_number > 0 ) {
$pricing_preview = sprintf(
/* translators: 1: currency code, 2: net price, 3: gross price, 4: tax percentage. */
__( 'Example: a %1$s %2$s book is charged as %1$s %3$s at checkout (%2$s + %4$s%% tax).', 'ebook-store' ),
esc_html( $current_currency ),
esc_html( number_format( $sample_net, 2 ) ),
esc_html( number_format( $sample_gross, 2 ) ),
esc_html( rtrim( rtrim( number_format( $vat_number, 2, '.', '' ), '0' ), '.' ) )
);
} else {
$pricing_preview = sprintf(
/* translators: 1: currency code, 2: price. */
__( 'Example: a %1$s %2$s book is charged as %1$s %2$s at checkout, with no tax added.', 'ebook-store' ),
esc_html( $current_currency ),
esc_html( number_format( $sample_net, 2 ) )
);
}
printf(
'
%s
',
$pricing_preview // Already escaped piecewise above.
);
ebook_store_section_close();
/*
* ---------------------------------------------------------------------------
* Internal flags carried through the save.
* ---------------------------------------------------------------------------
* ebookstorewpsc is not a shop setting: it records that the WP Super Cache
* notice was dismissed. It is registered in this option group, and
* wp-admin/options.php writes null over every registered option of the group
* that the submitted form does not contain, so the dismissal would come back
* on every save. Its current value is resubmitted unchanged instead.
*/
printf(
'',
esc_attr( (string) get_option( 'ebookstorewpsc', '' ) )
);