of ebook_options.php, within a plain *
. 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', '' ) ) );