array(
'label' => __( 'General', 'logvia' ),
'fields' => array( 'page_id', 'replace_wp_links', 'redirect_wp_login', 'redirect_after_login', 'redirect_custom', 'redirect_after_register', 'redirect_register_custom', 'redirect_after_logout', 'redirect_logout_custom', 'delete_data' ),
),
'registration' => array(
'label' => __( 'Registration', 'logvia' ),
'fields' => array( 'registration', 'email_verification', 'min_password', 'default_role', 'reset_method' ),
),
'popup' => array(
'label' => __( 'Popup', 'logvia' ),
'fields' => array( 'popup_enabled', 'popup_style', 'popup_side', 'popup_intercept', 'popup_auto', 'popup_auto_delay', 'popup_auto_pages', 'popup_auto_once', 'popup_force' ),
),
'appearance' => array(
'label' => __( 'Appearance', 'logvia' ),
'fields' => array( 'accent', 'button_text', 'tab_active', 'tab_active_text', 'radius', 'popup_width', 'overlay_color', 'overlay_opacity', 'logo', 'layout', 'image', 'image_title', 'image_text' ),
),
'forms' => array(
'label' => __( 'Form builder', 'logvia' ),
'fields' => array(),
),
'texts' => array(
'label' => __( 'Texts & emails', 'logvia' ),
'fields' => array(),
),
'security' => array(
'label' => __( 'Security', 'logvia' ),
'fields' => array( 'captcha', 'captcha_site_key', 'captcha_secret', 'captcha_login', 'captcha_register', 'captcha_forgot', 'recaptcha_threshold', 'strength_meter', 'min_strength', 'limit_attempts', 'limit_window' ),
),
'google' => array(
'label' => __( 'Google sign-in', 'logvia' ),
'fields' => array( 'google_enabled', 'google_client_id', 'google_client_secret', 'google_register', 'google_allow_privileged' ),
),
'woocommerce' => array(
'label' => __( 'WooCommerce', 'logvia' ),
'fields' => array( 'wc_replace_myaccount', 'wc_checkout_login', 'gate_enabled', 'gate_notice' ),
),
);
/**
* Filter the settings tabs and the fields each one saves. Add-ons
* append their own field keys to a tab here.
*
* @param array $tabs Tabs.
*/
return apply_filters( 'logvia_settings_tabs', $tabs );
}
/**
* Checkbox fields (missing from POST means off).
*
* @return string[]
*/
private static function checkboxes() {
return apply_filters( 'logvia_settings_checkboxes', array( 'registration', 'email_verification', 'replace_wp_links', 'redirect_wp_login', 'delete_data', 'popup_enabled', 'popup_intercept', 'popup_auto', 'popup_auto_once', 'popup_force', 'captcha_login', 'captcha_register', 'captcha_forgot', 'strength_meter', 'google_enabled', 'google_register', 'google_allow_privileged', 'wc_replace_myaccount', 'wc_checkout_login', 'gate_enabled' ) );
}
/**
* Allowed values for select/radio fields.
*
* @return array
*/
private static function choices() {
$choices = array(
'redirect_after_login' => array( 'account', 'home', 'same', 'custom' ),
'redirect_after_register' => array( 'login', 'account', 'home', 'same', 'custom' ),
'redirect_after_logout' => array( 'login', 'home', 'same', 'custom' ),
'reset_method' => array( 'link', 'code' ),
'popup_style' => array( 'modal', 'slide' ),
'popup_side' => array( 'right', 'left' ),
'layout' => array( 'card', 'split' ),
'captcha' => array( 'none', 'turnstile', 'recaptcha_v2', 'recaptcha_v3' ),
'min_strength' => array( '0', '2', '3' ),
);
return apply_filters( 'logvia_settings_choices', $choices );
}
/**
* Menu entry.
*/
public static function menu() {
add_options_page( __( 'Logvia', 'logvia' ), __( 'Logvia', 'logvia' ), 'manage_options', 'logvia', array( __CLASS__, 'render' ) );
}
/**
* Media picker on the Appearance tab.
*
* @param string $hook Hook.
*/
public static function assets( $hook ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab check only.
if ( 'settings_page_logvia' === $hook && isset( $_GET['tab'] ) && 'appearance' === $_GET['tab'] ) {
wp_enqueue_media();
wp_add_inline_script(
'media-editor',
"document.addEventListener('click',function(e){var b=e.target.closest('[data-logvia-media]');if(!b)return;e.preventDefault();var f=wp.media({library:{type:'image'},multiple:false});f.on('select',function(){var a=f.state().get('selection').first().toJSON();document.getElementById(b.getAttribute('data-logvia-media')).value=a.url;});f.open();});"
);
}
}
/**
* Register the option.
*/
public static function register() {
register_setting(
'logvia',
Logvia_Settings::OPTION,
array(
'type' => 'array',
'sanitize_callback' => array( __CLASS__, 'sanitize' ),
)
);
}
/**
* Sanitize the fields of the submitted tab and keep the rest.
*
* @param mixed $input Submitted values.
* @return array
*/
public static function sanitize( $input ) {
$input = is_array( $input ) ? $input : array();
$old = Logvia_Settings::all();
$tabs = self::tabs();
$tab = isset( $input['_tab'] ) && ! empty( $tabs[ $input['_tab'] ]['fields'] ) ? $input['_tab'] : '';
if ( ! $tab ) {
// Programmatic update_option() call: keep known keys only.
return array_intersect_key( wp_parse_args( $input, $old ), Logvia_Settings::defaults() );
}
$choices = self::choices();
$out = $old;
foreach ( $tabs[ $tab ]['fields'] as $field ) {
$value = isset( $input[ $field ] ) ? $input[ $field ] : null;
if ( in_array( $field, self::checkboxes(), true ) ) {
$out[ $field ] = empty( $value ) ? 0 : 1;
continue;
}
if ( isset( $choices[ $field ] ) ) {
$value = (string) $value;
$out[ $field ] = in_array( $value, $choices[ $field ], true ) ? ( is_numeric( $value ) ? (int) $value : $value ) : Logvia_Settings::defaults()[ $field ];
continue;
}
switch ( $field ) {
case 'page_id':
$out[ $field ] = absint( $value );
break;
case 'min_password':
$out[ $field ] = min( 64, max( 6, absint( $value ) ) );
break;
case 'radius':
$out[ $field ] = min( 30, absint( $value ) );
break;
case 'popup_width':
$out[ $field ] = min( 900, max( 320, absint( $value ) ) );
break;
case 'overlay_opacity':
$out[ $field ] = min( 100, absint( $value ) );
break;
case 'popup_auto_delay':
$out[ $field ] = min( 120, absint( $value ) );
break;
case 'limit_attempts':
$out[ $field ] = min( 50, max( 1, absint( $value ) ) );
break;
case 'limit_window':
$out[ $field ] = min( 1440, max( 1, absint( $value ) ) );
break;
case 'recaptcha_threshold':
$out[ $field ] = min( 1, max( 0, round( (float) $value, 1 ) ) );
break;
case 'default_role':
$value = sanitize_key( (string) $value );
$out[ $field ] = array_key_exists( $value, Logvia_Forms::assignable_roles() ) ? $value : '';
break;
case 'accent':
case 'button_text':
case 'tab_active':
case 'tab_active_text':
case 'overlay_color':
$color = sanitize_hex_color( (string) $value );
$out[ $field ] = $color ? $color : Logvia_Settings::defaults()[ $field ];
break;
case 'redirect_custom':
case 'redirect_register_custom':
case 'redirect_logout_custom':
case 'image':
case 'logo':
$out[ $field ] = esc_url_raw( trim( (string) $value ) );
break;
case 'image_text':
$out[ $field ] = sanitize_textarea_field( (string) $value );
break;
case 'popup_auto_pages':
$out[ $field ] = implode( ',', array_filter( array_map( 'sanitize_title', explode( ',', (string) $value ) ) ) );
break;
default:
$out[ $field ] = sanitize_text_field( (string) $value );
}
}
return $out;
}
/**
* Settings page.
*/
public static function render() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$tabs = self::tabs();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab switch only.
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
$tab = isset( $tabs[ $tab ] ) ? $tab : 'general';
$s = Logvia_Settings::all();
?>
$t ) : ?>
';
return;
}
if ( 'texts' === $tab ) {
Logvia_Forms_Admin::render_texts();
echo '
';
return;
}
?>
>
>
label.
* @param string $desc Description.
*/
public static function select( $s, $key, $title, $options, $desc = '' ) {
$id = 'logvia-' . $key;
?>
$label ) : ?>
>
' . esc_html( $title ) . ' ';
}
/* ------------------------------------------------------------------
* Tabs
* ------------------------------------------------------------------ */
/**
* General tab.
*
* @param array $s Settings.
*/
private static function tab_general( $s ) {
$page = Logvia_Plugin::page_id();
?>
esc_attr( self::name( 'page_id' ) ),
'id' => 'logvia-page_id',
'selected' => (int) $s['page_id'],
'show_option_none' => esc_html__( '— Select —', 'logvia' ),
'option_none_value' => '0',
)
);
?>
__( 'My Account page', 'logvia' ),
'home' => __( 'Home page', 'logvia' ),
'same' => __( 'Stay on the same page', 'logvia' ),
'custom' => __( 'Custom URL', 'logvia' ),
);
self::select( $s, 'redirect_after_login', __( 'After login', 'logvia' ), $common, __( 'Used when the visitor did not come from another page (for example a product they wanted to buy).', 'logvia' ) );
self::text( $s, 'redirect_custom', __( 'Custom URL after login', 'logvia' ), '', 'url', 'large-text' );
self::select( $s, 'redirect_after_register', __( 'After registration', 'logvia' ), array( 'login' => __( 'Same as after login', 'logvia' ) ) + $common );
self::text( $s, 'redirect_register_custom', __( 'Custom URL after registration', 'logvia' ), '', 'url', 'large-text' );
self::select(
$s,
'redirect_after_logout',
__( 'After logout', 'logvia' ),
array(
'login' => __( 'Login screen', 'logvia' ),
'home' => __( 'Home page', 'logvia' ),
'same' => __( 'Stay on the same page', 'logvia' ),
'custom' => __( 'Custom URL', 'logvia' ),
)
);
self::text( $s, 'redirect_logout_custom', __( 'Custom URL after logout', 'logvia' ), '', 'url', 'large-text' );
self::section( __( 'Data', 'logvia' ) );
self::checkbox( $s, 'delete_data', __( 'Uninstall', 'logvia' ), __( 'Delete Logvia settings and the account page when the plugin is deleted', 'logvia' ) );
}
/**
* Registration tab.
*
* @param array $s Settings.
*/
private static function tab_registration( $s ) {
self::checkbox( $s, 'registration', __( 'Registration', 'logvia' ), __( 'Let visitors create accounts', 'logvia' ) );
self::checkbox( $s, 'email_verification', __( 'Email verification', 'logvia' ), __( 'Confirm the email address with a six-digit code before the account is created', 'logvia' ), __( 'Recommended. It keeps fake and mistyped accounts out of your store.', 'logvia' ) );
self::text( $s, 'min_password', __( 'Minimum password length', 'logvia' ), '', 'number', 'small-text' );
$roles = array( '' => __( 'Default (Customer with WooCommerce)', 'logvia' ) ) + Logvia_Forms::assignable_roles();
self::select( $s, 'default_role', __( 'Role of new accounts', 'logvia' ), $roles, __( 'Only roles without admin, editing or shop-management rights can be chosen.', 'logvia' ) );
self::select(
$s,
'reset_method',
__( 'Password reset', 'logvia' ),
array(
'link' => __( 'Email a reset link', 'logvia' ),
'code' => __( 'Email a six-digit code (customers stay on the page)', 'logvia' ),
)
);
}
/**
* Popup tab.
*
* @param array $s Settings.
*/
private static function tab_popup( $s ) {
self::checkbox( $s, 'popup_enabled', __( 'Popup', 'logvia' ), __( 'Open login and registration in a popup', 'logvia' ), __( 'Guests can log in without leaving the page they are on.', 'logvia' ) );
self::select(
$s,
'popup_style',
__( 'Style', 'logvia' ),
array(
'modal' => __( 'Centered window', 'logvia' ),
'slide' => __( 'Slide-in panel', 'logvia' ),
)
);
self::select(
$s,
'popup_side',
__( 'Panel side', 'logvia' ),
array(
'right' => __( 'Right', 'logvia' ),
'left' => __( 'Left', 'logvia' ),
)
);
self::checkbox( $s, 'popup_intercept', __( 'Links', 'logvia' ), __( 'Open the popup when visitors click any link to the login or registration screen', 'logvia' ) );
?>
[logvia_link type="both"]
' . esc_html__( 'Choose image', 'logvia' ) . '';
self::text( $s, 'logo', __( 'Logo', 'logvia' ), __( 'Shown above the forms instead of the icon.', 'logvia' ), 'url', 'regular-text', sprintf( $pick, 'logo' ) );
self::select(
$s,
'layout',
__( 'Page layout', 'logvia' ),
array(
'card' => __( 'Centered card', 'logvia' ),
'split' => __( 'Image on the left, form on the right', 'logvia' ),
),
__( 'The split layout needs an image and works best on full-width page templates.', 'logvia' )
);
self::text( $s, 'image', __( 'Side image', 'logvia' ), __( 'Portrait images of at least 1200×1500 px look best.', 'logvia' ), 'url', 'regular-text', sprintf( $pick, 'image' ) );
self::text( $s, 'image_title', __( 'Image title', 'logvia' ) );
?>
__( 'Off', 'logvia' ),
'turnstile' => __( 'Cloudflare Turnstile (recommended, privacy-friendly)', 'logvia' ),
'recaptcha_v2' => __( 'Google reCAPTCHA v2 (checkbox)', 'logvia' ),
'recaptcha_v3' => __( 'Google reCAPTCHA v3 (invisible)', 'logvia' ),
)
);
self::text( $s, 'captcha_site_key', __( 'Site key', 'logvia' ), '', 'text', 'large-text' );
self::text( $s, 'captcha_secret', __( 'Secret key', 'logvia' ), __( 'Stored on your server only.', 'logvia' ), 'password', 'large-text' );
?>
__( 'Login', 'logvia' ),
'captcha_register' => __( 'Registration', 'logvia' ),
'captcha_forgot' => __( 'Lost password', 'logvia' ),
) as $key => $label
) :
?>
>
__( 'No requirement (length only)', 'logvia' ),
'2' => __( 'At least medium', 'logvia' ),
'3' => __( 'At least strong', 'logvia' ),
)
);
self::section( __( 'Login attempts', 'logvia' ) );
self::text( $s, 'limit_attempts', __( 'Failed logins allowed', 'logvia' ), __( 'Per account. Each IP address may try four times as many accounts.', 'logvia' ), 'number', 'small-text' );
self::text( $s, 'limit_window', __( 'Lockout time (minutes)', 'logvia' ), '', 'number', 'small-text' );
}
/**
* Google tab.
*
* @param array $s Settings.
*/
private static function tab_google( $s ) {
?>
' . esc_html__( 'Install and activate WooCommerce to use these options.', 'logvia' ) . '
';
}
self::checkbox( $s, 'wc_replace_myaccount', __( 'My Account', 'logvia' ), __( 'Replace the WooCommerce login/register form with Logvia', 'logvia' ), __( 'Logged-out visitors of My Account are sent to the Logvia login screen and brought back afterwards.', 'logvia' ) );
self::checkbox( $s, 'wc_checkout_login', __( 'Checkout', 'logvia' ), __( 'Replace the "Returning customer? Click here to login" box on the checkout', 'logvia' ), __( 'Uses the popup when it is on, otherwise an inline form. Needs "Allow customers to log into an existing account during checkout" in WooCommerce settings.', 'logvia' ) );
self::section( __( 'Require an account to check out', 'logvia' ) );
self::checkbox( $s, 'gate_enabled', __( 'Require login', 'logvia' ), __( 'Ask visitors to log in or register before checkout', 'logvia' ), __( 'Guests can fill a cart; at checkout they log in and come straight back with the cart kept.', 'logvia' ) );
self::text( $s, 'gate_notice', __( 'Message on the login screen', 'logvia' ), __( 'Leave empty for the default: "Please log in or create an account to place an order."', 'logvia' ), 'text', 'large-text' );
}
/* ------------------------------------------------------------------
* Misc
* ------------------------------------------------------------------ */
/**
* Create the account page from the settings screen.
*/
public static function create_page() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'logvia' ) );
}
check_admin_referer( 'logvia_create_page' );
Logvia_Plugin::ensure_page();
wp_safe_redirect( admin_url( 'options-general.php?page=logvia' ) );
exit;
}
/**
* Warnings.
*/
public static function notices() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( ! Logvia_Plugin::page_id() ) {
echo '';
}
if ( Logvia_Settings::get( 'google_enabled' ) && ! Logvia_Google::enabled() && Logvia_Plugin::page_id() ) {
echo '' . esc_html__( 'Logvia: Google sign-in is switched on but the Client ID or Client secret is missing.', 'logvia' ) . '
';
}
if ( 'none' !== Logvia_Settings::get( 'captcha' ) && 'none' === Logvia_Captcha::provider() ) {
echo '' . esc_html__( 'Logvia: a captcha is selected but its site key or secret key is missing, so it is not active.', 'logvia' ) . '
';
}
}
/**
* "Settings" link on the Plugins screen.
*
* @param array $links Links.
* @return array
*/
public static function action_links( $links ) {
array_unshift( $links, '' . esc_html__( 'Settings', 'logvia' ) . ' ' );
return $links;
}
/**
* Label the account page in the Pages list.
*
* @param array $states States.
* @param WP_Post $post Post.
* @return array
*/
public static function post_state( $states, $post ) {
if ( (int) $post->ID === (int) Logvia_Settings::get( 'page_id' ) ) {
$states['logvia'] = __( 'Logvia account page', 'logvia' );
}
return $states;
}
}