rate_limit = apply_filters( 'agentshop_api_rate_limit', $this->rate_limit ); /** * Filter the API rate limit window in seconds. * * @since 1.0.0 * @param int $rate_limit_window Time window in seconds. Default 60. */ $this->rate_limit_window = apply_filters( 'agentshop_api_rate_limit_window', $this->rate_limit_window ); } /** * Check if rate limit is exceeded. * * @return bool True if rate limited, false otherwise. */ private function is_rate_limited() { $transient_key = 'agentshop_api_requests'; $requests = get_transient( $transient_key ); if ( false === $requests ) { return false; } return $requests >= $this->rate_limit; } /** * Increment rate limit counter. */ private function increment_rate_limit() { $transient_key = 'agentshop_api_requests'; $requests = get_transient( $transient_key ); if ( false === $requests ) { set_transient( $transient_key, 1, $this->rate_limit_window ); } else { $timeout = get_option( '_transient_timeout_' . $transient_key ); $remaining = $timeout ? $timeout - time() : $this->rate_limit_window; set_transient( $transient_key, $requests + 1, max( 1, $remaining ) ); } } /** * Make an API request. * * @param string $method HTTP method. * @param string $path API path. * @param array $data Request data. * @param array $headers Additional headers. * @return array|\WP_Error */ public function request( $method, $path, $data = array(), $headers = array() ) { // Check rate limiting. if ( $this->is_rate_limited() ) { agentshop()->log( 'API rate limit exceeded', 'warning' ); return new \WP_Error( 'agentshop_rate_limited', __( 'API rate limit exceeded. Please try again later.', 'agentshop' ), array( 'status' => 429 ) ); } $endpoint = agentshop()->get_api_endpoint(); $url = trailingslashit( $endpoint ) . ltrim( $path, '/' ); // Validate HTTPS. if ( strpos( $url, 'https://' ) !== 0 && ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) { return new \WP_Error( 'agentshop_insecure_endpoint', __( 'API endpoint must use HTTPS.', 'agentshop' ) ); } $default_headers = array( 'Content-Type' => 'application/json', 'User-Agent' => 'AgentShop-WooCommerce/' . AGENTSHOP_VERSION, 'X-Site-URL' => get_site_url(), ); $options = agentshop()->get_options(); if ( ! empty( $options['pixel_token'] ) ) { $default_headers['X-AgentShop-Token'] = $options['pixel_token']; } $headers = array_merge( $default_headers, $headers ); $args = array( 'method' => $method, 'headers' => $headers, 'timeout' => $this->timeout, ); if ( in_array( $method, array( 'POST', 'PUT', 'PATCH' ), true ) && ! empty( $data ) ) { $args['body'] = wp_json_encode( $data ); } agentshop()->log( "API Request: {$method} {$path}", 'debug', array( 'data' => $data ) ); // Increment rate limit counter. $this->increment_rate_limit(); $response = wp_remote_request( $url, $args ); if ( is_wp_error( $response ) ) { agentshop()->log( 'API Error: ' . $response->get_error_message(), 'error' ); return $response; } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); $decoded = json_decode( $body, true ); if ( $code >= 400 ) { $error_message = isset( $decoded['message'] ) ? $decoded['message'] : __( 'Unknown error', 'agentshop' ); agentshop()->log( "API Error ({$code}): {$error_message}", 'error' ); return new \WP_Error( 'agentshop_api_error', $error_message, array( 'status' => $code, 'response' => $decoded, ) ); } return array( 'status' => $code, 'data' => $decoded, ); } /** * GET request. * * @param string $path API path. * @param array $params Query parameters. * @return array|\WP_Error */ public function get( $path, $params = array() ) { if ( ! empty( $params ) ) { $path .= '?' . http_build_query( $params ); } return $this->request( 'GET', $path ); } /** * POST request. * * @param string $path API path. * @param array $data Request body. * @return array|\WP_Error */ public function post( $path, $data = array() ) { return $this->request( 'POST', $path, $data ); } /** * Send pixel event. * * @param array $event_data Event data. * @return array|\WP_Error */ public function send_pixel_event( $event_data ) { $options = agentshop()->get_options(); $payload = array_merge( array( 'pixel_version' => AGENTSHOP_VERSION, 'store_url' => agentshop()->get_store_url(), 'pixel_token' => $options['pixel_token'] ?? '', 'session_id' => agentshop()->get_session_id(), 'customer_id' => agentshop()->get_customer_id(), 'event_source' => 'server', 'timestamp' => gmdate( 'c' ), 'url' => $this->get_current_url(), 'referrer' => wp_get_referer() ?: '', ), $event_data ); return $this->post( 'api/woocommerce/pixel/events', $payload ); } /** * Verify connection. * * @return array|\WP_Error */ public function verify_connection() { return $this->get( 'api/woocommerce/verify' ); } /** * Disconnect. * * @return array|\WP_Error */ public function disconnect() { return $this->post( 'api/woocommerce/disconnect' ); } /** * Check plugin status. * * @return array|\WP_Error */ public function check_plugin_status() { return $this->get( 'api/woocommerce/plugin/status', array( 'site' => agentshop()->get_store_url() ) ); } /** * Send WordPress credentials (snake_case). * * @param string $username Username. * @param string $password Password. * @return array|\WP_Error */ public function send_wordpress_credentials( $username, $password ) { return $this->post( 'api/woocommerce/plugin/wordpress-credentials', array( 'wp_username' => $username, 'wp_app_password' => $password, ) ); } /** * Fetch llms.txt content. * * @param string $version Version: 'standard' or 'full'. * @return array|\WP_Error */ public function fetch_llms_txt( $version = 'standard' ) { $params = array( 'site' => agentshop()->get_store_url() ); if ( 'full' === $version ) { $params['version'] = 'full'; } return $this->get( 'api/woocommerce/llms-txt/proxy', $params ); } /** * Get current URL. * * @return string */ private function get_current_url() { global $wp; if ( is_admin() ) { return admin_url( add_query_arg( array(), $GLOBALS['pagenow'] ) ); } return home_url( add_query_arg( array(), $wp->request ) ); } }