__( 'For a group chat, add the bot to the group first.', 'abds-connect' ), 'Leads from these forms go to Telegram automatically when the source is enabled below.' => __( 'Leads from these forms go to Telegram automatically when the source is enabled below.', 'abds-connect' ), 'Token saved. Leave blank to keep it unchanged.' => __( 'Token saved. Leave blank to keep it unchanged.', 'abds-connect' ), 'Get it from @BotFather (step 1 above).' => __( 'Get it from @BotFather (step 1 above).', 'abds-connect' ), /* translators: 1: @BotFather, 2: /newbot, both Telegram-specific terms wrapped in . */ 'In Telegram, message %1$s, send the %2$s command, and copy the token it gives you.' => __( 'In Telegram, message %1$s, send the %2$s command, and copy the token it gives you.', 'abds-connect' ), 'No lead data stored by the plugin — each submission is forwarded to your Telegram and nothing more.' => __( 'No lead data stored by the plugin — each submission is forwarded to your Telegram and nothing more.', 'abds-connect' ), /* translators: 1: the getUpdates API URL, 2: chat.id, both wrapped in . */ 'Find your Chat ID: message your new bot anything, then open %1$s in your browser — %2$s will be in the response.' => __( 'Find your Chat ID: message your new bot anything, then open %1$s in your browser — %2$s will be in the response.', 'abds-connect' ), 'Form leads forwarded to Telegram the moment they’re submitted — the source page and every field.' => __( 'Form leads forwarded to Telegram the moment they’re submitted — the source page and every field.', 'abds-connect' ), ); $canary = new ABDS_Translation_Canary( 'abds-connect', ABDS_CONNECT_VER, $canary_translations, array( 'notice_message' => __( "This plugin's translation for your site's language appears to be installed, but some screens are still showing in English. Try re-downloading translations (Dashboard → Updates → \"Update translations\") to see if that resolves it.", 'abds-connect' ), 'dismiss_label' => __( 'Got it, dismiss', 'abds-connect' ), 'permission_denied' => __( 'You do not have permission to perform this action.', 'abds-connect' ), ), 'manage_options' ); $canary->register_hooks(); } // Deferred to 'init', not called directly here: this function's __() calls // resolve real translations, and this file's top-level code runs before // 'plugins_loaded' even fires -- calling __() that early trips WordPress's // own "translation loading triggered too early" _doing_it_wrong() notice // (added 6.7), confirmed live on ABDS SEO's identical pattern 2026-09-02. // 'init' is WordPress's own stated minimum safe point. add_action( 'init', 'abds_connect_translation_canary_init' ); /* ------------------------- Активація ------------------------- */ /** * Fires on the free plugin's activation. The Pro add-on registers its own * activation hook for its tables; nothing paid is installed from here. */ function abds_connect_activate() { if ( ! wp_next_scheduled( 'abds_ln_telegram_daily' ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'twicedaily', 'abds_ln_telegram_daily' ); } do_action( 'abds_connect_activate' ); } register_activation_hook( __FILE__, 'abds_connect_activate' ); /** * Stops the background bot-status re-check on deactivation. */ function abds_connect_deactivate() { wp_clear_scheduled_hook( 'abds_ln_telegram_daily' ); } register_deactivation_hook( __FILE__, 'abds_connect_deactivate' ); /* ------------------------- Міграція старих опцій (free) ------------------------- */ /** * Migrates option names from the plugin's earliest (nt_*) incarnation, and * lets the Pro add-on run its own migrations via the abds_connect_migrate * action. */ function abds_connect_migrate() { // З найпершої версії (nt_*) плагіна сповіщень — стосується лише заявок (free). foreach ( array( 'nt_bot_token' => 'abds_ln_bot_token', 'nt_chat_id' => 'abds_ln_chat_id', 'nt_enabled' => 'abds_ln_enabled', ) as $old => $new ) { $v = get_option( $old, null ); if ( null !== $v && '' === (string) get_option( $new, '' ) ) { update_option( $new, $v ); } } /** Lets the Pro add-on run its own migrations (e.g. ensure its chat tables). */ do_action( 'abds_connect_migrate' ); } add_action( 'admin_init', 'abds_connect_migrate' ); /* ------------------------- Налаштування ------------------------- */ /** * Sanitizes the submitted licence key for a write-only field: a blank * submission means "leave unchanged" (the field never echoes the real * stored key back for the user to accidentally clear), not "clear it" — * same pattern as abds_ln_sanitize_bot_token() in includes/leads.php. * * @param mixed $value Raw submitted value. * @return string */ function abds_connect_sanitize_license_key( $value ) { $value = trim( (string) sanitize_text_field( $value ) ); if ( '' === $value ) { return (string) get_option( 'abds_license_key', '' ); } return $value; } /** * Registers the free plugin's own setting: the shared licence key (read by * both this plugin and, once installed, the Pro add-on). */ function abds_connect_register_core() { register_setting( 'abds_connect', 'abds_license_key', array( 'sanitize_callback' => 'abds_connect_sanitize_license_key', 'default' => '', ) ); } add_action( 'admin_init', 'abds_connect_register_core' ); /** * The admin-menu icon as a base64 SVG data URI — a monochrome silhouette (a * chat bubble with three text-line cutouts, single flat grey fill), the same * pattern as ABDS SEO's own menu_icon(): WordPress dims a data-URI menu icon * via opacity rather than recoloring it per admin colour scheme, so a single * flat grey (not a per-scheme colour swap) is the correct source colour here. * The full-colour logo for real inline-image contexts (the settings page * header) lives separately in assets/img/icon.svg. * * Source: assets-wporg/menu-icon.svg, verified against the sidebar at 20/40/80px * — used as-is, not redrawn. * * @return string A data:image/svg+xml;base64 URI. */ function abds_connect_menu_icon() { $svg = ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- encoding an inline SVG for a menu-icon data URI, not code obfuscation. return 'data:image/svg+xml;base64,' . base64_encode( $svg ); } /** * Registers the top-level ABDS Connect admin menu page. */ function abds_connect_menu() { $hook = add_menu_page( 'ABDS Connect', 'ABDS Connect', 'manage_options', 'abds-connect', 'abds_connect_page', abds_connect_menu_icon(), 58 ); add_action( 'admin_enqueue_scripts', function ( $current_hook ) use ( $hook ) { abds_connect_enqueue_settings_assets( $current_hook, $hook ); } ); $plans_hook = add_submenu_page( 'abds-connect', __( 'Plans', 'abds-connect' ), __( 'Plans', 'abds-connect' ), 'manage_options', 'abds-connect-plans', 'abds_connect_render_plans_page' ); add_action( 'admin_enqueue_scripts', function ( $current_hook ) use ( $plans_hook ) { abds_connect_enqueue_plans_assets( $current_hook, $plans_hook ); } ); } add_action( 'admin_menu', 'abds_connect_menu' ); /** * Loads the Plans screen's CSS, only on that one screen. * * @param string $current_hook The hook suffix of the screen being rendered. * @param string $our_hook The Plans screen's own hook suffix. */ function abds_connect_enqueue_plans_assets( $current_hook, $our_hook ) { if ( $current_hook !== $our_hook ) { return; } wp_enqueue_style( 'abds-connect-tokens', ABDS_CONNECT_URL . 'assets/css/tokens.css', array(), ABDS_CONNECT_VER ); wp_enqueue_style( 'abds-connect-plans', ABDS_CONNECT_URL . 'assets/css/plans-page.css', array( 'abds-connect-tokens' ), ABDS_CONNECT_VER ); } /** * Renders the Plans screen from its own partial (no markup in this file). */ function abds_connect_render_plans_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $pro = abds_is_pro(); require ABDS_CONNECT_DIR . 'includes/admin/plans-page.php'; } /** * Loads the settings screen's JS/CSS, only on that one screen. * * @param string $current_hook The hook suffix of the screen being rendered. * @param string $our_hook The ABDS Connect settings screen's own hook suffix. */ function abds_connect_enqueue_settings_assets( $current_hook, $our_hook ) { if ( $current_hook !== $our_hook ) { return; } wp_enqueue_style( 'abds-connect-tokens', ABDS_CONNECT_URL . 'assets/css/tokens.css', array(), ABDS_CONNECT_VER ); wp_enqueue_style( 'abds-connect-admin', ABDS_CONNECT_URL . 'assets/css/admin-settings.css', array( 'abds-connect-tokens' ), ABDS_CONNECT_VER ); wp_enqueue_script( 'abds-connect-admin', ABDS_CONNECT_URL . 'assets/js/admin-settings.js', array( 'wp-i18n' ), ABDS_CONNECT_VER, true ); wp_set_script_translations( 'abds-connect-admin', 'abds-connect', ABDS_CONNECT_DIR . 'languages' ); wp_localize_script( 'abds-connect-admin', 'abdsConnectSettings', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'abds_connect_test_message' ), ) ); } /** * Adds a "Налаштування" (Settings) link to the plugin's row on the Plugins * screen. * * @param string[] $links Existing action links. * @return string[] */ function abds_connect_action_links( $links ) { $links[] = '' . esc_html__( 'Settings', 'abds-connect' ) . ''; return $links; } add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'abds_connect_action_links' ); /** * Renders the ABDS Connect settings page: the leads → Telegram module, the * detected form sources, the licence field, and (via action hooks) the Pro * add-on's own sections when it's installed. Native wp-admin `.card` groups, * in onboarding order — Telegram first, since it's the only thing a fresh * install needs before it does anything useful. */ function abds_connect_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $pro = abds_is_pro(); $has_token = '' !== trim( (string) get_option( 'abds_ln_bot_token', '' ) ); $tg = abds_ln_telegram_status(); ?>

