'sanitize_text_field' ) ); register_setting( 'aigeschikt', 'aigeschikt_api_url', array( 'sanitize_callback' => 'aigeschikt_sanitize_api_url_setting' ) ); register_setting( 'aigeschikt', 'aigeschikt_api_key', array( 'sanitize_callback' => 'aigeschikt_sanitize_api_key_setting' ) ); register_setting( 'aigeschikt', 'aigeschikt_schema_mode', array( 'sanitize_callback' => function ( $v ) { return 'replace' === $v ? 'replace' : 'add'; }, 'default' => 'add', ) ); } /** * https only. Invalid input keeps the stored value and says so, instead of * silently saving an empty or insecure URL. * * @param mixed $v Input. * @return string */ function aigeschikt_sanitize_api_url_setting( $v ) { $schoon = aigeschikt_sanitize_api_url( $v ); if ( '' === $schoon && '' !== trim( (string) $v ) ) { add_settings_error( 'aigeschikt_api_url', 'aigeschikt_api_url_https', __( 'The API URL must start with https://; the previous value was kept.', 'aigeschikt-connector' ) ); return (string) get_option( 'aigeschikt_api_url', '' ); } return $schoon; } /** * The key field is write-only: leaving it empty keeps the stored key. The stored * key never appears in the HTML of the settings page again. * * @param mixed $v Input. * @return string */ function aigeschikt_sanitize_api_key_setting( $v ) { $v = sanitize_text_field( (string) $v ); return '' === $v ? (string) get_option( 'aigeschikt_api_key', '' ) : $v; } /** * Notices of the buttons travel through a per-user transient instead of as text * in the URL — a URL parameter that is shown as a success message is a free * social-engineering channel to an administrator. * * @param string $tekst Message. * @param string $terug URL to return to. * @return string The return URL with the flag. */ function aigeschikt_melding_url( $tekst, $terug ) { set_transient( 'aigeschikt_melding_' . get_current_user_id(), (string) $tekst, 120 ); return add_query_arg( 'aigeschikt_melding', '1', $terug ); } /** * Read and consume the current user's notice. * * @return string */ function aigeschikt_lees_melding() { $sleutel = 'aigeschikt_melding_' . get_current_user_id(); $tekst = get_transient( $sleutel ); if ( false !== $tekst ) { delete_transient( $sleutel ); } return is_string( $tekst ) ? $tekst : ''; } /** * Where the buttons send you back to. * * @return string */ function aigeschikt_settings_back_url() { $back = wp_get_referer(); return $back ? $back : admin_url( 'admin.php?page=aigeschikt' ); } /** * Render the settings page. */ function aigeschikt_render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $aig_ok = array( 'code' => array(), 'strong' => array(), ); ?>

AIGeschikt Connector


false, 'van_ons' => false, 'schrijfbaar_wp_content' => false, 'cache_const' => 'ontbreekt', 'backup_bestaat' => false ); $aig_wpc_pad = function_exists( 'aigeschikt_wp_config_path' ) ? aigeschikt_wp_config_path() : ''; // Enabling it ourselves needs: wp-content writable, and — when WP_CACHE is not // set anywhere yet — a findable, writable wp-config.php (for that one line). // If WP_CACHE is already on (another cache plugin set it), the latter is not needed. $aig_kan_zelf = ! is_multisite() && $aig_cfg['schrijfbaar_wp_content'] && ( 'ontbreekt' !== $aig_cfg['cache_const'] || ( '' !== $aig_wpc_pad && wp_is_writable( $aig_wpc_pad ) ) ); ?>

' . esc_html( $aig_cache_nm ) . '' ); ?>

advanced-cache.php — WordPress’ own, documented mechanism for early logic, the same hook caching plugins rely on. Nothing changes for your visitors: they get the same page, just as fast. If a caching plugin already has its own advanced-cache.php, it keeps working — our measurement is placed in front of it, not in its place.', 'aigeschikt-connector' ), $aig_ok ); ?>

' . esc_html( $aig_cache_nm ) . '' ); ?>

Early measurement could not load. The safety layer caught the error — your site keeps running, only the measurement is paused. You do not need to do anything.', 'aigeschikt-connector' ), $aig_ok ); ?>

0 ) : ?>

advanced-cache.php for you (and the matching WP_CACHE setting). The same button turns it off again.', 'aigeschikt-connector' ) : __( 'We set up advanced-cache.php for you. The same button turns it off again.', 'aigeschikt-connector' ), $aig_ok ); ?>

' . esc_html( WP_CONTENT_DIR ) . '' ); ?> WP_CACHE is explicitly turned off (by another plugin or your host) — we do not change that automatically.', 'aigeschikt-connector' ), $aig_ok ); ?> WP_CACHE line to it.', 'aigeschikt-connector' ), $aig_ok ); ?>

0 ) : ?>

  1. file manager (often called “File Manager” or “FTP”). If you work with a web developer, ask them.', 'aigeschikt-connector' ), $aig_ok ); ?>
  2. wp-admin, wp-content, wp-includes) and create a new file advanced-cache.php in wp-content with the contents below. Is there already an advanced-cache.php from another caching plugin? Do not overwrite it — contact AIGeschikt and we will chain the two for you.', 'aigeschikt-connector' ), $aig_ok ); ?>
  3. wp-config.php, find the line with require_once ABSPATH . \'wp-settings.php\'; near the bottom and paste the second line below directly above it, on its own line.', 'aigeschikt-connector' ), $aig_ok ); ?>

