channels_admin = new Channels_Admin();
$this->channels_admin->register();
$this->tools_admin = new Tools_Admin();
$this->tools_admin->register();
$this->feedback_admin = new Feedback_Admin();
$this->feedback_admin->register();
add_action( 'admin_menu', array( $this, 'add_menu' ) );
add_action( 'admin_init', array( $this, 'register_setting' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'menu_icon_style' ) );
add_action( 'admin_post_aslansheed_save_options', array( $this, 'handle_save_options' ) );
add_filter( 'plugin_action_links_' . ASLANSHEED_OTP_BASENAME, array( $this, 'action_links' ) );
}
/**
* Adds the menu entry.
*
* @return void
*/
public function add_menu() {
add_menu_page(
__( 'Aslansheed Login', 'aslansheed-login' ),
__( 'Aslansheed Login', 'aslansheed-login' ),
'manage_options',
self::PAGE_SLUG,
array( $this, 'render' ),
ASLANSHEED_OTP_URL . 'assets/images/menu-icon.png', // Full-color brand mark, shown at 20 px.
76
);
}
/**
* Keeps the full-color menu icon at full strength and sized to the menu slot.
*
* @return void
*/
public function menu_icon_style() {
wp_register_style( 'aslansheed-otp-menu', false, array(), ASLANSHEED_OTP_VERSION );
wp_enqueue_style( 'aslansheed-otp-menu' );
wp_add_inline_style( 'aslansheed-otp-menu', '#adminmenu .toplevel_page_' . self::PAGE_SLUG . ' .wp-menu-image img{width:20px;height:20px;padding:7px 0;opacity:1;border-radius:5px;box-sizing:content-box;}' );
}
/**
* Registers the settings option with its sanitizer.
*
* @return void
*/
public function register_setting() {
register_setting(
self::SETTINGS_GROUP,
Options::OPTION_NAME,
array(
'type' => 'array',
'sanitize_callback' => array( Options::class, 'sanitize_input' ),
'default' => Options::defaults(),
)
);
}
/**
* Adds a Settings link on the Plugins screen.
*
* @param string[] $links Existing links.
* @return string[]
*/
public function action_links( $links ) {
$url = admin_url( 'admin.php?page=' . self::PAGE_SLUG );
array_unshift( $links, '' . esc_html__( 'Settings', 'aslansheed-login' ) . '' );
return $links;
}
/**
* Returns the tab definitions, grouped for the sidebar.
*
* @return array}>
*/
private function groups() {
return array(
'start' => array(
'title' => '',
'tabs' => array(
'overview' => __( 'Overview', 'aslansheed-login' ),
),
),
'signin' => array(
'title' => __( 'Sign-in', 'aslansheed-login' ),
'tabs' => array(
'general' => __( 'General', 'aslansheed-login' ),
'forms' => __( 'Forms', 'aslansheed-login' ),
'google' => __( 'Google', 'aslansheed-login' ),
'woocommerce' => __( 'WooCommerce', 'aslansheed-login' ),
),
),
'delivery' => array(
'title' => __( 'Delivery', 'aslansheed-login' ),
'tabs' => array(
'otp' => __( 'One-time codes', 'aslansheed-login' ),
'channels' => __( 'Channels', 'aslansheed-login' ),
'providers' => __( 'SMS providers', 'aslansheed-login' ),
'messages' => __( 'Messages', 'aslansheed-login' ),
),
),
'safety' => array(
'title' => __( 'Protection', 'aslansheed-login' ),
'tabs' => array(
'captcha' => __( 'Captcha', 'aslansheed-login' ),
'twofa' => __( 'Two-factor authentication (2FA)', 'aslansheed-login' ),
'security' => __( 'Security', 'aslansheed-login' ),
),
),
'more' => array(
'title' => __( 'More', 'aslansheed-login' ),
'tabs' => array(
'tools' => __( 'Tools', 'aslansheed-login' ),
'deliveries' => __( 'Delivery log', 'aslansheed-login' ),
'audit' => __( 'Audit log', 'aslansheed-login' ),
'about' => __( 'About', 'aslansheed-login' ),
),
),
);
}
/**
* Returns the tab definitions.
*
* @return array
*/
private function tabs() {
$tabs = array();
foreach ( $this->groups() as $group ) {
$tabs = array_merge( $tabs, $group['tabs'] );
}
return $tabs;
}
/**
* Short text shown under the title of each tab.
*
* @param string $tab Tab id.
* @return string
*/
private function intro( $tab ) {
$intro = array(
'overview' => __( 'A quick look at what is switched on and what still needs setting up.', 'aslansheed-login' ),
'general' => __( 'Basics that apply everywhere.', 'aslansheed-login' ),
'forms' => __( 'Choose how people sign in and what you ask them when they register.', 'aslansheed-login' ),
'google' => __( 'Let visitors continue with their Google account.', 'aslansheed-login' ),
'woocommerce' => __( 'Use the same sign-in on My Account and the checkout.', 'aslansheed-login' ),
'otp' => __( 'How long codes are, how long they last and how often they can be requested.', 'aslansheed-login' ),
'channels' => __( 'Channels deliver the codes. Turn on the ones you want to offer. When several are on, visitors choose how they want to receive their code.', 'aslansheed-login' ),
'providers' => __( 'Enable one or more SMS gateways. If a gateway fails, the next one is tried, in the order set on each card. Secrets are encrypted and never shown again.', 'aslansheed-login' ),
'messages' => __( 'The text of the messages that carry the code.', 'aslansheed-login' ),
'captcha' => __( 'Adds a security check to the sign-in form. It protects SMS budgets from bots. The visitor solves it once, when submitting the phone or email.', 'aslansheed-login' ),
'twofa' => __( 'Ask for a code from an authenticator app (Google Authenticator, Microsoft Authenticator, Authy and others) after the first sign-in step.', 'aslansheed-login' ),
'security' => __( 'Limits, block lists and logging.', 'aslansheed-login' ),
'tools' => __( 'Import phone numbers from another plugin and back up your settings.', 'aslansheed-login' ),
'deliveries' => __( 'Every attempt to send a code, including test messages. Recipients are masked.', 'aslansheed-login' ),
'audit' => __( 'Sign-ins, failures and security changes.', 'aslansheed-login' ),
'about' => __( 'Who builds this plugin, and how you can support it.', 'aslansheed-login' ),
);
return isset( $intro[ $tab ] ) ? $intro[ $tab ] : '';
}
/**
* Splits the fields of a tab into titled cards.
*
* Fields that no section lists are gathered in a last card, so a new
* setting is never lost from the screen.
*
* @param string $tab Tab id.
* @return array
*/
public function layout( $tab ) {
$sections = array(
'general' => array(
array( __( 'Basics', 'aslansheed-login' ), '', array( 'general.default_country', 'general.login_redirect', 'general.legacy_phone_meta_keys' ) ),
array( __( 'WordPress login page', 'aslansheed-login' ), __( 'Show this plugin’s form instead of wp-login.php. Set the emergency address first so you can never be locked out.', 'aslansheed-login' ), array( 'general.replace_wp_login', 'general.emergency_slug' ) ),
array( __( 'Data', 'aslansheed-login' ), '', array( 'general.delete_data_on_uninstall' ) ),
),
'forms' => array(
array( __( 'Sign-in methods', 'aslansheed-login' ), __( 'What visitors can type, and how they prove it is theirs.', 'aslansheed-login' ), array( 'forms.accept_phone', 'forms.accept_email', 'forms.accept_username', 'forms.country_selector', 'forms.allow_otp', 'forms.channel_choice', 'forms.allow_password' ) ),
array( __( 'Registration', 'aslansheed-login' ), '', array( 'forms.allow_registration', 'forms.email_verify', 'forms.terms_required', 'forms.terms_url' ) ),
array( __( 'Registration fields', 'aslansheed-login' ), __( 'Choose what new users are asked after their code is verified. Each field can be hidden, optional or required.', 'aslansheed-login' ), array( 'forms.register_name', 'forms.field_first_name', 'forms.field_last_name', 'forms.field_username', 'forms.field_email', 'forms.register_password', 'forms.field_password_confirm', 'forms.password_min_length', 'forms.field_national_id', 'forms.national_id_unique', 'forms.field_postal_code', 'forms.field_birth_date', 'forms.date_calendar' ) ),
array( __( 'Custom fields', 'aslansheed-login' ), __( 'Ask new users for anything else: text, numbers, dates, dropdowns, checkboxes and more. Answers are saved in the user’s profile data.', 'aslansheed-login' ), array( 'forms.custom_fields' ) ),
array( __( 'Appearance and pages', 'aslansheed-login' ), '', array( 'forms.login_page_id', 'forms.accent_color' ) ),
),
'google' => array(
array( __( 'Google sign-in', 'aslansheed-login' ), '', array( 'google.enabled', 'google.client_id', 'forms.google_link_by_email' ) ),
),
'woocommerce' => array(
array( __( 'Account and checkout', 'aslansheed-login' ), '', array( 'woocommerce.replace_account_login', 'woocommerce.verify_checkout_phone' ) ),
array( __( 'Keep customer details in step', 'aslansheed-login' ), '', array( 'woocommerce.sync_billing_phone', 'woocommerce.sync_billing_details' ) ),
),
'otp' => array(
array( __( 'Code', 'aslansheed-login' ), '', array( 'otp.length', 'otp.expiry', 'otp.max_attempts' ) ),
array( __( 'Sending limits', 'aslansheed-login' ), '', array( 'otp.resend_delay', 'otp.send_limit', 'otp.send_window' ) ),
),
'messages' => array(
array( __( 'Message texts', 'aslansheed-login' ), __( 'Tags you can use: {code}, {site_title}, {minutes}.', 'aslansheed-login' ), array( 'channels.message_template', 'channels.email_subject', 'channels.email_body' ) ),
),
'twofa' => array(
array( __( 'Policy', 'aslansheed-login' ), __( 'Off hides the feature. Optional lets people turn it on for themselves and offers it once at sign-in. Required makes the chosen people set it up before they can sign in.', 'aslansheed-login' ), array( 'twofa.mode', 'twofa.scope', 'twofa.roles', 'twofa.users', 'twofa.issuer' ) ),
),
'security' => array(
array( __( 'Rate limits and lockout', 'aslansheed-login' ), '', array( 'security.ip_send_limit', 'security.verify_fail_limit', 'security.lockout_duration' ) ),
array( __( 'Block lists and countries', 'aslansheed-login' ), '', array( 'security.allowed_countries', 'security.blocked_identifiers', 'security.blocked_ips' ) ),
array( __( 'Notifications', 'aslansheed-login' ), '', array( 'security.notify_user_login', 'security.notify_admin_login' ) ),
array( __( 'Network', 'aslansheed-login' ), '', array( 'security.ip_header' ) ),
array( __( 'Audit log', 'aslansheed-login' ), '', array( 'security.audit_enabled', 'security.audit_retention_days' ) ),
),
);
$fields = $this->fields( $tab );
$out = array();
$used = array();
foreach ( isset( $sections[ $tab ] ) ? $sections[ $tab ] : array() as $section ) {
$keys = array_values( array_filter( $section[2], static fn( $key ) => isset( $fields[ $key ] ) ) );
if ( $keys ) {
$out[] = array(
'title' => $section[0],
'desc' => $section[1],
'keys' => $keys,
);
$used = array_merge( $used, $keys );
}
}
$rest = array_values( array_diff( array_keys( $fields ), $used ) );
if ( $rest ) {
$out[] = array(
'title' => __( 'Other', 'aslansheed-login' ),
'desc' => '',
'keys' => $rest,
);
}
return $out;
}
/**
* Tells which settings only matter when another one is on.
*
* @return array Setting key to controlling key and mode, "on" or "shown".
*/
public function dependencies() {
return array(
'forms.country_selector' => array( 'forms.accept_phone', 'on' ),
'forms.channel_choice' => array( 'forms.allow_otp', 'on' ),
'forms.terms_required' => array( 'forms.allow_registration', 'on' ),
'forms.terms_url' => array( 'forms.terms_required', 'on' ),
'forms.email_verify' => array( 'forms.field_email', 'shown' ),
'forms.field_password_confirm' => array( 'forms.register_password', 'shown' ),
'forms.password_min_length' => array( 'forms.register_password', 'shown' ),
'forms.national_id_unique' => array( 'forms.field_national_id', 'shown' ),
'forms.date_calendar' => array( 'forms.field_birth_date', 'shown' ),
'google.client_id' => array( 'google.enabled', 'on' ),
'forms.google_link_by_email' => array( 'google.enabled', 'on' ),
'security.audit_retention_days' => array( 'security.audit_enabled', 'on' ),
'twofa.scope' => array( 'twofa.mode', 'not:off' ),
'twofa.roles' => array( 'twofa.scope', 'is:roles' ),
'twofa.users' => array( 'twofa.scope', 'is:users' ),
'twofa.issuer' => array( 'twofa.mode', 'not:off' ),
);
}
/**
* Returns the fields of a tab with their labels and help text.
*
* @param string $tab Tab id.
* @return array
*/
private function fields( $tab ) {
$all = array(
'general' => array(
'general.default_country' => array(
'label' => __( 'Default country', 'aslansheed-login' ),
'help' => __( 'Used to complete phone numbers typed without a country code.', 'aslansheed-login' ),
),
'general.legacy_phone_meta_keys' => array(
'label' => __( 'Existing phone numbers (user meta keys)', 'aslansheed-login' ),
'help' => __( 'One key per line. When someone signs in with a phone number that is not linked yet, users whose meta value matches that number are found, for example digits_phone. The number is linked after the code is verified. Use only keys that hold verified numbers; billing_phone is not safe because customers type it themselves.', 'aslansheed-login' ),
),
'general.login_redirect' => array(
'label' => __( 'Redirect after login', 'aslansheed-login' ),
'help' => __( 'Leave empty to send users to the page they came from.', 'aslansheed-login' ),
),
'general.replace_wp_login' => array(
'label' => __( 'Replace the WordPress login page', 'aslansheed-login' ),
'help' => __( 'Off by default. Needs an emergency address first. When on, wp-login.php shows this plugin’s form and the emergency address below keeps the classic form available.', 'aslansheed-login' ),
),
'general.emergency_slug' => array(
'label' => __( 'Emergency login address', 'aslansheed-login' ),
'help' => __( 'A secret path that always shows the classic username and password form, for example "backdoor-7f3k".', 'aslansheed-login' ),
),
'general.delete_data_on_uninstall' => array(
'label' => __( 'Delete all data on uninstall', 'aslansheed-login' ),
'help' => __( 'Removes settings, tables and logs when the plugin is deleted.', 'aslansheed-login' ),
),
),
'forms' => array(
'forms.accept_phone' => array(
'label' => __( 'Accept phone numbers', 'aslansheed-login' ),
'help' => '',
),
'forms.accept_email' => array(
'label' => __( 'Accept email addresses', 'aslansheed-login' ),
'help' => '',
),
'forms.accept_username' => array(
'label' => __( 'Accept usernames', 'aslansheed-login' ),
'help' => __( 'Usernames can only sign in with a password.', 'aslansheed-login' ),
),
'forms.allow_otp' => array(
'label' => __( 'Allow sign-in with a code', 'aslansheed-login' ),
'help' => '',
),
'forms.allow_password' => array(
'label' => __( 'Allow sign-in with a password', 'aslansheed-login' ),
'help' => '',
),
'forms.allow_registration' => array(
'label' => __( 'Allow new registrations', 'aslansheed-login' ),
'help' => __( 'A verified phone or email without an account creates one.', 'aslansheed-login' ),
),
'forms.register_name' => array(
'label' => __( 'Display name field', 'aslansheed-login' ),
'help' => __( 'Saved as the WordPress display name and nickname. Shown after the code is verified for new users.', 'aslansheed-login' ),
),
'forms.register_password' => array(
'label' => __( 'Password field', 'aslansheed-login' ),
'help' => __( 'Hidden creates a random password the user can reset later.', 'aslansheed-login' ),
),
'forms.channel_choice' => array(
'label' => __( 'Let visitors choose how to get the code', 'aslansheed-login' ),
'help' => __( 'When more than one channel is on (for example SMS, WhatsApp and Telegram), visitors pick one. With this off, the first working channel is used automatically.', 'aslansheed-login' ),
),
'forms.country_selector' => array(
'label' => __( 'Let visitors pick their country', 'aslansheed-login' ),
'help' => __( 'Shows a country list next to phone numbers, for sites with users from several countries. The list contains the countries set under Security, Allowed phone countries (every country when that is empty). With this off, national numbers use the default country, or the only allowed country.', 'aslansheed-login' ),
),
'forms.email_verify' => array(
'label' => __( 'Verify the email address', 'aslansheed-login' ),
'help' => __( 'When a new user registers with a phone number and gives an email address, a code is sent to that email. Required: the account is created only after the code is entered. Optional: the visitor may skip the step, and the address is marked verified only if they enter the code.', 'aslansheed-login' ),
),
'forms.field_first_name' => array(
'label' => __( 'First name field', 'aslansheed-login' ),
'help' => '',
),
'forms.field_last_name' => array(
'label' => __( 'Last name field', 'aslansheed-login' ),
'help' => '',
),
'forms.field_username' => array(
'label' => __( 'Username field', 'aslansheed-login' ),
'help' => __( 'Hidden creates a username automatically.', 'aslansheed-login' ),
),
'forms.field_email' => array(
'label' => __( 'Email field', 'aslansheed-login' ),
'help' => __( 'Shown only to people who register with a phone number.', 'aslansheed-login' ),
),
'forms.field_password_confirm' => array(
'label' => __( 'Password confirmation field', 'aslansheed-login' ),
'help' => __( 'Used only when the password field is shown.', 'aslansheed-login' ),
),
'forms.password_min_length' => array(
'label' => __( 'Minimum password length', 'aslansheed-login' ),
'help' => '',
),
'forms.field_national_id' => array(
'label' => __( 'National ID field', 'aslansheed-login' ),
'help' => __( 'Validated with the Iranian national code checksum. Stored encrypted.', 'aslansheed-login' ),
),
'forms.national_id_unique' => array(
'label' => __( 'National ID must be unique', 'aslansheed-login' ),
'help' => __( 'Rejects a national ID that another account already uses.', 'aslansheed-login' ),
),
'forms.field_postal_code' => array(
'label' => __( 'Postal code field', 'aslansheed-login' ),
'help' => __( 'Ten digits.', 'aslansheed-login' ),
),
'forms.field_birth_date' => array(
'label' => __( 'Birth date field', 'aslansheed-login' ),
'help' => '',
),
'forms.custom_fields' => array(
'label' => __( 'Fields', 'aslansheed-login' ),
'help' => __( 'Shown after the built-in fields, in this order. Only people who register with a code are asked; Google sign-up skips the form.', 'aslansheed-login' ),
),
'forms.date_calendar' => array(
'label' => __( 'Birth date calendar', 'aslansheed-login' ),
'help' => __( 'The date is always stored as a Gregorian date.', 'aslansheed-login' ),
),
'forms.terms_required' => array(
'label' => __( 'Require accepting terms', 'aslansheed-login' ),
'help' => '',
),
'forms.terms_url' => array(
'label' => __( 'Terms page address', 'aslansheed-login' ),
'help' => '',
),
'forms.login_page_id' => array(
'label' => __( 'Sign-in page', 'aslansheed-login' ),
'help' => __( 'Optional. A page that contains the [aslansheed_login] shortcode. When the WordPress login page is replaced, visitors are sent here. Leave empty to use a built-in page.', 'aslansheed-login' ),
),
'forms.accent_color' => array(
'label' => __( 'Accent color', 'aslansheed-login' ),
'help' => __( 'Hex color such as #2563eb.', 'aslansheed-login' ),
),
),
'google' => array(
'google.enabled' => array(
'label' => __( 'Enable Google sign-in', 'aslansheed-login' ),
'help' => __( 'Create an OAuth client of type "Web application" in Google Cloud Console and add your site address under Authorized JavaScript origins.', 'aslansheed-login' ),
),
'google.client_id' => array(
'label' => __( 'Client ID', 'aslansheed-login' ),
'help' => __( 'A long identifier shown in Google Cloud Console that ends with googleusercontent dot com. No client secret is needed.', 'aslansheed-login' ),
),
'forms.google_link_by_email' => array(
'label' => __( 'Link to existing account by email', 'aslansheed-login' ),
'help' => __( 'If a user with the same verified Google email exists, sign them in to it.', 'aslansheed-login' ),
),
),
'woocommerce' => array(
'woocommerce.replace_account_login' => array(
'label' => __( 'Use the sign-in form on My Account', 'aslansheed-login' ),
'help' => __( 'Replaces the default WooCommerce login and registration forms.', 'aslansheed-login' ),
),
'woocommerce.verify_checkout_phone' => array(
'label' => __( 'Verify the phone number at checkout', 'aslansheed-login' ),
'help' => __( 'Customers must confirm their billing phone with a code before ordering. Works with the classic checkout; the block-based checkout is not supported yet, so leave this off if your store uses it.', 'aslansheed-login' ),
),
'woocommerce.sync_billing_details' => array(
'label' => __( 'Fill billing and shipping details automatically', 'aslansheed-login' ),
'help' => __( 'Copies the first name, last name, email, postal code and country of a new user to the WooCommerce address fields when they are empty. The postal code stays in step when the customer edits the billing address.', 'aslansheed-login' ),
),
'woocommerce.sync_billing_phone' => array(
'label' => __( 'Fill the billing phone automatically', 'aslansheed-login' ),
'help' => __( 'Copies the verified phone number to the billing phone when that field is empty.', 'aslansheed-login' ),
),
),
'otp' => array(
'otp.length' => array(
'label' => __( 'Code length', 'aslansheed-login' ),
'help' => __( 'Number of digits, from 4 to 8.', 'aslansheed-login' ),
),
'otp.expiry' => array(
'label' => __( 'Code lifetime (seconds)', 'aslansheed-login' ),
'help' => __( 'How long a code stays valid.', 'aslansheed-login' ),
),
'otp.resend_delay' => array(
'label' => __( 'Resend delay (seconds)', 'aslansheed-login' ),
'help' => __( 'Minimum wait before a new code can be requested.', 'aslansheed-login' ),
),
'otp.max_attempts' => array(
'label' => __( 'Wrong attempts per code', 'aslansheed-login' ),
'help' => __( 'After this many wrong entries the code is discarded.', 'aslansheed-login' ),
),
'otp.send_limit' => array(
'label' => __( 'Codes per recipient', 'aslansheed-login' ),
'help' => __( 'Maximum codes sent to one number or email inside the window below.', 'aslansheed-login' ),
),
'otp.send_window' => array(
'label' => __( 'Limit window (seconds)', 'aslansheed-login' ),
'help' => __( 'Length of the window used by the send limits.', 'aslansheed-login' ),
),
),
'messages' => array(
'channels.message_template' => array(
'label' => __( 'Text message', 'aslansheed-login' ),
'help' => __( 'Used for SMS, Telegram and WhatsApp text. Tags: {code}, {site_title}, {minutes}.', 'aslansheed-login' ),
),
'channels.email_subject' => array(
'label' => __( 'Email subject', 'aslansheed-login' ),
'help' => __( 'Tags: {code}, {site_title}, {minutes}.', 'aslansheed-login' ),
),
'channels.email_body' => array(
'label' => __( 'Email body', 'aslansheed-login' ),
'help' => __( 'Plain text. Tags: {code}, {site_title}, {minutes}.', 'aslansheed-login' ),
),
),
'twofa' => array(
'twofa.mode' => array(
'label' => __( 'Two-factor authentication (2FA)', 'aslansheed-login' ),
'help' => __( 'Covers this plugin’s form and the classic wp-login.php form. The emergency address never asks for a code, so a lost phone cannot lock you out.', 'aslansheed-login' ),
),
'twofa.scope' => array(
'label' => __( 'Who it covers', 'aslansheed-login' ),
'help' => __( 'Everyone, only the roles you tick, or only the people you list, for example just yourself.', 'aslansheed-login' ),
),
'twofa.roles' => array(
'label' => __( 'Roles', 'aslansheed-login' ),
'help' => __( 'People with any of these roles are covered.', 'aslansheed-login' ),
),
'twofa.users' => array(
'label' => __( 'People', 'aslansheed-login' ),
'help' => __( 'One per line: a username, an email address or a user ID. Enter your own username to turn it on for yourself only.', 'aslansheed-login' ),
),
'twofa.issuer' => array(
'label' => __( 'Name shown in the app', 'aslansheed-login' ),
'help' => __( 'Leave empty to use the site title.', 'aslansheed-login' ),
),
),
'security' => array(
'security.ip_send_limit' => array(
'label' => __( 'Codes per IP address', 'aslansheed-login' ),
'help' => __( 'Maximum codes requested from one IP inside the window. Protects against SMS pumping.', 'aslansheed-login' ),
),
'security.verify_fail_limit' => array(
'label' => __( 'Failed verifications before lockout', 'aslansheed-login' ),
'help' => __( 'Wrong codes allowed for one account before it is locked.', 'aslansheed-login' ),
),
'security.lockout_duration' => array(
'label' => __( 'Lockout duration (seconds)', 'aslansheed-login' ),
'help' => __( 'How long a locked account or IP must wait.', 'aslansheed-login' ),
),
'security.blocked_identifiers' => array(
'label' => __( 'Blocked phones and emails', 'aslansheed-login' ),
'help' => __( 'One per line. Use * as a wildcard, for example *@spam.example or +98912*.', 'aslansheed-login' ),
),
'security.blocked_ips' => array(
'label' => __( 'Blocked IP addresses', 'aslansheed-login' ),
'help' => __( 'One per line. Use * as a wildcard, for example 198.51.100.*.', 'aslansheed-login' ),
),
'security.allowed_countries' => array(
'label' => __( 'Allowed phone countries', 'aslansheed-login' ),
'help' => __( 'Two-letter codes separated by commas or lines, for example US, GB, DE. Leave empty to accept every country. Limiting countries is the best protection against SMS pumping fraud. Countries that share a calling code, such as the US and Canada, are accepted together.', 'aslansheed-login' ),
),
'security.notify_user_login' => array(
'label' => __( 'Email users about sign-ins', 'aslansheed-login' ),
'help' => __( 'Sends a short notice when someone signs in with this plugin.', 'aslansheed-login' ),
),
'security.notify_admin_login' => array(
'label' => __( 'Email the site admin about administrator sign-ins', 'aslansheed-login' ),
'help' => '',
),
'security.ip_header' => array(
'label' => __( 'Client IP source', 'aslansheed-login' ),
'help' => __( 'Only change this when your site is behind a trusted proxy such as Cloudflare.', 'aslansheed-login' ),
),
'security.audit_enabled' => array(
'label' => __( 'Enable audit log', 'aslansheed-login' ),
'help' => __( 'Records logins, failures and security changes. Codes and secrets are never stored.', 'aslansheed-login' ),
),
'security.audit_retention_days' => array(
'label' => __( 'Keep audit log for (days)', 'aslansheed-login' ),
'help' => __( '0 keeps entries forever.', 'aslansheed-login' ),
),
),
);
return isset( $all[ $tab ] ) ? $all[ $tab ] : array();
}
/**
* Loads the admin styles and script on this screen only.
*
* @param string $hook Admin page hook.
* @return void
*/
public function enqueue( $hook ) {
if ( 'toplevel_page_' . self::PAGE_SLUG !== $hook ) {
return;
}
wp_enqueue_style( 'aslansheed-otp-admin', ASLANSHEED_OTP_URL . 'assets/css/admin.css', array( 'dashicons' ), Assets::version( 'assets/css/admin.css' ) );
wp_enqueue_script( 'aslansheed-otp-admin', ASLANSHEED_OTP_URL . 'assets/js/admin.js', array(), Assets::version( 'assets/js/admin.js' ), true );
wp_localize_script(
'aslansheed-otp-admin',
'AslansheedAdmin',
array(
'ajaxUrl' => admin_url( 'admin-post.php' ),
'i18n' => array(
'saving' => __( 'Saving…', 'aslansheed-login' ),
'saved' => __( 'Settings saved.', 'aslansheed-login' ),
'failed' => __( 'Could not save. Please try again.', 'aslansheed-login' ),
'network' => __( 'Connection problem. Please try again.', 'aslansheed-login' ),
'unsaved' => __( 'You have unsaved changes.', 'aslansheed-login' ),
'sending' => __( 'Sending…', 'aslansheed-login' ),
'on' => __( 'On', 'aslansheed-login' ),
'off' => __( 'Off', 'aslansheed-login' ),
'savedHint' => __( 'Saved. Type to replace.', 'aslansheed-login' ),
),
)
);
}
/**
* Renders the page.
*
* @return void
*/
public function render() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to access this page.', 'aslansheed-login' ) );
}
$tabs = $this->tabs();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only tab selection.
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'overview';
$tab = isset( $tabs[ $tab ] ) ? $tab : 'overview';
echo '
';
}
/**
* Renders the About tab: the developer, the services and the donation link.
*
* Everything here stays on this tab. The plugin shows no notices or banners elsewhere.
*
* @return void
*/
private function render_about() {
$links = array(
'site' => 'https://aslansheed.ir',
'lab' => 'https://sheedcore.aslansheed.ir',
'donate' => 'https://sheedcore.aslansheed.ir/Donate',
'support' => 'https://wordpress.org/support/plugin/aslansheed-login/',
'review' => 'https://wordpress.org/support/plugin/aslansheed-login/reviews/#new-post',
);
Admin_Ui::card_open( __( 'Free, with nothing held back', 'aslansheed-login' ), __( 'Every feature is included. There are no paid tiers, no locked options and no ads.', 'aslansheed-login' ), array( 'class' => 'aslo-about-hero' ) );
echo '
' . esc_html__( 'Aslansheed Login is built and maintained by the Aslansheed team. If it saves you time or money, you can help us keep improving it.', 'aslansheed-login' ) . '
' . esc_html__( 'We design websites and stay with you after launch. Need a new site, or help with one you already have? Talk to us.', 'aslansheed-login' ) . '
' . esc_html__( 'The lab is where we build and sell products: WordPress plugins, Telegram bots and other tools for your business.', 'aslansheed-login' ) . '
';
}
/**
* Renders the overview: what is on, what is missing and the latest deliveries.
*
* @return void
*/
private function render_overview() {
$summary = $this->channels_admin->summary();
$checks = array(
array( __( 'A delivery method is enabled', 'aslansheed-login' ), $summary['channels_on'] > 0 || $summary['providers_on'] > 0, 'channels', __( 'Enable email, an SMS provider or another channel.', 'aslansheed-login' ) ),
array( __( 'Codes can reach phones', 'aslansheed-login' ), $summary['providers_on'] > 0 || $summary['phone_channels_on'] > 0, 'providers', __( 'Enable an SMS provider, WhatsApp or Telegram.', 'aslansheed-login' ) ),
array( __( 'Bots are filtered', 'aslansheed-login' ), $summary['captcha'], 'captcha', __( 'Turn on a captcha to protect your SMS budget.', 'aslansheed-login' ) ),
array( __( 'Phone countries are limited', 'aslansheed-login' ), array() !== \Aslansheed\OTP\Security\Access_Control::allowed_countries(), 'security', __( 'List the countries you serve to stop SMS pumping.', 'aslansheed-login' ) ),
);
echo '
';
Admin_Ui::card_open( __( 'Setup checklist', 'aslansheed-login' ), __( 'A few things worth checking before you go live.', 'aslansheed-login' ) );
echo '