ABDS Connect

ABDS Connect Pro' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted brand-name markup, not user input. ); ?>

1.

. */ esc_html__( 'In Telegram, message %1$s, send the %2$s command, and copy the token it gives you.', 'abds-connect' ), '@BotFather', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted markup, not user input. '/newbot' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted markup, not user input. ); ?>

. */ esc_html__( 'Find your Chat ID: message your new bot anything, then open %1$s in your browser — %2$s will be in the response.', 'abds-connect' ), 'https://api.telegram.org/bot<token>/getUpdates', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted markup, not user input. 'chat.id' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted markup, not user input. ); ?>

$def ) { if ( ! abds_ln_source_on( $key ) ) { continue; } if ( call_user_func( $def[1] ) ) { $enabled_found[] = $def[0]; } else { $enabled_missing[] = $def[0]; } } if ( ! empty( $enabled_missing ) && empty( $enabled_found ) ) : ?>

    $def ) : ?>
  • ' . esc_html( $name ) . ''; }, $idance_names ) ); ?>

    . */ esc_html__( 'Currently accepted: %s. Only a submission using one of these exact action names is treated as a lead.', 'abds-connect' ), $idance_names_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- each name already esc_html()'d before wrapping in static trusted markup. ); ?>

    . */ esc_html__( 'Saw a submission using action %s — if that\'s your form, paste it above and save.', 'abds-connect' ), '' . esc_html( $idance_seen ) . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc_html() already applied to the option value before wrapping in static trusted markup. ); ?>

