admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'aidf_admin_nonce' ), 'i18n' => array( 'confirmReset' => __( 'Reset all settings to defaults? This cannot be undone.', 'ai-discovery-files' ), 'generating' => __( 'Generating preview…', 'ai-discovery-files' ), 'validating' => __( 'Validating files…', 'ai-discovery-files' ), 'saved' => __( 'Settings saved.', 'ai-discovery-files' ), 'selectLogo' => __( 'Select Logo', 'ai-discovery-files' ), 'changeLogo' => __( 'Change Logo', 'ai-discovery-files' ), 'useThisImage' => __( 'Use This Image', 'ai-discovery-files' ), 'chooseLogo' => __( 'Choose a Logo Image', 'ai-discovery-files' ), 'remove' => __( 'Remove', 'ai-discovery-files' ), 'questionPlaceholder' => __( 'Question…', 'ai-discovery-files' ), 'answerPlaceholder' => __( 'Answer…', 'ai-discovery-files' ), 'servicePlaceholder' => __( 'Service or product name', 'ai-discovery-files' ), 'serviceUrlPlaceholder' => __( 'Page URL (optional)', 'ai-discovery-files' ), 'namePlaceholder' => __( 'Full name', 'ai-discovery-files' ), 'rolePlaceholder' => __( 'Role (e.g. Managing Director)', 'ai-discovery-files' ), 'personUrlPlaceholder' => __( 'Profile URL (LinkedIn, team page, etc.)', 'ai-discovery-files' ), 'valid' => __( 'Valid', 'ai-discovery-files' ), 'allValid' => __( 'All active files are valid and consistent.', 'ai-discovery-files' ), 'previewError' => __( 'Error: Could not load preview.', 'ai-discovery-files' ), 'errorPrefix' => __( 'Error: ', 'ai-discovery-files' ), 'unknownError' => __( 'Unknown error', 'ai-discovery-files' ), 'crossFileConsistency' => __( 'Cross-File Consistency', 'ai-discovery-files' ), 'verifyFileActive' => __( 'Verification file is live at', 'ai-discovery-files' ), 'verifyFileInactive' => __( 'No verification code set. Enter your code above to activate.', 'ai-discovery-files' ), ), ) ); } /** * Redirect to settings page after activation. * * @return void */ public static function handle_activation_redirect() { if ( ! get_transient( 'aidf_activation_redirect' ) ) { return; } delete_transient( 'aidf_activation_redirect' ); if ( wp_doing_ajax() || isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } wp_safe_redirect( admin_url( 'admin.php?page=ai-discovery-files' ) ); exit; } /** * Add Settings and Docs links to plugin action links. * * @param array $links Existing action links. * @return array */ public static function add_action_links( $links ) { $settings_link = sprintf( '%s', esc_url( admin_url( 'admin.php?page=ai-discovery-files' ) ), esc_html__( 'Settings', 'ai-discovery-files' ) ); $docs_link = sprintf( '%s', esc_url( 'https://www.ai-visibility.org.uk/specifications/' ), esc_html__( 'Docs', 'ai-discovery-files' ) ); array_unshift( $links, $docs_link ); array_unshift( $links, $settings_link ); return $links; } /** * Render the settings page. * * @return void */ public static function render_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } include AIDF_PLUGIN_DIR . 'admin/views/settings-page.php'; } /** * AJAX handler: preview a file type. * * @return void */ public static function ajax_preview_file() { check_ajax_referer( 'aidf_admin_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'Permission denied.', 'ai-discovery-files' ) ); } $file_slug = isset( $_POST['file_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['file_slug'] ) ) : ''; $file_types = AIDF_Plugin::get_file_types(); if ( empty( $file_slug ) || ! isset( $file_types[ $file_slug ] ) ) { wp_send_json_error( __( 'Invalid file type.', 'ai-discovery-files' ) ); } $content = AIDF_Generator::generate( $file_slug ); if ( false === $content ) { wp_send_json_error( __( 'Could not generate file.', 'ai-discovery-files' ) ); } wp_send_json_success( array( 'content' => $content, 'filename' => $file_types[ $file_slug ]['filename'], 'url' => AIDF_Server::get_file_url( $file_slug ), ) ); } /** * AJAX handler: validate all active files. * * @return void */ public static function ajax_validate_files() { check_ajax_referer( 'aidf_admin_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'Permission denied.', 'ai-discovery-files' ) ); } $results = AIDF_Validator::validate_all(); wp_send_json_success( $results ); } /** * AJAX handler: save domain verification code. * * @return void */ public static function ajax_save_verify_code() { check_ajax_referer( 'aidf_admin_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'Permission denied.', 'ai-discovery-files' ) ); } $code = isset( $_POST['verify_code'] ) ? sanitize_text_field( wp_unslash( $_POST['verify_code'] ) ) : ''; // Validate: must match "ai-visibility-verify=" followed by hex, or empty to clear. if ( '' !== $code && ! preg_match( '/^ai-visibility-verify=[a-f0-9]+$/i', $code ) ) { wp_send_json_error( __( 'Invalid verification code. The code should be in the format: ai-visibility-verify=abc123', 'ai-discovery-files' ) ); } $settings = get_option( 'aidf_settings', array() ); $settings['verify_code'] = $code; update_option( 'aidf_settings', $settings ); // Flush rewrite rules to ensure the verify file route is active. AIDF_Server::register_rewrite_rules(); flush_rewrite_rules(); $data = array( 'message' => empty( $code ) ? __( 'Verification code removed.', 'ai-discovery-files' ) : __( 'Verification code saved.', 'ai-discovery-files' ), 'active' => ! empty( $code ), 'url' => home_url( '/ai-visibility-verify.txt' ), ); wp_send_json_success( $data ); } /** * Get the current active tab. * * @return string */ public static function get_current_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab display only, no state change. $tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'identity'; $valid_tabs = array( 'identity', 'permissions', 'content', 'advanced', 'preview', 'status' ); if ( ! in_array( $tab, $valid_tabs, true ) ) { $tab = 'identity'; } return $tab; } /** * Get all tabs with labels. * * @return array */ public static function get_tabs() { return array( 'identity' => __( 'Identity', 'ai-discovery-files' ), 'permissions' => __( 'Permissions', 'ai-discovery-files' ), 'content' => __( 'Content', 'ai-discovery-files' ), 'advanced' => __( 'Technical', 'ai-discovery-files' ), 'preview' => __( 'Preview', 'ai-discovery-files' ), 'status' => __( 'Status', 'ai-discovery-files' ), ); } /** * Render an inline tooltip icon with hover popup. * * @param string $text Tooltip content (plain text). * @return void */ public static function tooltip( $text ) { printf( '', esc_attr__( 'More information', 'ai-discovery-files' ), esc_html( $text ) ); } /** * Show a welcome notice if the plugin hasn't been configured. * * @return void */ public static function maybe_show_welcome_notice() { if ( ! current_user_can( 'manage_options' ) ) { return; } // Don't show on our own settings page — they're already there. $screen = get_current_screen(); if ( $screen && self::$hook_suffix === $screen->id ) { return; } // Check if user has dismissed this notice. if ( get_user_meta( get_current_user_id(), 'aidf_welcome_dismissed', true ) ) { return; } // Only show if plugin hasn't been meaningfully configured. $saved = get_option( 'aidf_settings', array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $has_custom_name = ! empty( $saved['business_name'] ) && $saved['business_name'] !== get_bloginfo( 'name' ); $has_services = ! empty( $saved['services'] ); if ( $has_custom_name || $has_services ) { return; } // Inline only the welcome notice styles — avoid loading the full admin.css on every page. wp_register_style( 'aidf-welcome-notice', false, array(), AIDF_VERSION ); wp_enqueue_style( 'aidf-welcome-notice' ); wp_add_inline_style( 'aidf-welcome-notice', '.aidf-welcome-notice{border-left-color:#e77d15!important}' . '.aidf-welcome-notice .aidf-welcome-inner{display:flex;align-items:center;gap:12px;padding:4px 0}' . '.aidf-welcome-notice .aidf-welcome-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#fef3e6;border-radius:6px;flex-shrink:0}' . '.aidf-welcome-notice .aidf-welcome-icon .dashicons{color:#e77d15;font-size:18px;width:18px;height:18px}' . '.aidf-welcome-notice .aidf-welcome-text strong{display:block;margin-bottom:2px}' . '.aidf-welcome-notice .aidf-welcome-text a{font-weight:600}' ); $settings_url = admin_url( 'admin.php?page=ai-discovery-files' ); $nonce = wp_create_nonce( 'aidf_dismiss_welcome' ); wp_add_inline_script( 'jquery', 'jQuery( document ).on( "click", "#aidf-welcome-notice .notice-dismiss", function() {' . 'jQuery.post( ajaxurl, {' . 'action: "aidf_dismiss_welcome",' . 'nonce: document.getElementById( "aidf-welcome-notice" ).getAttribute( "data-nonce" )' . '} );' . '} );' ); ?> id, array( self::$hook_suffix, 'plugins' ), true ) ) { return; } $conflicts = self::detect_file_conflicts(); if ( empty( $conflicts ) ) { return; } $messages = array(); foreach ( $conflicts as $conflict ) { $messages[] = sprintf( /* translators: 1: plugin name, 2: feature name */ esc_html__( '%1$s has %2$s enabled, which may conflict with this plugin.', 'ai-discovery-files' ), '' . esc_html( $conflict['plugin'] ) . '', esc_html( $conflict['feature'] ) ); } printf( '

