fallback->extract( $snapshot ); try { $cloud = $this->settings->load()->cloud(); if ( ! $cloud->isConfigured() ) { return $fallback; } $keywords = $this->client->normalize( $cloud, self::payloadFromSnapshot( $snapshot ), time(), bin2hex( random_bytes( 12 ) ) ); } catch ( Throwable $unused ) { unset( $unused ); return $fallback; } $keywords = trim( $keywords ); if ( $keywords === '' ) { return $fallback; } return new SearchKeywordsExtractionResult( $keywords, 'cloud' ); } /** * Build Cloud product payload. * * @param ProductSnapshot $snapshot Product snapshot. * * @return array */ private static function payloadFromSnapshot( ProductSnapshot $snapshot ): array { $payload = array( 'provider' => self::boundedString( $snapshot->provider(), 80 ), 'provider_item_id' => self::boundedString( $snapshot->providerProductId()->value(), 255 ), 'title' => self::boundedString( $snapshot->title(), 500 ), 'brand' => self::boundedNullableString( $snapshot->brand(), 255 ), 'description' => self::boundedNullableString( $snapshot->description(), 4000 ), 'detail_url' => self::boundedNullableString( $snapshot->detailUrl(), 2048 ), 'affiliate_url' => self::boundedNullableString( $snapshot->affiliateUrl(), 2048 ), 'rating' => self::boundedNullableString( $snapshot->rating(), 32 ), 'review_count' => $snapshot->reviewCount(), 'categories' => self::boundedStringList( $snapshot->categories(), 20, 255 ), ); $attributes = self::boundedAttributeMap( $snapshot->attributes(), 40, 120, 512 ); if ( $attributes !== array() ) { $payload['attributes'] = $attributes; } return $payload; } /** * Bound text for Cloud schema limits. * * @param string $value Text. * @param int $max Maximum character length. */ private static function boundedString( string $value, int $max ): string { if ( function_exists( 'mb_substr' ) ) { return mb_substr( $value, 0, $max ); } return substr( $value, 0, $max ); } /** * Bound nullable text. * * @param string|null $value Text. * @param int $max Maximum character length. */ private static function boundedNullableString( ?string $value, int $max ): ?string { if ( $value === null ) { return null; } return self::boundedString( $value, $max ); } /** * @param array $values Raw string list. * @param int $countLimit Maximum number of values. * @param int $itemLimit Maximum character length per value. * * @return array */ private static function boundedStringList( array $values, int $countLimit, int $itemLimit ): array { $result = array(); foreach ( array_slice( $values, 0, $countLimit ) as $value ) { $result[] = self::boundedString( $value, $itemLimit ); } return $result; } /** * @param array $attributes Raw attributes. * @param int $countLimit Maximum number of attributes. * @param int $keyLimit Maximum character length per key. * @param int $valueLimit Maximum character length per string value. * * @return array */ private static function boundedAttributeMap( array $attributes, int $countLimit, int $keyLimit, int $valueLimit ): array { $result = array(); foreach ( $attributes as $key => $value ) { if ( count( $result ) >= $countLimit ) { break; } $key = self::boundedString( $key, $keyLimit ); if ( $key === '' ) { continue; } if ( is_string( $value ) ) { $result[ $key ] = self::boundedString( $value, $valueLimit ); } else { $result[ $key ] = $value; } } return $result; } }