get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $aig_tab ) ) === $aig_tab ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- own buffer table; must be read live. $aig_recent = (array) $wpdb->get_results( $wpdb->prepare( 'SELECT bot_name, detection_layer, url_path, http_status, synced, created_at FROM %i ORDER BY id DESC LIMIT 15', $aig_tab ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- own buffer table; must be read live. } } $aig_lagen = array( 'early_php' => __( 'before the cache', 'aigeschikt-connector' ), 'ua_match' => __( 'via WordPress', 'aigeschikt-connector' ), 'ip_only' => __( 'by IP address', 'aigeschikt-connector' ), ); ?>

90 ? substr( $aig_u, 0, 90 ) . '…' : $aig_u ); ?>

before the cache = counted before your cache served the page — those visits never reached WordPress and were invisible without this plugin. via WordPress = a visit where WordPress started normally. Every visit should appear here once.', 'aigeschikt-connector' ), $aig_ok ); ?>




Application Passwords (Users → Profile → Application Passwords).', 'aigeschikt-connector' ), $aig_ok ); ?>

403 ) ); } check_admin_referer( 'aigeschikt_early_toggle' ); $back = aigeschikt_settings_back_url(); $aan = isset( $_POST['doel'] ) && 'aan' === sanitize_key( wp_unslash( $_POST['doel'] ) ); if ( $aan && ! function_exists( 'aigeschikt_install_early_advanced_cache' ) ) { wp_safe_redirect( aigeschikt_melding_url( __( 'This plugin version cannot do that yet.', 'aigeschikt-connector' ), $back ) ); exit; } $res = $aan ? aigeschikt_install_early_advanced_cache() : aigeschikt_remove_early_advanced_cache(); wp_safe_redirect( aigeschikt_melding_url( $res['melding'], $back ) ); exit; } add_action( 'admin_post_aigeschikt_send_now', 'aigeschikt_handle_send_now' ); /** * Send what is in the queue to AIGeschikt right away, instead of waiting for the * hourly cron. */ function aigeschikt_handle_send_now() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to do that.', 'aigeschikt-connector' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'aigeschikt_send_now' ); $back = aigeschikt_settings_back_url(); if ( ! aigeschikt_is_connected() ) { wp_safe_redirect( aigeschikt_melding_url( __( 'This site is not connected to AIGeschikt yet.', 'aigeschikt-connector' ), $back ) ); exit; } if ( function_exists( 'aigeschikt_early_spool_flush' ) ) { aigeschikt_early_spool_flush(); } $open_voor = 0; if ( function_exists( 'aigeschikt_bot_table' ) ) { global $wpdb; $open_voor = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE synced = 0', aigeschikt_bot_table() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- own buffer table; must be read live. } if ( function_exists( 'aigeschikt_bot_sync_buffer' ) ) { aigeschikt_bot_sync_buffer(); } $open_na = 0; if ( function_exists( 'aigeschikt_bot_table' ) ) { global $wpdb; $open_na = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE synced = 0', aigeschikt_bot_table() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- own buffer table; must be read live. } $verstuurd = max( 0, $open_voor - $open_na ); if ( $open_na > 0 ) { /* translators: 1: number of visits sent, 2: number of visits still queued. */ $msg = sprintf( __( '%1$d visits sent, %2$d remain queued (sending failed — the next round tries again).', 'aigeschikt-connector' ), $verstuurd, $open_na ); } elseif ( $verstuurd > 0 ) { /* translators: %d: number of visits sent. */ $msg = sprintf( __( '%d visits sent to AIGeschikt.', 'aigeschikt-connector' ), $verstuurd ); } else { $msg = __( 'There was nothing waiting to be sent.', 'aigeschikt-connector' ); } wp_safe_redirect( aigeschikt_melding_url( $msg, $back ) ); exit; } /* --------------------------------------------------------------------- */ /* Privacy policy suggestion */ /* --------------------------------------------------------------------- */ add_action( 'admin_init', 'aigeschikt_add_privacy_policy_content' ); /** * Suggested text for Tools → Privacy → Policy Guide. */ function aigeschikt_add_privacy_policy_content() { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $content = '

' . esc_html__( 'The AIGeschikt Connector keeps a list of visits by AI crawlers (for example GPTBot or ClaudeBot) and, when the site is connected to AIGeschikt, sends it to AIGeschikt. Regular visitors are not recorded.', 'aigeschikt-connector' ) . '

' . '

' . esc_html__( 'Suggested text:', 'aigeschikt-connector' ) . ' ' . esc_html__( 'When an AI crawler visits this site we record the crawler\'s name, its IP address, the visited page (URL, and the title if the page is published), the HTTP status and the time. This is stored for up to 90 days and shared with AIGeschikt (https://aigeschikt.nl), which uses it to report on this site\'s visibility in AI search. Visits of regular visitors are not recorded.', 'aigeschikt-connector' ) . '

'; wp_add_privacy_policy_content( 'AIGeschikt Connector', wp_kses_post( $content ) ); }