version = $version; } /** * Fetches and populates user account details from the remote API. * */ private function load_account() { $api_key = AltTextLab_Utility::get_api_key(); $this->account = array( 'plan' => '', 'isActive' => '', 'credits' => '', 'nextReceiving' => '', 'nextReceivingAt' => '', ); if ( empty( $api_key ) ) { return; } $api = new AltTextLab_API( $api_key ); $this->account = $api->get_account(); } /** * Returns the transient key used to review block. */ private function get_account_review_block_hidden_key( $user_id ) { return 'alttextlab_account_review_block_hidden_' . absint( $user_id ); } /** * Returns the total number of generated alt texts in history. */ private function get_total_generated_assets_count() { $storage = new AltTextLab_Storage(); return (int) $storage->get_count_total_assets(); } /** * Prepares the review block visibility state for the account page. */ private function load_review_block_state() { $user_id = get_current_user_id(); $this->review_block_generated_count = $this->get_total_generated_assets_count(); $has_valid_api_key = ! empty( AltTextLab_Utility::get_api_key() ) && is_array( $this->account ); $has_positive_credits = $has_valid_api_key && isset( $this->account['credits'] ) && (int) $this->account['credits'] > 0; $is_hidden = (bool) get_transient( $this->get_account_review_block_hidden_key( $user_id ) ); $review_block_minimum_generated_count = 150; $this->show_review_block = $this->review_block_generated_count >= $review_block_minimum_generated_count && $has_positive_credits && ! $is_hidden; } /** * Incorporates CSS resources needed for the administration dashboard. * */ public function enqueue_styles() { wp_enqueue_style( 'alttextlab-admin', plugin_dir_url( __FILE__ ) . 'css/tailwind.min.css', array(), $this->version, 'all' ); } /** * Creates and configures the admin menu pages for plugin configuration. * */ public function register_settings_pages() { // Main page add_menu_page( __( 'AltTextLab WordPress Settings', 'alttextlab' ), __( 'AltTextLab', 'alttextlab' ), 'manage_options', 'alttextlab-account-setting', array( $this, 'render_account_settings_page' ), plugin_dir_url(__FILE__) . 'img/logo_white_17x17.svg' ); // Account Setting Page $hook_suffix = add_submenu_page( 'alttextlab-account-setting', __( 'Account Settings', 'alttextlab' ), __( 'Account Settings', 'alttextlab' ), 'manage_options', 'alttextlab-account-setting' ); add_action("admin_head-{$hook_suffix}", array($this, 'enqueue_styles') ); $hook_suffix = add_submenu_page( 'alttextlab-account-setting', __( 'AltTextLab WordPress Settings', 'alttextlab' ), __( 'Settings', 'alttextlab' ), 'manage_options', 'alttextlab', array( $this, 'render_settings_page' ) ); add_action("admin_head-{$hook_suffix}", array($this, 'enqueue_styles') ); // Bulk Generate Page $hook_suffix = add_submenu_page( 'alttextlab-account-setting', __( 'Bulk Generation', 'alttextlab' ), __( 'Bulk Generation', 'alttextlab' ), 'manage_options', 'alttextlab-bulk-generate', array( $this, 'render_bulk_generate_page' ) ); add_action("admin_head-{$hook_suffix}", array($this, 'enqueue_styles') ); // Sync Alt in Content Page $hook_suffix = add_submenu_page( 'alttextlab-account-setting', __( 'Sync Alt Text in Content', 'alttextlab' ), __( 'Sync Alt Text in Content', 'alttextlab' ), 'manage_options', 'alttextlab-sync-alt-in-posts', array( $this, 'render_sync_alt_in_posts_page' ) ); add_action("admin_head-{$hook_suffix}", array($this, 'enqueue_styles') ); // History Page if ( AltTextLab_Utility::get_api_key() ) { $hook_suffix = add_submenu_page( 'alttextlab-account-setting', __( 'History', 'alttextlab' ), __( 'History', 'alttextlab' ), 'manage_options', 'alttextlab-history', array( $this, 'render_history_page' ) ); add_action("admin_head-{$hook_suffix}", array($this, 'enqueue_styles') ); } } /** * Establishes and defines the user account options. * */ public function register_account_settings() { register_setting( 'alttextlab-account-settings', 'alttextlab_api_key', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ) ); } /** * Establishes and defines the plugin's configuration options. * */ public function register_settings() { register_setting( 'alttextlab-settings', 'alttextlab_lang', array( 'type' => 'string', 'sanitize_callback' => array($this, 'sanitize_language_option'), 'default' => 'en', ) ); register_setting( 'alttextlab-settings', 'alttextlab_model_name', array( 'type' => 'string', 'sanitize_callback' => array($this, 'sanitize_model_name_option'), 'default' => null, ) ); register_setting( 'alttextlab-settings', 'alttextlab_update_title', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_update_caption', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_update_description', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_auto_generation_enabled', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'yes', ) ); register_setting( 'alttextlab-settings', 'alttextlab_keywords', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'yes', ) ); register_setting( 'alttextlab-settings', 'alttextlab_use_post_title_as_context', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_ecomm', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'yes', ) ); register_setting( 'alttextlab-settings', 'alttextlab_ecomm_use_title', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_ecomm_use_color', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_ecomm_use_material', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_ecomm_color_field', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ) ); register_setting( 'alttextlab-settings', 'alttextlab_ecomm_material_field', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ) ); register_setting( 'alttextlab-settings', 'alttextlab_public', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'yes' ) ); register_setting( 'alttextlab-settings', 'alttextlab_alt_prefix', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ) ); register_setting( 'alttextlab-settings', 'alttextlab_alt_suffix', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ) ); register_setting( 'alttextlab-settings', 'alttextlab_timeout', array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 20, ) ); register_setting( 'alttextlab-settings', 'alttextlab_use_single_lang', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_yes_no_checkbox' ), 'default' => 'no', ) ); register_setting( 'alttextlab-settings', 'alttextlab_wpml_selected_langs', array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_selected_wpml_languages' ), ) ); } /** * Validates and normalizes checkbox values to guarantee they are either 'yes' or 'no'. * */ public function sanitize_yes_no_checkbox( $input ) { return $input === 'yes' ? 'yes' : 'no'; } /** * Sanitizes language selection input * */ public function sanitize_language_option( $input ){ $available_languages = AltTextLab_Utility::supported_languages(); return array_key_exists($input, $available_languages) ? $input : 'en'; } /** * Sanitizes model names selection input * */ public function sanitize_model_name_option( $input ){ $available_model_names = AltTextLab_Utility::supported_model_names(); return in_array($input, $available_model_names, true) ? $input : null; } /** * Displays the interface for account settings. * */ public function render_account_settings_page() { $this->load_account(); $this->load_review_block_state(); require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/account-settings.php'; } /** * Displays the interface for batch alt text generation. * */ public function render_bulk_generate_page() { $this->load_account(); require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/bulk-generate.php'; } /** * Displays the interface for batch alt text sync. * */ public function render_sync_alt_in_posts_page() { $this->load_account(); require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/sync-alt-in-posts.php'; } /** * Presents the log of past alt text generation activities. * */ public function render_history_page() { $this->load_account(); require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/history.php'; } /** * Generates the configuration interface for plugin options. * */ public function render_settings_page() { $this->load_account(); require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/settings.php'; } /** * Manages the storage or removal of API authentication credentials. * */ public function save_api_key( $api_key, $old_api_key ) { $delete = is_null( $api_key ); if ( $delete ) { delete_option( 'alttextlab_api_key' ); } if ( empty( $api_key ) ) { return $api_key; } if ( $api_key === '*********' ) { return $old_api_key; } $api = new AltTextLab_API( $api_key ); if ( ! $api->get_account() ) { $invalid_key_message = wp_kses( __( 'The API key you entered is not valid.
Please check that it was copied correctly or create a new API key in your AltTextLab dashboard.', 'alttextlab' ), array( 'br' => array() ) ); add_settings_error( 'invalid-api-key', '', $invalid_key_message ); return $old_api_key; } // Add custom success message /* translators: %s: URL for the Bulk Generate feature. */ $message = __( 'API Key saved. Pro tip: Add alt text to all your existing images with our Bulk Generate feature!', 'alttextlab' ); $message = sprintf( $message, admin_url( 'admin.php?page=alttextlab-bulk-generate' ) ); $message = wp_kses( $message, array( 'a' => array( 'href' => array(), 'class' => array(), ), ) ); add_settings_error( 'alttextlab_api_key_updated', '', $message, 'updated' ); return $api_key; } /** * Purges stored error records when the page is initialized * */ public function clear_error_logs() { if ( ! isset( $_GET['alttextlab_action'] ) ) { return; } if ( $_GET['alttextlab_action'] !== 'clear-error-logs' ) { return; } delete_option( 'alttextlab_error_logs' ); wp_safe_redirect( add_query_arg( 'alttextlab_action', false ) ); } /** * Removes the temporary warning about insufficient credits to prevent it from showing again. * */ public function expire_insufficient_credits_notice() { check_ajax_referer( 'alttextlab_insufficient_credits_notice', 'security' ); delete_transient( 'alttextlab_insufficient_credits' ); wp_send_json( array( 'status' => 'success', 'message' => __( 'Notice expired.', 'alttextlab' ), ) ); } /** * Removes the temporary warning about the monthly token limit to prevent it from showing again. * */ public function expire_monthly_limit_notice() { check_ajax_referer( 'alttextlab_monthly_limit_notice', 'security' ); delete_transient( 'alttextlab_monthly_limit_exceeded' ); wp_send_json( array( 'status' => 'success', 'message' => __( 'Notice expired.', 'alttextlab' ), ) ); } /** * Cleans up the browser address by eliminating the API key warning parameter. * */ public function remove_api_key_missing_param() { if ( ! isset( $_GET['api_key_missing'] ) ) { return; } $api_key = AltTextLab_Utility::get_api_key(); if ( empty( $api_key ) ) { return; } $current_url = ( is_ssl() ? 'https://' : 'http://' ) . ( isset($_SERVER['HTTP_HOST']) ? sanitize_text_field($_SERVER['HTTP_HOST']) : '') . ( isset($_SERVER['REQUEST_URI']) ? sanitize_url($_SERVER['REQUEST_URI']) : ''); $updated_url = remove_query_arg( 'api_key_missing', $current_url ); if ( $current_url !== $updated_url ) { wp_safe_redirect( $updated_url ); exit; } } /** * Handles user preferences for the review prompt banner * */ function handleReviewBannerSettings() { check_ajax_referer('alttextlab_security_review_settings', 'security'); $setting = sanitize_text_field($_POST['setting'] ? : ''); $user_id = get_current_user_id(); if ($setting === 'later') { set_transient('alttextlab_review_hidden_' . $user_id, true, 7 * DAY_IN_SECONDS); } elseif ($setting === 'never') { set_transient('alttextlab_review_hidden_' . $user_id, true, 0); } } /** * Handles user preferences for the account review block. */ public function handleAccountReviewBlockSettings() { check_ajax_referer( 'alttextlab_security_account_review_block_settings', 'security' ); $setting = sanitize_text_field( $_POST['setting'] ? : '' ); $user_id = get_current_user_id(); $account_review_key = $this->get_account_review_block_hidden_key( $user_id ); $allowed_settings = array( 'alttextlab-review-block-maybe-later-button', 'alttextlab-review-block-dont-show-again-button', 'alttextlab-review-block-skip-button', 'account_not_really', ); if ( ! in_array( $setting, $allowed_settings, true ) ) { wp_send_json( array( 'status' => 'error', 'message' => __( 'Unknown account review setting.', 'alttextlab' ), ) ); } if ( $setting === 'alttextlab-review-block-maybe-later-button' ) { set_transient( $account_review_key, true, 14 * DAY_IN_SECONDS ); } elseif ( $setting === 'alttextlab-review-block-skip-button' ) { set_transient( $account_review_key, true, 45 * DAY_IN_SECONDS ); } elseif ( $setting === 'account_not_really' || $setting === 'alttextlab-review-block-dont-show-again-button' ) { set_transient( $account_review_key, true, 0 ); } wp_send_json( array( 'status' => 'success', 'message' => __( 'Account review block preference saved.', 'alttextlab' ), ) ); } /** * Sanitizes selected WPML languages. */ public function sanitize_selected_wpml_languages( $input ) { if ( $input === null || $input === '' || ! is_array( $input ) ) { return array(); } $active_languages = apply_filters( 'wpml_active_languages', null ); $valid_codes = is_array( $active_languages ) ? array_keys( $active_languages ) : array(); $sanitized_codes = array_map( 'sanitize_text_field', $input ); $sanitized_codes = array_map( 'trim', $sanitized_codes ); $sanitized_codes = array_filter( $sanitized_codes, 'strlen' ); $sanitized_codes = array_values( array_unique( $sanitized_codes ) ); if ( empty( $valid_codes ) ) { return $sanitized_codes; } return array_values( array_intersect( $sanitized_codes, $valid_codes ) ); } /** * Check whether this is plugin settings form submission. */ private function is_settings_form_request() { return isset( $_POST['option_page'], $_POST['action'] ) && $_POST['option_page'] === 'alttextlab-settings' && $_POST['action'] === 'update'; } /** * Preserve selected WPML languages if the selector block was not rendered. */ public function keep_selected_wpml_languages( $value, $old_value, $option ) { if ( ! $this->is_settings_form_request() ) { return $value; } if ( empty( $_POST['alttextlab_wpml_languages_block_rendered'] ) ) { return $old_value; } if ( ! isset( $_POST['alttextlab_wpml_selected_langs'] ) ) { return array(); } return $value; } }