*/ class Accessibility_Onetap_Admin { /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; /** * The option group name used for registering accessibility status settings. * * @since 1.0.0 * @access protected * @var string $accessibility_status_option_group Option group ID for accessibility status settings. */ protected $accessibility_status_option_group = 'options_group_onetap_accessibility_status'; /** * The list of allowed languages for accessibility status settings. * * @since 1.0.0 * @access public * @const array ALLOWED_LANGUAGES Array of allowed language codes. */ const ALLOWED_LANGUAGES = array( 'en', 'de', 'es', 'fr', 'it', 'pl', 'se', 'fi', 'pt', 'ro', 'si', 'sk', 'nl', 'dk', 'gr', 'cz', 'hu', 'lt', 'lv', 'ee', 'hr', 'ie', 'bg', 'no', 'tr', 'id', 'pt-br', 'ja', 'ko', 'zh', 'ar', 'ru', 'hi', 'uk', 'sr', 'gb', 'ir', 'il', 'mk', 'th', 'vn', 'bn', 'tl', 'ur', ); /** * Initialize the class and set its properties. * * @since 1.0.0 * @param string $plugin_name The name of this plugin. * @param string $version The version of this plugin. */ public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; } /** * Register the stylesheets for the admin area. * * @since 1.0.0 * * @param string $hook The current admin page hook suffix passed by WordPress. */ public function enqueue_styles( $hook ) { if ( 'toplevel_page_accessibility-onetap-settings' === $hook || 'onetap_page_accessibility-onetap-general-settings' === $hook || 'onetap_page_accessibility-onetap-alt-text' === $hook || 'onetap_page_accessibility-onetap-modules' === $hook || 'admin_page_onetap-module-labels' === $hook || 'onetap_page_accessibility-onetap-accessibility-status' === $hook ) { // Enqueue the main admin CSS for the plugin. wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/accessibility-onetap-admin.css', array(), $this->version, 'all' ); // Enqueue Google Fonts (Inter) for the admin pages of the plugin. wp_enqueue_style( 'onetap-google-fonts', 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', array(), $this->version, 'all' ); } } /** * Register the JavaScript for the admin area. * * @since 1.0.0 * * @param string $hook The current admin page hook suffix passed by WordPress. */ public function enqueue_scripts( $hook ) { // Both localize calls below expose the same `adminLocalize` JS object, and the // last one printed wins - so every one of them has to carry these strings. $i18n = array( 'warning' => __( 'Warning!', 'accessibility-onetap' ), 'completeAllFields' => __( 'Please complete all fields.', 'accessibility-onetap' ), 'altPilotInstallFailed' => __( 'AltPilot could not be installed. Please try again from Plugins → Add New.', 'accessibility-onetap' ), ); if ( 'toplevel_page_accessibility-onetap-settings' === $hook || 'onetap_page_accessibility-onetap-general-settings' === $hook || 'onetap_page_accessibility-onetap-alt-text' === $hook || 'onetap_page_accessibility-onetap-modules' === $hook || 'admin_page_onetap-module-labels' === $hook || 'onetap_page_accessibility-onetap-accessibility-status' === $hook ) { wp_enqueue_script( $this->plugin_name, ACCESSIBILITY_ONETAP_PLUGINS_URL . 'assets/js/admin-menu.min.js', array( 'jquery' ), $this->version, true ); wp_enqueue_script( $this->plugin_name . '-sweetalert', ACCESSIBILITY_ONETAP_PLUGINS_URL . 'assets/js/sweetalert.min.js', array(), $this->version, true ); // Get plugin settings. $plugin_settings = get_option( 'onetap_settings' ); // Get active language. $language = isset( $plugin_settings['language'] ) ? $plugin_settings['language'] : 'en'; // Localize admin. wp_localize_script( $this->plugin_name, 'adminLocalize', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'ajaxNonce' => wp_create_nonce( 'onetap-ajax-verification' ), 'adminUrl' => esc_url( admin_url() ), 'pluginUrl' => ACCESSIBILITY_ONETAP_PLUGINS_URL, 'activeLanguage' => $language, 'localizedLabels' => get_option( 'apop_localized_labels' ), 'i18n' => $i18n, ) ); } // The Alt Text screen offers a one-click AltPilot install, which is run by // the updater that ships with WordPress. Loading its script here is what // keeps the install on the page instead of sending the admin to // update.php; without it the button falls back to that screen. if ( 'onetap_page_accessibility-onetap-alt-text' === $hook && current_user_can( 'install_plugins' ) ) { wp_enqueue_script( 'updates' ); } wp_enqueue_script( $this->plugin_name . '-admin-global', ACCESSIBILITY_ONETAP_PLUGINS_URL . 'assets/js/admin-global.min.js', array( 'jquery' ), $this->version, true ); // Localize admin-global script for all admin pages. wp_localize_script( $this->plugin_name . '-admin-global', 'adminLocalize', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'ajaxNonce' => wp_create_nonce( 'onetap-ajax-verification' ), 'adminUrl' => esc_url( admin_url() ), 'pluginUrl' => ACCESSIBILITY_ONETAP_PLUGINS_URL, 'localizedLabels' => get_option( 'apop_localized_labels' ), 'i18n' => $i18n, ) ); } /** * Display review banner. * * Shown only on the Dashboard (Home) submenu. Shows a review banner with three action buttons: * - "Leave a Review": Redirects to review page and permanently disables banner * - "Maybe later": Dismisses banner and sets next show date to +14 days * - "Don't show again": Dismisses banner and permanently disables it * * The banner respects existing database options and will not reset on plugin updates. */ public function display_review_banner() { // Show only on Dashboard (Home) submenu. $screen = get_current_screen(); if ( ! $screen || 'dashboard' !== $screen->id ) { return; } // Ensure the plugin has been in use for at least 14 days. $install_timestamp = (int) get_option( 'onetap_install_timestamp', 0 ); if ( ! $install_timestamp ) { return; } $current_time = time(); if ( ( $current_time - $install_timestamp ) < ( 14 * DAY_IN_SECONDS ) ) { return; } // Check if banner is permanently disabled (kill-switch). $banner_disabled = get_option( 'onetap_review_banner_disabled', false ); if ( $banner_disabled ) { return; } // Check if we should show the banner based on next show date. $next_show_date = get_option( 'onetap_review_banner_next_show', '' ); if ( ! empty( $next_show_date ) ) { $show_timestamp = strtotime( $next_show_date ); if ( $current_time < $show_timestamp ) { return; // Not time to show yet. } } ?>
true ) ); wp_die(); } /** * AJAX handler for "Maybe later" action. * * Sets the next show date to +14 days from today. * This value is preserved and checked before showing the banner. */ public function ajax_handle_maybe_later() { // Security check: Ensure nonce is valid. check_ajax_referer( 'onetap-ajax-verification', 'mynonce' ); // Calculate next show date: +14 days from today. $next_show_date = gmdate( 'Y-m-d H:i:s', strtotime( '+14 days' ) ); // Update the next show date option. // This will be checked in display_review_banner() before showing. update_option( 'onetap_review_banner_next_show', $next_show_date ); // Terminate the AJAX request. wp_send_json( array( 'success' => true ) ); wp_die(); } /** * AJAX handler for "Don't show again" action. * * Sets a permanent flag to disable the banner forever. * This acts as a kill-switch and will not be reset on plugin updates. */ public function ajax_handle_dont_show_again() { // Security check: Ensure nonce is valid. check_ajax_referer( 'onetap-ajax-verification', 'mynonce' ); // Check if already disabled - respect existing value (update-proof). $existing_value = get_option( 'onetap_review_banner_disabled', false ); if ( ! $existing_value ) { // Only update if not already set - prevents reset on updates. update_option( 'onetap_review_banner_disabled', true ); } // Terminate the AJAX request. wp_send_json( array( 'success' => true ) ); wp_die(); } /** * Display admin notice. */ public function display_admin_notice() { $get_cookie_disable_admin_notices = isset( $_COOKIE['onetap_disable_admin_notices'] ) ? sanitize_key( $_COOKIE['onetap_disable_admin_notices'] ) : ''; if ( 'disable' === $get_cookie_disable_admin_notices ) { return; } ?> true ) ); wp_die(); } /** * Allow external domain for safe redirect. * * @param string[] $hosts Array of allowed hosts. * @return string[] Modified array with external host. */ public function onetap_allow_external_redirect_host( $hosts ) { $hosts[] = 'wponetap.com'; return $hosts; } /** * Redirect users accessing a specific admin page to an external pricing URL. * * This function runs on 'admin_init' and checks whether the current admin page * matches a specific plugin slug. * * @return void */ public function onetap_redirect_admin_page_to_pricing() { global $plugin_page, $pagenow; // Check if we are in the admin and on admin.php. if ( is_admin() && 'admin.php' === $pagenow && 'accessibility-onetap-pro' === $plugin_page ) { // Redirect. wp_safe_redirect( 'https://wponetap.com/pricing/?utm_source=dashboard-link&utm_medium=link&utm_campaign=ref-link-toolbar', 301 ); exit; } } /** * Add a custom "Settings" link below the plugin description metadata * (next to "Version | By | View details") in the Plugins admin screen. * * This function hooks into 'plugin_row_meta' and adds a link to the plugin's settings page * only for this specific plugin file. * * @param array $links Array of existing plugin row meta links. * @param string $file The plugin file path (relative to the plugins directory). * * @return array Modified array of plugin row meta links. */ public function add_row_meta( $links, $file ) { // Only add the settings link for this specific plugin. if ( 'accessibility-onetap/accessibility-onetap.php' === $file ) { $settings_url = admin_url( 'admin.php?page=accessibility-onetap-settings' ); $settings_link = '' . esc_html__( 'Settings', 'accessibility-onetap' ) . ''; // Append the custom settings link to the plugin meta row. $links[] = $settings_link; } return $links; } /** * Register settings related to the accessibility status section. * * This method registers multiple options using the WordPress Settings API, * including checkboxes, text inputs, select dropdowns, and rich text fields. * * @since 1.0.0 * @access public * @return void */ public function register_settings_for_accessibility_status() { // Register a boolean setting for showing the accessibility status toggle. register_setting( $this->accessibility_status_option_group, 'onetap_show_accessibility', array( 'type' => 'boolean', 'sanitize_callback' => array( $this, 'sanitize_checkbox_input' ), ) ); // Register a string setting for language selection. register_setting( $this->accessibility_status_option_group, 'onetap_select_language', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_select_language' ), ) ); // Register a string setting for the company name input. register_setting( $this->accessibility_status_option_group, 'onetap_company_name', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ) ); // Register a string setting for the company website URL. register_setting( $this->accessibility_status_option_group, 'onetap_company_website', array( 'type' => 'string', 'sanitize_callback' => array( $this, 'sanitize_company_website' ), ) ); // Register a string setting for the business email address. register_setting( $this->accessibility_status_option_group, 'onetap_business_email', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_email', ) ); // Register a boolean setting for the confirmation checkbox. register_setting( $this->accessibility_status_option_group, 'onetap_confirmation_checkbox', array( 'type' => 'boolean', 'sanitize_callback' => array( $this, 'sanitize_checkbox_input' ), ) ); // Register a string setting for a WYSIWYG or rich text editor. register_setting( $this->accessibility_status_option_group, 'onetap_editor_generator', array( 'type' => 'string', 'sanitize_callback' => 'wp_kses_post', ) ); } /** * Sanitize checkbox input value. * * Converts any truthy value to integer 1, otherwise returns 0. * * @since 1.0.0 * @access public * * @param mixed $value The input value to sanitize. * @return int Sanitized checkbox value (1 or 0). */ public function sanitize_checkbox_input( $value ) { return ( isset( $value ) && (bool) $value ) ? 1 : 0; } /** * Sanitize the selected language input. * * Ensures the selected language is in the list of allowed values. * Defaults to 'en' if the input is not valid. * * @since 1.0.0 * @access public * * @param string $value The selected language value. * @return string Sanitized language code. */ public function sanitize_select_language( $value ) { return in_array( $value, self::ALLOWED_LANGUAGES, true ) ? $value : 'en'; } /** * Sanitize company website input by removing protocol prefixes. * * Removes "https://" or "http://" from the beginning of the URL * and sanitizes the remaining value. * * @since 1.0.0 * @access public * * @param string $value The company website URL value. * @return string Sanitized website URL without protocol. */ public function sanitize_company_website( $value ) { // Convert to string and trim whitespace. $value = trim( (string) $value ); // Remove "https://" or "http://" from the beginning (case-insensitive). $value = preg_replace( '#^https?://#i', '', $value ); // Remove any trailing slashes. $value = rtrim( $value, '/' ); // Sanitize the remaining value. return sanitize_text_field( $value ); } /** * Register the "Missing Alt-Text" widget on the Dashboard home screen. * * @since 1.0.0 * @return void */ public function register_missing_alt_text_dashboard_widget() { if ( ! current_user_can( 'manage_options' ) ) { return; } // Skip the widget when the Alt Text feature is hidden. if ( Accessibility_Onetap_Config::is_alt_text_feature_hidden() ) { return; } wp_add_dashboard_widget( 'onetap_missing_alt_text', __( 'Missing Alt-Text', 'accessibility-onetap' ), array( $this, 'render_missing_alt_text_dashboard_widget' ), null, null, 'side', 'high' ); } /** * Output the Dashboard widget body (missing vs total images + CTA). * * @since 1.0.0 * @return void */ public function render_missing_alt_text_dashboard_widget() { $missing = $this->count_attachment_images_missing_alt_text(); $total = $this->count_attachment_images_total(); $alt_url = admin_url( 'admin.php?page=accessibility-onetap-alt-text' ); if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( is_plugin_active( 'altpilot/altpilot.php' ) ) { $alt_url = admin_url( 'admin.php?page=altpilot' ); } ?> version ); wp_enqueue_style( $handle ); $css = ' #onetap_missing_alt_text .onetap-dashboard-missing-alt__stats { margin: 0 0 8px; font-size: 20px; font-weight: 600; line-height: 1.3; color: #1d2327; } #onetap_missing_alt_text .onetap-dashboard-missing-alt__desc { margin: 0 0 16px; font-size: 13px; line-height: 1.5; color: #646970; } #onetap_missing_alt_text .onetap-dashboard-missing-alt__actions { margin: 0; } #onetap_missing_alt_text .onetap-dashboard-missing-alt__cta { text-align: center; } '; wp_add_inline_style( $handle, $css ); } /** * Count image attachments in the media library. * * @since 1.0.0 * @return int */ private function count_attachment_images_total() { $query = new WP_Query( array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => false, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); return (int) $query->found_posts; } /** * Count image attachments with empty or missing alt text meta. * * @since 1.0.0 * @return int */ private function count_attachment_images_missing_alt_text() { $query = new WP_Query( array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => false, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS', ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=', ), ), ) ); return (int) $query->found_posts; } }