%s

    ', esc_html__( 'AI Discovery Files — Potential Conflict Detected', 'ai-discovery-files' ) ); foreach ( $messages as $msg ) { echo '
  • ' . wp_kses( $msg, array( 'strong' => array() ) ) . '
  • '; } printf( '

%s

', esc_html__( 'To avoid duplicate or conflicting AI discovery files, disable the other plugin\'s AI file generation feature, or deactivate those file types in AI Discovery Files settings.', 'ai-discovery-files' ) ); } /** * Detect other plugins that generate AI discovery files. * * @return array> List of conflicts with 'plugin' and 'feature' keys. */ private static function detect_file_conflicts() { $conflicts = array(); // Yoast SEO — llms.txt (added in Yoast SEO 24.x). if ( defined( 'WPSEO_VERSION' ) ) { $yoast_options = get_option( 'wpseo', array() ); if ( ! empty( $yoast_options['enable_llms_txt'] ) ) { $conflicts[] = array( 'plugin' => 'Yoast SEO', 'feature' => 'llms.txt generation', ); } } // Rank Math — llms.txt (added in Rank Math 1.0.230+). if ( class_exists( 'RankMath' ) ) { $rm_general = get_option( 'rank-math-options-general', array() ); if ( ! empty( $rm_general['enable_llms_txt'] ) ) { $conflicts[] = array( 'plugin' => 'Rank Math SEO', 'feature' => 'llms.txt generation', ); } } // SEOPress — llms.txt support. if ( defined( 'SEOPRESS_VERSION' ) ) { $sp_options = get_option( 'seopress_pro_option_name', array() ); if ( ! empty( $sp_options['seopress_ai_llms_txt'] ) ) { $conflicts[] = array( 'plugin' => 'SEOPress', 'feature' => 'llms.txt generation', ); } } // All in One SEO. if ( defined( 'AIOSEO_VERSION' ) ) { $aioseo_options = get_option( 'aioseo_options', '' ); if ( is_string( $aioseo_options ) ) { $aioseo_options = json_decode( $aioseo_options, true ); } if ( is_array( $aioseo_options ) && ! empty( $aioseo_options['searchAppearance']['advanced']['llmsTxt']['enable'] ) ) { $conflicts[] = array( 'plugin' => 'All in One SEO', 'feature' => 'llms.txt generation', ); } } return $conflicts; } /** * AJAX handler: dismiss the welcome notice permanently. * * @return void */ public static function ajax_dismiss_welcome() { check_ajax_referer( 'aidf_dismiss_welcome', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error(); } update_user_meta( get_current_user_id(), 'aidf_welcome_dismissed', 1 ); wp_send_json_success(); } }