src or CSS background-image. ' . 'Always include photographer credit (name + Unsplash profile link) near the image when displayed. ' . 'Requires an Unsplash API key configured in Settings > Connectors.'; } public function input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'query' => array( 'type' => 'string', 'description' => 'Search keywords describing the image, e.g. "mountain landscape sunset".', ), 'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 20, 'default' => 5, 'description' => 'Number of results to return (max 20).', ), 'orientation' => array( 'type' => 'string', 'enum' => array( 'landscape', 'portrait', 'squarish' ), 'description' => 'Optional. Preferred orientation of the images.', ), ), 'required' => array( 'query' ), ); } public function execute( array $input, array $context = array() ): array { $api_key = Connector_Settings::get_unsplash_api_key(); if ( empty( $api_key ) ) { return array( 'type' => 'error', 'message' => __( 'Unsplash API key is not configured. Please add it in Settings > Connectors.', 'advanced-animation' ), ); } $query = sanitize_text_field( (string) ( $input['query'] ?? '' ) ); $per_page = min( max( intval( $input['per_page'] ?? 5 ), 1 ), 20 ); if ( empty( $query ) ) { return array( 'type' => 'error', 'message' => __( 'No search query provided.', 'advanced-animation' ), ); } $args = array( 'query' => $query, 'per_page' => $per_page, 'client_id' => $api_key, ); $orientation = sanitize_key( (string) ( $input['orientation'] ?? '' ) ); if ( in_array( $orientation, array( 'landscape', 'portrait', 'squarish' ), true ) ) { $args['orientation'] = $orientation; } $url = add_query_arg( $args, 'https://api.unsplash.com/search/photos' ); $response = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'Accept-Version' => 'v1', ), ) ); if ( is_wp_error( $response ) ) { return array( 'type' => 'error', 'message' => $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( 200 !== intval( $code ) ) { $message = __( 'Unsplash request failed.', 'advanced-animation' ); if ( is_array( $data ) && ! empty( $data['errors'] ) ) { $first_error = is_array( $data['errors'][0] ) ? ( $data['errors'][0]['detail'] ?? '' ) : (string) $data['errors'][0]; if ( $first_error ) { $message = sanitize_text_field( $first_error ); } } return array( 'type' => 'error', 'message' => $message, ); } if ( ! is_array( $data ) || ! isset( $data['results'] ) || ! is_array( $data['results'] ) ) { return array( 'type' => 'error', 'message' => __( 'Unsplash returned an unexpected response.', 'advanced-animation' ), ); } $images = array(); foreach ( $data['results'] as $result ) { if ( ! is_array( $result ) ) { continue; } $user = is_array( $result['user'] ?? null ) ? $result['user'] : array(); $urls = is_array( $result['urls'] ?? null ) ? $result['urls'] : array(); $links = is_array( $result['links'] ?? null ) ? $result['links'] : array(); $user_links = is_array( $user['links'] ?? null ) ? $user['links'] : array(); $photographer = sanitize_text_field( (string) ( $user['name'] ?? '' ) ); $username = sanitize_text_field( (string) ( $user['username'] ?? '' ) ); $profile_url = esc_url_raw( (string) ( $user_links['html'] ?? '' ) ); $photo_url = esc_url_raw( (string) ( $links['html'] ?? '' ) ); $download_loc = esc_url_raw( (string) ( $links['download_location'] ?? '' ) ); $images[] = array( 'id' => sanitize_text_field( (string) ( $result['id'] ?? '' ) ), 'description' => sanitize_text_field( (string) ( $result['description'] ?? ( $result['alt_description'] ?? '' ) ) ), 'url' => esc_url_raw( (string) ( $urls['regular'] ?? '' ) ), 'full' => esc_url_raw( (string) ( $urls['full'] ?? '' ) ), 'thumb' => esc_url_raw( (string) ( $urls['small'] ?? '' ) ), 'width' => intval( $result['width'] ?? 0 ), 'height' => intval( $result['height'] ?? 0 ), 'photographer' => $photographer, 'photographer_url' => $profile_url, 'unsplash_url' => $photo_url, 'attribution' => $photographer && $username ? sprintf( /* translators: 1: photographer name, 2: Unsplash username */ __( 'Photo by %1$s (@%2$s) on Unsplash', 'advanced-animation' ), $photographer, $username ) : '', 'download_location' => $download_loc, ); } self::remember_images( $images ); return array( 'type' => 'image_search_results', 'images' => $images, ); } /** * Remember download endpoints for image URLs returned by search. * * @param array $images Search result image rows. */ public static function remember_images( array $images ): void { $map = self::get_download_map(); foreach ( $images as $image ) { if ( ! is_array( $image ) ) { continue; } $download = esc_url_raw( (string) ( $image['download_location'] ?? '' ) ); if ( '' === $download ) { continue; } foreach ( array( 'url', 'full', 'thumb' ) as $key ) { $image_url = esc_url_raw( (string) ( $image[ $key ] ?? '' ) ); if ( '' === $image_url ) { continue; } $map[ self::normalize_image_url( $image_url ) ] = $download; } } self::set_download_map( $map ); } /** * Ping Unsplash download endpoints for any Unsplash images found in content. * * @param string $content HTML or CSS that may embed images.unsplash.com URLs. * @return int Number of download pings dispatched. */ public static function track_downloads_in_content( string $content ): int { if ( '' === $content || false === stripos( $content, 'images.unsplash.com' ) ) { return 0; } if ( ! preg_match_all( '#https?://images\.unsplash\.com/[^\s"\'\)<>]+#i', $content, $matches ) ) { return 0; } $map = self::get_download_map(); $tracked = array(); $count = 0; foreach ( $matches[0] as $raw_url ) { $key = self::normalize_image_url( $raw_url ); if ( isset( $tracked[ $key ] ) ) { continue; } $tracked[ $key ] = true; $download = $map[ $key ] ?? ''; if ( '' === $download ) { continue; } if ( self::track_download( $download ) ) { ++$count; } } return $count; } /** * Notify Unsplash that a photo was used (API guideline). * * @param string $download_location Download endpoint from the API response. * @return bool True when the request was dispatched. */ public static function track_download( string $download_location ): bool { $download_location = esc_url_raw( $download_location ); if ( '' === $download_location ) { return false; } $api_key = Connector_Settings::get_unsplash_api_key(); if ( '' === $api_key ) { return false; } $url = add_query_arg( 'client_id', $api_key, $download_location ); wp_remote_get( $url, array( 'timeout' => 5, 'blocking' => false, 'headers' => array( 'Accept-Version' => 'v1', ), ) ); return true; } /** * Normalize an Unsplash CDN URL for map lookups (strip query string). * * @param string $url Image URL. * @return string */ public static function normalize_image_url( string $url ): string { $parts = wp_parse_url( $url ); if ( ! is_array( $parts ) || empty( $parts['host'] ) || empty( $parts['path'] ) ) { return untrailingslashit( esc_url_raw( $url ) ); } $scheme = ! empty( $parts['scheme'] ) ? $parts['scheme'] : 'https'; return $scheme . '://' . $parts['host'] . $parts['path']; } /** * @return array */ private static function get_download_map(): array { $user_id = get_current_user_id(); $key = self::DOWNLOAD_MAP_TRANSIENT_PREFIX . $user_id; $map = get_transient( $key ); return is_array( $map ) ? $map : array(); } /** * @param array $map URL path => download_location. */ private static function set_download_map( array $map ): void { $user_id = get_current_user_id(); $key = self::DOWNLOAD_MAP_TRANSIENT_PREFIX . $user_id; set_transient( $key, $map, HOUR_IN_SECONDS ); } }