*/ public static function normalize(mixed $input): array { if (is_string($input)) { $input = json_decode($input, true); } $input = is_array($input) ? $input : []; $result = ['source' => ($input['source'] ?? '') === 'venue' ? 'venue' : 'custom', 'venue_id' => absint($input['venue_id'] ?? 0)]; foreach (self::FIELDS as $field) { $result[$field] = sanitize_text_field((string) ($input[$field] ?? '')); } $result['coordinate_address'] = sanitize_text_field((string) ($input['coordinate_address'] ?? '')); $result['latitude'] = self::coordinate($input['latitude'] ?? null, 90); $result['longitude'] = self::coordinate($input['longitude'] ?? null, 180); if ($result['latitude'] === null || $result['longitude'] === null || $result['coordinate_address'] !== self::addressKey($result)) { $result['latitude'] = null; $result['longitude'] = null; } return $result; } public static function coordinate(mixed $value, int $bound): ?float { return is_numeric($value) && is_finite((float) $value) && abs((float) $value) <= $bound ? (float) $value : null; } /** @param array $location */ public static function addressKey(array $location): string { return (string) json_encode(array_map(static fn (string $key): string => trim((string) ($location[$key] ?? '')), self::FIELDS), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } /** @return array */ public static function stored(int $id): array { if (metadata_exists('post', $id, self::KEY)) { return self::normalize(get_post_meta($id, self::KEY, true)); } // Preserve old coordinates without writing or contacting a provider. $legacy = ['latitude' => get_post_meta($id, '_pcwp_latitude', true), 'longitude' => get_post_meta($id, '_pcwp_longitude', true)]; $legacy['coordinate_address'] = self::addressKey([]); $legacy['source'] = self::coordinate($legacy['latitude'], 90) !== null && self::coordinate($legacy['longitude'], 180) !== null ? 'custom' : 'venue'; if (get_post_type($id) === 'pcwp_venue') { $legacy['source'] = 'custom'; } return self::normalize($legacy); } /** @return list */ public static function venues(int $id): array { if (get_post_type($id) === 'pcwp_series') { return array_values(array_map('absint', (array) get_post_meta($id, '_pcwp_venue_ids', true))); } return \ProCalendarWP\Events\WpEventRepository::venueIds($id); } /** * @param array|null $input * @return array */ public static function resolve(int $id, ?array $input = null): array { $location = $input === null ? self::stored($id) : self::normalize($input); if ($location['source'] !== 'venue' || get_post_type($id) === 'pcwp_venue') { return $location; } $venues = self::venues($id); $venueId = in_array($location['venue_id'], $venues, true) ? $location['venue_id'] : ($venues[0] ?? 0); $resolved = $venueId > 0 && get_post_status($venueId) === 'publish' ? self::stored($venueId) : self::normalize([]); return array_merge($resolved, ['source' => 'venue', 'venue_id' => $venueId]); } /** @return array */ public static function definition(): array { $properties = []; foreach (array_merge(self::FIELDS, ['coordinate_address']) as $key) { $properties[$key] = ['type' => 'string']; } $properties['source'] = ['type' => 'string', 'enum' => ['venue', 'custom']]; $properties['venue_id'] = ['type' => 'integer', 'minimum' => 0]; $properties['latitude'] = ['type' => ['number', 'null'], 'minimum' => -90, 'maximum' => 90]; $properties['longitude'] = ['type' => ['number', 'null'], 'minimum' => -180, 'maximum' => 180]; return ['type' => 'object', 'sanitize_callback' => [self::class, 'normalize'], 'show_in_rest' => ['schema' => ['type' => 'object', 'properties' => $properties, 'additionalProperties' => false]]]; } /** @return array */ public static function publicData(int $id): array { $location = self::resolve($id); unset($location['coordinate_address']); return $location; } }