'GET', 'callback' => array( __CLASS__, 'data' ), 'permission_callback' => '__return_true', ) ); register_rest_route( 'replyvane/v1', '/event', array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'event' ), 'permission_callback' => '__return_true', ) ); } public static function data() { $response = rest_ensure_response( Replyvane_Store::public_data() ); // Safe to cache: the URL carries a version number that changes on every edit. $response->header( 'Cache-Control', 'public, max-age=3600' ); return $response; } public static function event( WP_REST_Request $request ) { global $wpdb; $ok = rest_ensure_response( array( 'ok' => true ) ); if ( ! self::within_limit() ) { return $ok; // Quietly ignore floods; the visitor never notices. } $type = $request->get_param( 'type' ); if ( ! in_array( $type, array( 'click', 'search' ), true ) ) { return $ok; } $slug = sanitize_title( (string) $request->get_param( 'id' ) ); $item = $slug ? Replyvane_Store::get_by_slug( $slug ) : null; if ( 'click' === $type ) { if ( ! $item ) { return $ok; } $table = Replyvane_Plugin::items_table(); $wpdb->query( $wpdb->prepare( "UPDATE %i SET clicks = clicks + 1 WHERE id = %d", $table, $item['id'] ) ); } if ( ! Replyvane_Plugin::get( 'log_enabled' ) ) { return $ok; } $query = 'search' === $type ? mb_substr( trim( sanitize_text_field( (string) $request->get_param( 'query' ) ) ), 0, 300 ) : ''; if ( 'search' === $type && '' === $query ) { return $ok; } $page = esc_url_raw( (string) $request->get_param( 'page' ) ); $home = preg_replace( '/^www\./', '', (string) wp_parse_url( home_url(), PHP_URL_HOST ) ); if ( $page && preg_replace( '/^www\./', '', (string) wp_parse_url( $page, PHP_URL_HOST ) ) !== $home ) { $page = ''; } $wpdb->insert( Replyvane_Plugin::log_table(), array( 'created_at' => current_time( 'mysql', true ), 'session_id' => substr( preg_replace( '/[^a-zA-Z0-9]/', '', (string) $request->get_param( 'session' ) ), 0, 40 ), 'event' => $type, 'faq_slug' => $item ? $item['slug'] : '', 'query' => $query, 'matched' => $item ? 1 : ( 'search' === $type && $request->get_param( 'site' ) ? ( $request->get_param( 'ai' ) ? 3 : 2 ) : 0 ), // 2 = answered from the website's pages, 3 = written by AI. 'page_url' => mb_substr( $page, 0, 500 ), ), array( '%s', '%s', '%s', '%s', '%s', '%d', '%s' ) ); return $ok; } /** At most 120 recorded events per visitor every 10 minutes, to keep bots from filling the log. */ private static function within_limit() { $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; $key = 'replyvane_ev_' . md5( $ip . wp_salt( 'nonce' ) ); $count = (int) get_transient( $key ); if ( $count >= 120 ) { return false; } set_transient( $key, $count + 1, 10 * MINUTE_IN_SECONDS ); return true; } } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching