PluginProbe
Ebook Store / 6.25
Ebook Store v6.25
6.25 6.24 6.23 6.22 6.21 6.20 trunk
ebook-store / ebook_options.php

ebook_options.php in Ebook Store 6.25, at ebook_options.php

995 lines 43.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // create custom plugin settings menu
3
4 add_action('admin_menu', 'ebook_create_menu');
5
6 function ebook_create_menu() {
7
8 //create new top-level menu
9 add_options_page('Ebook Store', 'Ebook Store', 'manage_options', 'ebook_options.php', 'ebook_settings_page');
10
11 }
12
13 if ( is_admin() ){ // admin actions
14 add_action( 'admin_init', 'register_ebook_store_settings' );
15 } else {
16 // non-admin enqueues, actions, and filters
17 }
18
19 /*
20 * The authoritative Pro check lives in includes/ebook-store-license.php, which
21 * the main plugin file loads before this one. Declaring it again here was a
22 * "Cannot redeclare ebook_store_has_pro_license()" fatal on every request, and
23 * the copy below was also the wrong test: a saved key string is not the same as
24 * a valid licence. This fallback only ever runs if the licence module is absent.
25 */
26 if ( ! function_exists( 'ebook_store_has_pro_license' ) ) {
27 function ebook_store_has_pro_license() {
28 return trim( (string) get_option( 'ebook_store_license_key', '' ) ) !== '';
29 }
30 }
31
32 /**
33 * Options that hold more than a single line of plain text.
34 *
35 * sanitize_text_field() strips newlines, which silently flattened the
36 * multi-line watermark template, and it mangles URLs.
37 *
38 * @return array option name => 'textarea'|'url'
39 */
40 function ebook_store_get_pro_locked_option_types() {
41 return apply_filters(
42 'ebook_store_pro_locked_option_types',
43 array(
44 'buyer_info_text' => 'textarea',
45 'ebook_store_epub_watermark_text' => 'textarea',
46 'stripe_cancel_url' => 'url',
47 // adyen_live_prefix is a bare host prefix such as
48 // "1797a841fbb37ca7-AdyenDemo", not a URL: esc_url_raw() prepended
49 // "http://" and broke every live API host built from it.
50 )
51 );
52 }
53
54 function ebook_store_sanitize_pro_locked_setting( $value, $option_name ) {
55 if ( ! ebook_store_has_pro_license() ) {
56 // Tell the user their change was not saved. This used to revert
57 // silently while WordPress still reported "Settings saved."
58 add_settings_error(
59 $option_name,
60 'ebook_store_pro_required',
61 sprintf(
62 /* translators: %s: the name of the setting that was not saved. */
63 __( '"%s" was not saved because it is a Pro feature. Add your license key in the General tab to unlock it.', 'ebook-store' ),
64 $option_name
65 ),
66 'warning'
67 );
68
69 return get_option( $option_name, '' );
70 }
71
72 $types = ebook_store_get_pro_locked_option_types();
73 $type = isset( $types[ $option_name ] ) ? $types[ $option_name ] : 'text';
74
75 if ( is_array( $value ) ) {
76 return array_map( 'sanitize_text_field', $value );
77 }
78
79 if ( ! is_scalar( $value ) ) {
80 return '';
81 }
82
83 $value = (string) $value;
84
85 if ( $type === 'textarea' ) {
86 return sanitize_textarea_field( $value );
87 }
88
89 if ( $type === 'url' ) {
90 return esc_url_raw( trim( $value ) );
91 }
92
93 return sanitize_text_field( $value );
94 }
95
96 function ebook_store_register_pro_locked_setting( $group, $option_name ) {
97 register_setting(
98 $group,
99 $option_name,
100 array(
101 'sanitize_callback' => function( $value ) use ( $option_name ) {
102 return ebook_store_sanitize_pro_locked_setting( $value, $option_name );
103 },
104 )
105 );
106 }
107
108 function ebook_store_register_pro_locked_array_setting( $group, $option_name ) {
109 register_setting(
110 $group,
111 $option_name,
112 array(
113 'type' => 'array',
114 'sanitize_callback' => function( $value ) use ( $option_name ) {
115 return ebook_store_sanitize_pro_locked_setting( $value, $option_name );
116 },
117 )
118 );
119 }
120
121 function ebook_store_get_settings_tab_url( $tab, $args = array() ) {
122 $args = array_merge(
123 array(
124 'page' => 'ebook_options.php',
125 'tab' => $tab,
126 ),
127 $args
128 );
129
130 return add_query_arg( $args, admin_url( 'options-general.php' ) );
131 }
132
133 function ebook_store_get_autocomplete_woocommerce_plugin_file() {
134 return 'autocomplete-woocommerce-orders/autocomplete-woocommerce-orders.php';
135 }
136
137 function ebook_store_is_autocomplete_woocommerce_installed() {
138 if ( ! function_exists( 'get_plugins' ) ) {
139 require_once ABSPATH . 'wp-admin/includes/plugin.php';
140 }
141
142 $plugins = get_plugins();
143
144 return isset( $plugins[ ebook_store_get_autocomplete_woocommerce_plugin_file() ] );
145 }
146
147 function ebook_store_is_autocomplete_woocommerce_active() {
148 if ( ! function_exists( 'is_plugin_active' ) ) {
149 require_once ABSPATH . 'wp-admin/includes/plugin.php';
150 }
151
152 return is_plugin_active( ebook_store_get_autocomplete_woocommerce_plugin_file() );
153 }
154
155 function ebook_store_get_autocomplete_woocommerce_mode() {
156 return sanitize_key( (string) get_option( 'wc_silkwave_aco_mode', '' ) );
157 }
158
159 function ebook_store_get_autocomplete_woocommerce_setup_state() {
160 $installed = ebook_store_is_autocomplete_woocommerce_installed();
161 $active = $installed && ebook_store_is_autocomplete_woocommerce_active();
162 $mode = $active ? ebook_store_get_autocomplete_woocommerce_mode() : '';
163 $required_order_status = sanitize_key( (string) get_option( 'ebook_store_woocommerce_required_order_status', 'completed' ) );
164
165 return array(
166 'installed' => $installed,
167 'active' => $active,
168 'mode' => $mode,
169 'required_order_status' => $required_order_status,
170 'is_recommended' => $active && $mode === 'virtual_downloadable' && $required_order_status === 'completed',
171 );
172 }
173
174 function ebook_store_get_autocomplete_woocommerce_setup_url() {
175 return wp_nonce_url(
176 admin_url( 'admin-post.php?action=ebook_store_setup_wc_autocomplete' ),
177 'ebook_store_setup_wc_autocomplete'
178 );
179 }
180
181 function ebook_store_handle_wc_autocomplete_setup() {
182 if ( ! current_user_can( 'manage_options' ) ) {
183 wp_die( esc_html__( 'You are not allowed to manage WooCommerce autocomplete setup.', 'ebook-store' ) );
184 }
185
186 check_admin_referer( 'ebook_store_setup_wc_autocomplete' );
187
188 $redirect_args = array();
189 $redirect_url = ebook_store_get_settings_tab_url( 'WooCommerce' );
190 $plugin_file = ebook_store_get_autocomplete_woocommerce_plugin_file();
191
192 if ( ! ebook_store_is_autocomplete_woocommerce_installed() ) {
193 if ( ! current_user_can( 'install_plugins' ) ) {
194 $redirect_args['ebook_wc_autocomplete_status'] = 'error';
195 $redirect_args['ebook_wc_autocomplete_message'] = __( 'The Autocomplete WooCommerce Orders plugin is not installed, and your account is not allowed to install plugins.', 'ebook-store' );
196 wp_safe_redirect( add_query_arg( $redirect_args, $redirect_url ) );
197 exit;
198 }
199
200 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
201 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
202 require_once ABSPATH . 'wp-admin/includes/file.php';
203 require_once ABSPATH . 'wp-admin/includes/misc.php';
204
205 $api = plugins_api(
206 'plugin_information',
207 array(
208 'slug' => 'autocomplete-woocommerce-orders',
209 'fields' => array(
210 'sections' => false,
211 ),
212 )
213 );
214
215 if ( is_wp_error( $api ) || empty( $api->download_link ) ) {
216 $redirect_args['ebook_wc_autocomplete_status'] = 'error';
217 $redirect_args['ebook_wc_autocomplete_message'] = __( 'WordPress could not fetch the Autocomplete WooCommerce Orders plugin package.', 'ebook-store' );
218 wp_safe_redirect( add_query_arg( $redirect_args, $redirect_url ) );
219 exit;
220 }
221
222 $skin = new Automatic_Upgrader_Skin();
223 $upgrader = new Plugin_Upgrader( $skin );
224 $installed = $upgrader->install( $api->download_link );
225
226 if ( is_wp_error( $installed ) || ! $installed ) {
227 $redirect_args['ebook_wc_autocomplete_status'] = 'error';
228 $redirect_args['ebook_wc_autocomplete_message'] = __( 'WordPress could not install the Autocomplete WooCommerce Orders plugin automatically.', 'ebook-store' );
229 wp_safe_redirect( add_query_arg( $redirect_args, $redirect_url ) );
230 exit;
231 }
232 }
233
234 if ( ! ebook_store_is_autocomplete_woocommerce_active() ) {
235 if ( ! current_user_can( 'activate_plugins' ) ) {
236 $redirect_args['ebook_wc_autocomplete_status'] = 'error';
237 $redirect_args['ebook_wc_autocomplete_message'] = __( 'The plugin is installed, but your account is not allowed to activate plugins.', 'ebook-store' );
238 wp_safe_redirect( add_query_arg( $redirect_args, $redirect_url ) );
239 exit;
240 }
241
242 $activation_result = activate_plugin( $plugin_file );
243 if ( is_wp_error( $activation_result ) ) {
244 $redirect_args['ebook_wc_autocomplete_status'] = 'error';
245 $redirect_args['ebook_wc_autocomplete_message'] = __( 'WordPress could not activate the Autocomplete WooCommerce Orders plugin.', 'ebook-store' );
246 wp_safe_redirect( add_query_arg( $redirect_args, $redirect_url ) );
247 exit;
248 }
249 }
250
251 update_option( 'wc_silkwave_aco_mode', 'virtual_downloadable' );
252 update_option( 'ebook_store_woocommerce_required_order_status', 'completed' );
253
254 $redirect_args['ebook_wc_autocomplete_status'] = 'success';
255 $redirect_args['ebook_wc_autocomplete_message'] = __( 'Autocomplete WooCommerce Orders is ready. Virtual and downloadable paid orders will now complete automatically, and Ebook Store downloads are set to wait for Completed orders.', 'ebook-store' );
256
257 wp_safe_redirect( add_query_arg( $redirect_args, $redirect_url ) );
258 exit;
259 }
260 add_action( 'admin_post_ebook_store_setup_wc_autocomplete', 'ebook_store_handle_wc_autocomplete_setup' );
261
262 function ebook_store_sanitize_link_expiration( $value ) {
263 $value = is_scalar( $value ) ? trim( (string) $value ) : '';
264
265 if ( $value === '' ) {
266 return '1 year';
267 }
268
269 $value = sanitize_text_field( $value );
270
271 // Downloads compare strtotime( '+' . $value, order time ) with now, so a
272 // value PHP cannot parse (or one that is not in the future) expired every
273 // download link on the site the moment it was saved.
274 $now = time();
275 $expires = strtotime( '+' . $value, $now );
276
277 if ( $expires === false || $expires <= $now ) {
278 add_settings_error(
279 'link_expiration',
280 'ebook_store_invalid_link_expiration',
281 sprintf(
282 /* translators: %s: the value that was typed into the field. */
283 __( '"%s" is not a time period PHP understands, so the download link lifetime was left unchanged. Use something like "1 year", "6 months" or "30 days".', 'ebook-store' ),
284 esc_html( $value )
285 ),
286 'error'
287 );
288
289 return get_option( 'link_expiration', '1 year' );
290 }
291
292 return $value;
293 }
294
295 function ebook_store_sanitize_currency_setting( $value ) {
296 $value = is_scalar( $value ) ? strtoupper( trim( (string) $value ) ) : '';
297
298 return preg_match( '/^[A-Z]{3}$/', $value ) ? $value : 'USD';
299 }
300
301 function ebook_store_sanitize_vat_percent( $value ) {
302 if ( ! is_scalar( $value ) ) {
303 return '0';
304 }
305
306 $value = trim( (string) $value );
307 if ( $value === '' ) {
308 return '0';
309 }
310
311 return (string) max( 0, (float) $value );
312 }
313
314 function ebook_store_sanitize_downloads_limit( $value ) {
315 if ( ! is_scalar( $value ) ) {
316 return '5';
317 }
318
319 $value = trim( (string) $value );
320 if ( $value === '' ) {
321 return '5';
322 }
323
324 return (string) max( 1, (int) $value );
325 }
326
327 /**
328 * Store the "Confirm every payment with PayPal" toggle as '1' or '0'.
329 *
330 * The unchecked toggle posts '', which the IPN handler reads as "never
331 * configured, so verify" — so the option could never actually be switched off.
332 * An explicit '0' is the only value that turns verification off.
333 */
334 function ebook_store_sanitize_paypal_verify_transactions( $value ) {
335 return ( is_scalar( $value ) && (string) $value === '1' ) ? '1' : '0';
336 }
337
338 function register_ebook_store_settings() {
339
340 //register our settings
341 // Font sizes now live on the General tab; the unreachable Fonts tab is gone.
342 register_setting( 'ebook-settings-group-general', 'ebook_store_buy_link_font_size' );
343 register_setting( 'ebook-settings-group-general', 'ebook_store_title_font_size' );
344 register_setting( 'ebook-settings-group-general', 'attach_files' );
345 register_setting(
346 'ebook-settings-group-general',
347 'downloads_limit',
348 array(
349 'sanitize_callback' => 'ebook_store_sanitize_downloads_limit',
350 )
351 );
352 register_setting( 'ebook-settings-group-general', 'ebook_store_license_key' );
353 ebook_store_register_pro_locked_setting( 'ebook-settings-group-general', 'ebook_store_locale' );
354 register_setting( 'ebook-settings-group-general', 'ebook_store_form_template' );
355 register_setting( 'ebook-settings-group-general', 'email_delivery' );
356 register_setting(
357 'ebook-settings-group-general',
358 'link_expiration',
359 array(
360 'sanitize_callback' => 'ebook_store_sanitize_link_expiration',
361 )
362 );
363 register_setting( 'ebook-settings-group-general', 'form_scale' );
364 register_setting( 'ebook-settings-group-paypal', 'paypal_integration_enabled' );
365 register_setting( 'ebook-settings-group-paypal', 'paypal_account' );
366 // PayPal REST app credentials (OAuth 2.0 client credentials) - free, not Pro-gated.
367 register_setting( 'ebook-settings-group-paypal', 'paypal_rest_client_id', array( 'sanitize_callback' => 'ebook_store_sanitize_paypal_rest_credential' ) );
368 register_setting( 'ebook-settings-group-paypal', 'paypal_rest_client_secret', array( 'sanitize_callback' => 'ebook_store_sanitize_paypal_rest_credential' ) );
369 register_setting( 'ebook-settings-group-paypal', 'paypal_rest_webhook_id', array( 'sanitize_callback' => 'ebook_store_sanitize_paypal_rest_credential' ) );
370 register_setting(
371 'ebook-settings-group-general',
372 'vat_percent',
373 array(
374 'sanitize_callback' => 'ebook_store_sanitize_vat_percent',
375 )
376 );
377 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_wp_affiliate_integration' );
378 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'formEnabled' );
379 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'kindleDelivery' );
380 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_disable_cover_buy_now' );
381 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_hide_buy_now' );
382 register_setting( 'ebook-settings-group-integrations', 'ebook_store_viewer_js' );
383 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_silent_registration' );
384 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_no_autorefresh' );
385 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_no_viewerjs_previews' );
386 ebook_store_register_pro_locked_setting( 'ebook-settings-group-integrations', 'ebook_store_wpforms_default_form_force' );
387 ebook_store_register_pro_locked_setting( 'ebook-settings-group-woocommerce', 'ebook_store_woocommerce_integration' );
388 ebook_store_register_pro_locked_setting( 'ebook-settings-group-woocommerce', 'ebook_store_woocommerce_integration_no_added_to_cart' );
389 ebook_store_register_pro_locked_setting( 'ebook-settings-group-woocommerce', 'ebook_store_woocommerce_required_order_status' );
390 ebook_store_register_pro_locked_setting( 'ebook-settings-group-woocommerce', 'ebook_store_woocommerce_pdf_reader' );
391
392 ebook_store_register_pro_locked_setting( 'ebook-settings-group-mailchimp', 'mailchimp_api_key' );
393 ebook_store_register_pro_locked_setting( 'ebook-settings-group-mailchimp', 'mailchimp_lists' );
394 ebook_store_register_pro_locked_setting( 'ebook-settings-group-mailchimp', 'mailchimp_integration_enabled' );
395 ebook_store_register_pro_locked_setting( 'ebook-settings-group-brevo', 'brevo_api_key' );
396 ebook_store_register_pro_locked_setting( 'ebook-settings-group-brevo', 'brevo_lists' );
397 ebook_store_register_pro_locked_setting( 'ebook-settings-group-brevo', 'brevo_integration_enabled' );
398 register_setting( 'ebook-settings-group-paypal', 'ebook_store_allow_echeck' );
399 register_setting( 'ebook-settings-group-paypal', 'ebook_store_require_shipping' );
400 register_setting(
401 'ebook-settings-group-general',
402 'paypal_currency',
403 array(
404 'sanitize_callback' => 'ebook_store_sanitize_currency_setting',
405 )
406 );
407 register_setting( 'ebook-settings-group-paypal', 'paypal_language' );
408 register_setting( 'ebook-settings-group-paypal', 'paypal_return_button_text' );
409 register_setting( 'ebook-settings-group-paypal', 'paypal_button_text', array(
410 'type' => 'array',
411 'sanitize_callback' => function($value) {
412 if (!is_array($value)) return array();
413 return array_map('sanitize_text_field', $value);
414 }
415 ));
416 register_setting( 'ebook-settings-group-paypal', 'paypal_sandbox' );
417 register_setting(
418 'ebook-settings-group-paypal',
419 'paypal_verify_transactions',
420 array(
421 'sanitize_callback' => 'ebook_store_sanitize_paypal_verify_transactions',
422 )
423 );
424 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'buyer_info' );
425 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'buyer_info_text' );
426 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'disable_annot-forms' );
427 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'disable_pdf_copy' );
428 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'disable_pdf_modify' );
429 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'disable_pdf_printing' );
430 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'ebook_store_blank_password' );
431 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'ebook_store_watermark_color_hex' );
432 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'ebook_store_buyer_info_position' );
433 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'ebook_store_watermark_mode' );
434
435
436 register_setting( 'ebook-settings-group-templates', 'ebook_store_checkout_page' );
437 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'ebook_store_owner_password' );
438 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'ebook_store_random_password' );
439 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'encrypt_files_for_logged_in_users' );
440 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'encrypt_pdf' );
441 ebook_store_register_pro_locked_setting( 'ebook-settings-group-pdf-protection', 'qr_code' );
442 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_watermark' );
443 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_watermark_text' );
444 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_watermark_scope' );
445 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_watermark_position' );
446 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_watermark_style' );
447 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_stamp_metadata' );
448 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_qr' );
449 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_qr_scope' );
450 ebook_store_register_pro_locked_setting( 'ebook-settings-group-epub-protection', 'ebook_store_epub_qr_position' );
451 register_setting( 'ebook-settings-group-templates', 'thankyou_page' );
452 register_setting( 'ebook-settings-group-templates', 'ebook_store_cancel_page' );
453 register_setting( 'ebook-settings-group-templates', 'email_delivery_subject' );
454 register_setting( 'ebook-settings-group-templates', 'email_delivery_text' );
455 register_setting( 'ebook-settings-group-templates', 'formContent' );
456 register_setting( 'ebook-settings-group-general', 'ebookstorewpsc' ); //doesn't really matter where is assigned.
457 ebook_store_register_pro_locked_setting( 'ebook-settings-group-google-analytics', 'ebook_store_google_analytics_enabled' );
458 ebook_store_register_pro_locked_setting( 'ebook-settings-group-google-analytics', 'ebook_store_google_analytics_measurement_id' );
459 ebook_store_register_pro_locked_setting( 'ebook-settings-group-google-analytics', 'ebook_store_google_analytics_api_secret' );
460
461 ebook_store_register_pro_locked_setting('ebook-settings-group-stripe', 'stripe_publishable_key');
462 ebook_store_register_pro_locked_setting('ebook-settings-group-stripe', 'stripe_secret_key');
463 ebook_store_register_pro_locked_setting('ebook-settings-group-stripe', 'stripe_webhook_secret');
464 ebook_store_register_pro_locked_setting('ebook-settings-group-stripe', 'stripe_cancel_url');
465 ebook_store_register_pro_locked_setting('ebook-settings-group-stripe', 'stripe_integration_enabled');
466 ebook_store_register_pro_locked_array_setting('ebook-settings-group-stripe', 'stripe_button_text');
467 ebook_store_register_pro_locked_setting('ebook-settings-group-adyen', 'adyen_api_key');
468 ebook_store_register_pro_locked_setting('ebook-settings-group-adyen', 'adyen_merchant_account');
469 ebook_store_register_pro_locked_setting('ebook-settings-group-adyen', 'adyen_hmac_key');
470 ebook_store_register_pro_locked_setting('ebook-settings-group-adyen', 'adyen_environment');
471 ebook_store_register_pro_locked_setting('ebook-settings-group-adyen', 'adyen_live_prefix');
472 ebook_store_register_pro_locked_setting('ebook-settings-group-adyen', 'adyen_integration_enabled');
473 ebook_store_register_pro_locked_array_setting('ebook-settings-group-adyen', 'adyen_button_text');
474
475 do_action('ebook_store_extend_options');
476 }
477
478
479
480 /**
481 * Create the two pages a new store needs, and point the plugin at them.
482 *
483 * Reachable from the store-health panel. It used to run from a bare GET with no
484 * nonce, and it discarded the ID of the downloads page it created, orphaning it.
485 */
486 function ebook_store_handle_fix_thankyou_page() {
487 if ( ! current_user_can( 'manage_options' ) ) {
488 wp_die( esc_html__( 'You are not allowed to create pages.', 'ebook-store' ) );
489 }
490
491 check_admin_referer( 'ebook_store_fix_thankyou' );
492
493 $created = array();
494
495 // Thank-you page: where buyers land after paying and collect their download.
496 $checkout_page = (int) get_option( 'ebook_store_checkout_page', 0 );
497 if ( $checkout_page < 1 || get_post_status( $checkout_page ) !== 'publish' ) {
498 $page_id = wp_insert_post(
499 array(
500 'post_content' => '[ebook_thank_you]',
501 'post_name' => 'thank-you',
502 'post_title' => __( 'Thank you for your order', 'ebook-store' ),
503 'post_status' => 'publish',
504 'post_type' => 'page',
505 ),
506 true
507 );
508
509 if ( ! is_wp_error( $page_id ) ) {
510 update_option( 'ebook_store_checkout_page', $page_id );
511 $created[] = __( 'Thank-you page', 'ebook-store' );
512 }
513 }
514
515 // Downloads page: where buyers re-download things they already bought.
516 $downloads_page = (int) get_option( 'ebook_store_downloads_page', 0 );
517 if ( $downloads_page < 1 || get_post_status( $downloads_page ) !== 'publish' ) {
518 $page_id = wp_insert_post(
519 array(
520 'post_content' => '[ebook_store_downloads]',
521 'post_name' => 'my-ebook-orders',
522 'post_title' => __( 'My ebook orders', 'ebook-store' ),
523 'post_status' => 'publish',
524 'post_type' => 'page',
525 ),
526 true
527 );
528
529 // The old code created this page and then threw the ID away, so nothing
530 // ever linked to it.
531 if ( ! is_wp_error( $page_id ) ) {
532 update_option( 'ebook_store_downloads_page', $page_id );
533 $created[] = __( 'Downloads page', 'ebook-store' );
534 }
535 }
536
537 wp_safe_redirect(
538 add_query_arg(
539 array( 'ebook_pages_created' => rawurlencode( implode( ', ', $created ) ) ),
540 ebook_store_get_settings_tab_url( 'Templates' )
541 )
542 );
543 exit;
544 }
545 add_action( 'admin_post_ebook_store_fix_thankyou', 'ebook_store_handle_fix_thankyou_page' );
546
547 /**
548 * The settings tabs, in the order a store owner needs them.
549 *
550 * @return array slug => label
551 */
552 function ebook_store_get_settings_tabs() {
553 $tabs = array(
554 'General' => __( 'General', 'ebook-store' ),
555 'PayPal' => __( 'PayPal', 'ebook-store' ),
556 'Stripe' => __( 'Stripe', 'ebook-store' ),
557 'Adyen' => __( 'Adyen', 'ebook-store' ),
558 'WooCommerce' => __( 'WooCommerce', 'ebook-store' ),
559 'Templates' => __( 'Pages & emails', 'ebook-store' ),
560 'PDF-Protection' => __( 'PDF protection', 'ebook-store' ),
561 'EPUB-Protection' => __( 'ePub protection', 'ebook-store' ),
562 'Integrations' => __( 'Integrations', 'ebook-store' ),
563 'MailChimp' => __( 'Mailchimp', 'ebook-store' ),
564 'Brevo' => __( 'Brevo', 'ebook-store' ),
565 'Google-Analytics' => __( 'Analytics', 'ebook-store' ),
566 );
567
568 return apply_filters( 'ebook_store_options_add_tab', $tabs );
569 }
570
571 /**
572 * A one-line explanation of what each tab is for.
573 *
574 * @return array
575 */
576 function ebook_store_get_settings_tab_descriptions() {
577 return array(
578 'General' => __( 'Your license, currency, tax, download limits and order emails.', 'ebook-store' ),
579 'PayPal' => __( 'Take payments with PayPal. This is the payment method available in the free version.', 'ebook-store' ),
580 'Stripe' => __( 'Take card payments with Stripe, including Apple Pay, Google Pay and Klarna.', 'ebook-store' ),
581 'Adyen' => __( 'Take payments with Adyen Pay by Link.', 'ebook-store' ),
582 'WooCommerce' => __( 'Sell your ebooks through the WooCommerce cart and checkout instead of the built-in one.', 'ebook-store' ),
583 'Templates' => __( 'The pages buyers land on and the emails they receive.', 'ebook-store' ),
584 'PDF-Protection' => __( 'Encrypt, watermark and restrict the PDFs you deliver.', 'ebook-store' ),
585 'EPUB-Protection' => __( 'Watermark and stamp the ePubs you deliver so every copy traces back to its buyer.', 'ebook-store' ),
586 'Integrations' => __( 'Connect forms, affiliates, Kindle delivery and customer accounts.', 'ebook-store' ),
587 'MailChimp' => __( 'Add buyers to a Mailchimp audience automatically.', 'ebook-store' ),
588 'Brevo' => __( 'Add buyers to a Brevo contact list automatically.', 'ebook-store' ),
589 'Google-Analytics' => __( 'Send purchase and refund events to Google Analytics 4.', 'ebook-store' ),
590 );
591 }
592
593 /**
594 * The icon shown on each tab.
595 *
596 * @return array
597 */
598 function ebook_store_get_settings_tab_icons() {
599 $icons = array(
600 'General' => 'general.svg',
601 'WooCommerce' => 'woocommerce.svg',
602 'PDF-Protection' => 'pdf-protection.svg',
603 'PayPal' => 'paypal.svg',
604 'Stripe' => 'stripe.svg',
605 'Adyen' => 'adyen.svg',
606 'Templates' => 'templates.svg',
607 'Integrations' => 'integrations.svg',
608 'MailChimp' => 'mailchimp.svg',
609 'Brevo' => 'brevo.svg',
610 'Google-Analytics' => 'google-analytics.svg',
611 );
612
613 foreach ( $icons as $key => $file ) {
614 $icons[ $key ] = ebook_store_url( 'assets/payment-logos/' . $file );
615 }
616
617 return $icons;
618 }
619
620 /**
621 * Which option group belongs to which tab.
622 *
623 * @param string $tab Tab slug.
624 * @return string
625 */
626 function ebook_store_get_settings_group_for_tab( $tab ) {
627 return 'ebook-settings-group-' . strtolower( str_replace( '-', '-', $tab ) );
628 }
629
630 /**
631 * The settings navigation, grouped so a first-time user sees ~5 choices instead
632 * of a wall of 11 tabs. Standalone entries are single tabs; grouped entries roll
633 * several related tabs into one dropdown (Payments, Advanced).
634 *
635 * Only tabs that actually exist in ebook_store_get_settings_tabs() are shown, so
636 * a filter that adds or removes a tab keeps working.
637 *
638 * @return array Ordered list of nav entries.
639 */
640 function ebook_store_get_settings_nav_groups() {
641 $tabs = ebook_store_get_settings_tabs();
642
643 $layout = array(
644 array( 'type' => 'tab', 'slug' => 'General' ),
645 array(
646 'type' => 'group',
647 'label' => __( 'Payments', 'ebook-store' ),
648 'icon' => 'paypal.svg',
649 'tabs' => array( 'PayPal', 'Stripe', 'Adyen', 'WooCommerce' ),
650 ),
651 array( 'type' => 'tab', 'slug' => 'Templates' ),
652 array( 'type' => 'tab', 'slug' => 'PDF-Protection' ),
653 array( 'type' => 'tab', 'slug' => 'EPUB-Protection' ),
654 array(
655 'type' => 'group',
656 'label' => __( 'Advanced', 'ebook-store' ),
657 'icon' => 'integrations.svg',
658 'tabs' => array( 'Integrations', 'MailChimp', 'Brevo', 'Google-Analytics' ),
659 ),
660 );
661
662 // Keep only entries whose tabs still exist, and collect any tab that a filter
663 // added but the layout does not mention, so nothing becomes unreachable.
664 $placed = array();
665 $nav = array();
666
667 foreach ( $layout as $entry ) {
668 if ( $entry['type'] === 'tab' ) {
669 if ( isset( $tabs[ $entry['slug'] ] ) ) {
670 $entry['label'] = $tabs[ $entry['slug'] ];
671 $nav[] = $entry;
672 $placed[ $entry['slug'] ] = true;
673 }
674 continue;
675 }
676
677 $entry['tabs'] = array_values( array_filter(
678 $entry['tabs'],
679 static function ( $slug ) use ( $tabs ) {
680 return isset( $tabs[ $slug ] );
681 }
682 ) );
683
684 foreach ( $entry['tabs'] as $slug ) {
685 $placed[ $slug ] = true;
686 }
687
688 if ( ! empty( $entry['tabs'] ) ) {
689 $nav[] = $entry;
690 }
691 }
692
693 $extra = array();
694 foreach ( $tabs as $slug => $label ) {
695 if ( empty( $placed[ $slug ] ) ) {
696 $extra[] = array( 'type' => 'tab', 'slug' => $slug, 'label' => $label );
697 }
698 }
699
700 if ( ! empty( $extra ) ) {
701 // Fold any unplaced third-party tabs into the Advanced group if present,
702 // otherwise append them as standalone tabs.
703 $nav = array_merge( $nav, $extra );
704 }
705
706 return $nav;
707 }
708
709 /**
710 * Render one settings tab link.
711 *
712 * @param string $slug Tab slug.
713 * @param string $label Tab label.
714 * @param string $current Current tab.
715 * @param array $icons Icon map.
716 * @param bool $in_menu Whether this link sits inside a group dropdown.
717 */
718 function ebook_store_render_settings_tab_link( $slug, $label, $current, $icons, $in_menu = false ) {
719 $is_active = ( $slug === $current );
720 $classes = $in_menu ? 'ebook-nav-menu__item' : 'nav-tab';
721 if ( $is_active ) {
722 $classes .= $in_menu ? ' is-active' : ' nav-tab-active';
723 }
724 ?>
725 <a
726 href="<?php echo esc_url( ebook_store_get_settings_tab_url( $slug ) ); ?>"
727 class="<?php echo esc_attr( $classes ); ?>"
728 <?php echo $is_active ? ' aria-current="page"' : ''; ?>
729 >
730 <?php if ( isset( $icons[ $slug ] ) ) { ?>
731 <span class="ebook-store-nav-tab__icon" aria-hidden="true">
732 <img src="<?php echo esc_url( $icons[ $slug ] ); ?>" alt="" width="20" height="20" loading="lazy" />
733 </span>
734 <?php } ?>
735 <span class="ebook-store-nav-tab__label"><?php echo esc_html( $label ); ?></span>
736 </a>
737 <?php
738 }
739
740 /**
741 * Render the grouped settings navigation.
742 *
743 * Groups use a native <details>/<summary> disclosure, so the dropdowns are
744 * keyboard-accessible and work with no JavaScript.
745 *
746 * @param string $current Current tab.
747 * @param array $tabs slug => label.
748 * @param array $icons slug => icon url.
749 */
750 function ebook_store_render_settings_nav( $current, $tabs, $icons ) {
751 $nav = ebook_store_get_settings_nav_groups();
752 ?>
753 <nav class="ebook-store-nav" aria-label="<?php echo esc_attr__( 'Ebook Store settings sections', 'ebook-store' ); ?>">
754 <?php
755 foreach ( $nav as $entry ) {
756 if ( $entry['type'] === 'tab' ) {
757 ebook_store_render_settings_tab_link( $entry['slug'], $entry['label'], $current, $icons );
758 continue;
759 }
760
761 // Group dropdown.
762 $active_child = '';
763 foreach ( $entry['tabs'] as $slug ) {
764 if ( $slug === $current ) {
765 $active_child = isset( $tabs[ $slug ] ) ? $tabs[ $slug ] : $slug;
766 break;
767 }
768 }
769 $group_active = ( $active_child !== '' );
770 // The dropdown starts CLOSED even when it holds the current tab —
771 // the highlighted summary (with the active child's name) already
772 // shows where you are. Leaving it open would float the menu over the
773 // page content on every reload.
774 ?>
775 <details class="ebook-nav-group<?php echo $group_active ? ' is-active' : ''; ?>">
776 <summary class="nav-tab<?php echo $group_active ? ' nav-tab-active' : ''; ?>">
777 <span class="ebook-store-nav-tab__label">
778 <?php echo esc_html( $entry['label'] ); ?>
779 <?php if ( $group_active ) { ?>
780 <span class="ebook-nav-group__current"><?php echo esc_html( $active_child ); ?></span>
781 <?php } ?>
782 </span>
783 <span class="ebook-nav-group__caret dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
784 </summary>
785 <div class="ebook-nav-menu" role="menu">
786 <?php
787 foreach ( $entry['tabs'] as $slug ) {
788 ebook_store_render_settings_tab_link( $slug, isset( $tabs[ $slug ] ) ? $tabs[ $slug ] : $slug, $current, $icons, true );
789 }
790 ?>
791 </div>
792 </details>
793 <?php } ?>
794 </nav>
795 <?php
796 }
797
798 /**
799 * Render the Ebook Store settings screen.
800 */
801 function ebook_settings_page() {
802 if ( ! current_user_can( 'manage_options' ) ) {
803 wp_die( esc_html__( 'You are not allowed to manage Ebook Store settings.', 'ebook-store' ) );
804 }
805
806 $tabs = ebook_store_get_settings_tabs();
807 $tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'General';
808
809 if ( $tab === '' || ! array_key_exists( $tab, $tabs ) ) {
810 $tab = 'General';
811 }
812
813 $descriptions = ebook_store_get_settings_tab_descriptions();
814 $icons = ebook_store_get_settings_tab_icons();
815 $description = isset( $descriptions[ $tab ] )
816 ? $descriptions[ $tab ]
817 : __( 'Manage how your store sells and delivers ebooks.', 'ebook-store' );
818
819 $license = ebook_store_get_license_summary();
820
821 // The tab files read these; they are shared context, not per-tab state.
822 include_once( plugin_dir_path( EBOOK_STORE_PLUGIN_FILE ) . 'locale.php' );
823 $locale = ebook_store_get_locale_strings();
824 $formats_locale = ebook_store_get_format_labels();
825 $languages = function_exists( 'ebook_store_get_language_labels' )
826 ? ebook_store_get_language_labels()
827 : array( 0 => __( 'Default', 'ebook-store' ) );
828 $op = new QSWPOptions();
829
830 ebook_store_render_settings_notices();
831 ?>
832 <div class="wrap ebook-settings-page">
833
834 <div class="ebook-settings-page__hero">
835 <div class="ebook-settings-page__hero-copy">
836 <span class="ebook-settings-page__eyebrow"><?php echo esc_html__( 'Ebook Store', 'ebook-store' ); ?></span>
837 <h1><?php echo esc_html__( 'Settings', 'ebook-store' ); ?></h1>
838 <p><?php echo esc_html( $description ); ?></p>
839 </div>
840 <div class="ebook-settings-page__hero-meta">
841 <span class="ebook-settings-page__plan is-<?php echo esc_attr( $license['tone'] ); ?>">
842 <?php echo esc_html( $license['label'] ); ?>
843 </span>
844 <?php if ( ! ebook_store_has_pro_license() ) { ?>
845 <a class="ebook-settings-page__upgrade" href="https://www.shopfiles.com/index.php/products/wordpress-ebook-store" target="_blank" rel="noopener noreferrer">
846 <?php echo esc_html__( 'Upgrade to Pro', 'ebook-store' ); ?>
847 </a>
848 <?php } ?>
849 </div>
850 </div>
851
852 <?php
853 // The guided 3-step "Start here" card leads. When every essential is in
854 // place it shrinks to a one-line "ready" state on its own.
855 ebook_store_render_getting_started();
856
857 $store_ready = ebook_store_is_ready();
858
859 // On the landing (the General tab, arrived at without picking a tab) of a
860 // store that is not set up yet, keep the full settings tucked behind a
861 // disclosure so the three steps above are all a new user has to face.
862 // Once the store is ready, or the user has opened a specific tab, the
863 // settings are shown expanded.
864 $on_landing = ( $tab === 'General' && ! isset( $_GET['tab'] ) );
865 $settings_expanded = $store_ready || ! $on_landing;
866 ?>
867
868 <details class="ebook-all-settings"<?php echo $settings_expanded ? ' open' : ''; ?>>
869 <summary class="ebook-all-settings__summary">
870 <span class="ebook-all-settings__summary-label">
871 <span class="dashicons dashicons-admin-generic" aria-hidden="true"></span>
872 <?php echo esc_html__( 'All settings', 'ebook-store' ); ?>
873 </span>
874 <span class="ebook-all-settings__summary-hint"><?php echo esc_html__( 'Currency, payment methods, file protection, integrations and more', 'ebook-store' ); ?></span>
875 </summary>
876
877 <div class="ebook-settings-toolbar">
878 <label class="ebook-settings-search" for="ebook-settings-search">
879 <span class="dashicons dashicons-search" aria-hidden="true"></span>
880 <span class="screen-reader-text"><?php echo esc_html__( 'Search settings', 'ebook-store' ); ?></span>
881 <input
882 type="search"
883 id="ebook-settings-search"
884 placeholder="<?php echo esc_attr__( 'Search settings on this page…', 'ebook-store' ); ?>"
885 autocomplete="off"
886 />
887 </label>
888 <p class="ebook-settings-search__status" role="status" aria-live="polite"></p>
889 </div>
890
891 <?php ebook_store_render_settings_nav( $tab, $tabs, $icons ); ?>
892
893 <form method="post" action="options.php" class="ebook-settings-form" id="ebook-settings-form">
894 <div class="ebook-settings-panel">
895 <?php
896 $tab_file = plugin_dir_path( EBOOK_STORE_PLUGIN_FILE ) . 'settings-' . $tab . '.php';
897 if ( file_exists( $tab_file ) ) {
898 include $tab_file;
899 }
900
901 // Third-party gateway fields are only rendered inside their own
902 // nonce scope. Firing this on every tab meant an add-on's fields
903 // were silently discarded on save from any other tab.
904 if ( in_array( $tab, array( 'PayPal', 'Stripe', 'Adyen' ), true ) ) {
905 do_action( 'ebook_store_extend_options_payment_gateways', $tab );
906 }
907 ?>
908 </div>
909
910 <div class="ebook-settings-savebar" id="ebook-settings-savebar">
911 <span class="ebook-settings-savebar__status" role="status" aria-live="polite"></span>
912 <?php submit_button( __( 'Save changes', 'ebook-store' ), 'primary', 'submit', false ); ?>
913 </div>
914 </form>
915 </details>
916
917 <?php
918 // The full store-status checklist, and the plan comparison, sit at the
919 // very bottom as reference rather than competing with the 3 steps.
920 ebook_store_render_health_panel();
921 ?>
922
923 <?php if ( ! ebook_store_has_pro_license() ) { ?>
924 <details class="ebook-settings-pricing">
925 <summary><?php echo esc_html__( 'Compare the free and Pro versions', 'ebook-store' ); ?></summary>
926 <?php include plugin_dir_path( EBOOK_STORE_PLUGIN_FILE ) . 'pricing_table.php'; ?>
927 </details>
928 <?php } ?>
929 </div>
930 <?php
931 }
932
933 /**
934 * Admin notices shown above the settings screen.
935 *
936 * These used to be scattered through the render: a global "allow_url_fopen is
937 * off" nag on every tab, an SMTP recommendation disconnected from the toggle it
938 * referred to, and a Zopim live-chat widget from the plugin vendor loaded into
939 * the customer's admin session on the same page as their payment secret keys.
940 */
941 function ebook_store_render_settings_notices() {
942 // Surfaces anything add_settings_error() recorded during the save, such as a
943 // Pro-locked field that was silently reverted.
944 settings_errors();
945
946 if ( isset( $_GET['ebook_license_checked'] ) ) {
947 $state = sanitize_key( wp_unslash( $_GET['ebook_license_checked'] ) );
948 $summary = ebook_store_get_license_summary();
949 $class = $summary['tone'] === 'ok' ? 'notice-success' : ( $summary['tone'] === 'error' ? 'notice-error' : 'notice-warning' );
950
951 printf(
952 '<div class="notice %1$s is-dismissible"><p><strong>%2$s</strong> %3$s</p></div>',
953 esc_attr( $class ),
954 esc_html( $summary['label'] ),
955 esc_html( $summary['message'] )
956 );
957 }
958
959 if ( isset( $_GET['ebook_pages_created'] ) ) {
960 $created = sanitize_text_field( wp_unslash( $_GET['ebook_pages_created'] ) );
961
962 if ( $created !== '' ) {
963 printf(
964 '<div class="notice notice-success is-dismissible"><p>%s</p></div>',
965 esc_html(
966 sprintf(
967 /* translators: %s: comma-separated list of page names. */
968 __( 'Created and selected: %s. Your store can now deliver downloads.', 'ebook-store' ),
969 $created
970 )
971 )
972 );
973 } else {
974 printf(
975 '<div class="notice notice-info is-dismissible"><p>%s</p></div>',
976 esc_html__( 'Those pages already existed, so nothing was changed.', 'ebook-store' )
977 );
978 }
979 }
980
981 if ( isset( $_GET['ebook_wc_autocomplete_status'] ) && isset( $_GET['ebook_wc_autocomplete_message'] ) ) {
982 $status = sanitize_key( wp_unslash( $_GET['ebook_wc_autocomplete_status'] ) );
983
984 printf(
985 '<div class="notice %1$s is-dismissible"><p>%2$s</p></div>',
986 $status === 'success' ? 'notice-success' : 'notice-error',
987 esc_html( sanitize_text_field( wp_unslash( $_GET['ebook_wc_autocomplete_message'] ) ) )
988 );
989 }
990
991 if ( function_exists( 'ebook_store_wp_super_cache_warning' ) ) {
992 ebook_store_wp_super_cache_warning();
993 }
994 }
995