Add on-demand media URL refresh for expired MLS Grid tokens

MLS Grid media URLs expire after ~24 hours. Instead of running
scheduled syncs, this adds on-demand refresh when images are requested:

- Add is_url_expired() to parse expires timestamp from media URLs
- Add refresh_media_urls() to fetch fresh URLs from API for one listing
- Add get_property_media() API method using ListingId filter
- Image endpoint checks URL expiration before fetching
- If expired, refreshes URLs from API then proceeds with fetch

This is more efficient than scheduled full syncs because:
- Only refreshes URLs for listings actually being viewed
- Zero overhead for unviewed listings
- Scales naturally with traffic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-15 23:45:44 -06:00
parent 4db53b607c
commit 72b932b25e
3 changed files with 146 additions and 0 deletions
@@ -494,6 +494,87 @@ class MLS_Media_Handler {
));
}
/**
* Check if a media URL has expired
*
* MLS Grid media URLs contain an 'expires' parameter with Unix timestamp.
* Returns true if the URL is expired or will expire within the buffer time.
*
* @param string $media_url The media URL to check
* @param int $buffer_seconds Buffer time before actual expiration (default: 300 = 5 min)
* @return bool True if expired or expiring soon
*/
public function is_url_expired($media_url, $buffer_seconds = 300) {
if (empty($media_url)) {
return true;
}
// Extract expires parameter from URL
if (preg_match('/expires=(\d+)/', $media_url, $matches)) {
$expires = (int) $matches[1];
return (time() + $buffer_seconds) >= $expires;
}
// If no expires param found, assume expired to be safe
return true;
}
/**
* Refresh media URLs for a listing from the API
*
* Fetches fresh media data from MLS Grid and updates the database.
* This is used when cached URLs have expired.
*
* Note: MLS Grid API only allows filtering by ListingId, not ListingKey.
* This method looks up the listing_id from the local database first.
*
* @param string $listing_key Listing key
* @return bool True on success, false on failure
*/
public function refresh_media_urls($listing_key) {
global $wpdb;
$plugin = mls_plugin();
$api_client = $plugin->get_api_client();
// Look up listing_id from database (MLS Grid API requires ListingId for filtering)
$listing_id = $wpdb->get_var($wpdb->prepare(
"SELECT listing_id FROM {$this->db->properties_table()} WHERE listing_key = %s",
$listing_key
));
if (!$listing_id) {
$this->logger->warning('Cannot refresh media: listing_id not found', array(
'listing_key' => $listing_key,
));
return false;
}
// Fetch property with media from API using ListingId
$property = $api_client->get_property_media($listing_id);
if (is_wp_error($property) || !$property) {
$this->logger->warning('Failed to refresh media URLs', array(
'listing_key' => $listing_key,
'listing_id' => $listing_id,
'error' => is_wp_error($property) ? $property->get_error_message() : 'Property not found',
));
return false;
}
// Update media records with fresh URLs
if (isset($property['Media']) && is_array($property['Media'])) {
$this->sync_property_media($listing_key, $property['Media']);
$this->logger->debug('Refreshed media URLs', array(
'listing_key' => $listing_key,
'media_count' => count($property['Media']),
));
return true;
}
return false;
}
/**
* Clean up orphaned media files (files without database records)
*