ABDS Connect Pro' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted brand-name markup, not user input. ); ?> ' . esc_html__( 'Free', 'abds-connect' ) . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- the inner esc_html__() already escapes the translated text; the wrapper is static trusted markup. ); ?>

Pro

ABDS Connect Pro', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted brand-name markup, not user input. '' . esc_html__( 'Learn more', 'abds-connect' ) . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static trusted link markup; the link text itself is already escaped via esc_html__(). ); ?>


__( 'Insufficient permissions.', 'abds-connect' ) ), 403 ); } check_ajax_referer( 'abds_connect_test_message', 'nonce' ); $token = trim( (string) get_option( 'abds_ln_bot_token', '' ) ); $chat = trim( (string) get_option( 'abds_ln_chat_id', '' ) ); if ( '' === $token || '' === $chat ) { wp_send_json_error( array( 'message' => __( 'Fill in the token and Chat ID first, and save the settings.', 'abds-connect' ) ) ); } // The test message itself lands in the site owner's own Telegram chat — // same audience as the admin UI (see includes/leads.php's notification // strings), so it goes through __() the same way. The checkmark is purely // decorative and stays outside the translatable string. $body = '✅ ' . sprintf( /* translators: %s: site name. */ __( 'Lead test — %s', 'abds-connect' ), get_bloginfo( 'name' ) ) . "\n" . sprintf( /* translators: %s: date and time. */ __( 'Time: %s', 'abds-connect' ), wp_date( 'Y-m-d H:i' ) ); $res = abds_ln_send( $token, $chat, $body ); if ( is_wp_error( $res ) ) { wp_send_json_error( array( /* translators: %s: the transport/API error message. */ 'message' => sprintf( __( 'Could not send: %s', 'abds-connect' ), $res->get_error_message() ), ) ); } wp_send_json_success( array( 'message' => __( 'Sent — check the chat.', 'abds-connect' ) ) ); } add_action( 'wp_ajax_abds_connect_test_message', 'abds_connect_ajax_test_message' ); /* ------------------------- Повідомлення ------------------------- */ /** * Renders the post-redirect admin notice for the settings page (test-lead * result, chat-webhook connect/disconnect result), keyed by the * `abds_notice` query arg. */ function abds_connect_notices() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display of a post-redirect admin-notice flag; sanitized below, changes no state. if ( ! isset( $_GET['page'], $_GET['abds_notice'] ) || 'abds-connect' !== $_GET['page'] ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- same read-only notice flag as the guard above; mapped to a fixed message. $n = sanitize_key( wp_unslash( $_GET['abds_notice'] ) ); $map = array( 'hookok' => array( 'success', __( 'Chat webhook connected — Telegram replies will now reach the chat.', 'abds-connect' ) ), 'hookfailing' => array( 'error', __( 'Webhook connected, but Telegram reports delivery failures. Check the site\'s SSL certificate or whether a security plugin is blocking the REST API.', 'abds-connect' ) ), 'hookmismatch' => array( 'error', __( 'The request went through, but Telegram still shows a different webhook address. Try again in a minute.', 'abds-connect' ) ), 'hookfail' => array( 'error', __( 'Could not connect the chat webhook. Check the chat bot token.', 'abds-connect' ) ), 'hookcheckfail' => array( 'error', __( 'Webhook request sent, but confirming it failed — could not reach Telegram. Refresh the page in a few minutes and check the status above.', 'abds-connect' ) ), 'hookdel' => array( 'success', __( 'Chat webhook disconnected.', 'abds-connect' ) ), ); if ( isset( $map[ $n ] ) ) { printf( '

%s

', esc_attr( $map[ $n ][0] ), esc_html( $map[ $n ][1] ) ); } } add_action( 'admin_notices', 'abds_connect_notices' );