Snapshot: MLS sync fixes, image refresh, plugin/theme updates

MLS plugin fixes from this session:
- Fix silent insert failures: location column NOT NULL was rejecting wpdb->insert calls,
  causing ~18k new properties since Dec 2025 to be lost. Inserts now build raw SQL
  with ST_PointFromText so the spatial column is populated atomically.
- Auto-refresh expired media URLs in MLS_Media_Handler::fetch_and_cache(), guarded by
  a property-level GET_LOCK so concurrent fetches share one API refresh.
- Normalize WP_Error to null in mls_get_property_image() so callers can rely on the
  documented string|null contract.
- Support comma-separated property_type filters in MLS_Query and MLS_Cluster so the
  homepage "View All Commercial" link (?property_type=Commercial+Sale,Land,Farm)
  actually filters correctly.
- Incremental sync now looks back 10 minutes past the latest modification timestamp
  as a safety margin against missed records.
- Smart sync exits silently (info-level, not warning) when a full sync is in progress.

Operational:
- New cron: weekly full sync Sundays at 3 AM (/usr/local/bin/mls-full-sync).
- New cron: hourly 2GB cap on mls-thumbnails/ and cache/transformed-images/
  (/usr/local/bin/mls-image-cache-cap).
- Logrotate config for wp-content/debug.log (2-day retention, daily rotation,
  delaycompress).

Repo policy:
- CLAUDE.md updated with explicit "commit everything except build artifacts" policy.
- .gitignore: untrack runtime image caches and debug.log rotations.

Other modifications in this snapshot are pre-existing in-flight theme/plugin/db_content_updates work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-29 15:32:23 +00:00
parent 57b752f54e
commit b6df4dbb92
5385 changed files with 838580 additions and 2416 deletions
@@ -128,11 +128,12 @@ class MLS_API_Client {
* @param string $endpoint API endpoint (relative to base URL)
* @param array $params Query parameters
* @param int $retry Current retry attempt
* @param string $channel Rate limit channel ('general' or 'image')
* @return array|WP_Error Response data or error
*/
public function request($endpoint, $params = array(), $retry = 0) {
// Check and wait for rate limits
$this->rate_limiter->check_and_wait(true);
public function request($endpoint, $params = array(), $retry = 0, $channel = 'general') {
// Check and wait for rate limits (uses global advisory lock coordination)
$this->rate_limiter->check_and_wait(true, $channel);
$url = $this->build_url($endpoint, $params);
@@ -172,7 +173,7 @@ class MLS_API_Client {
// Retry on transient errors
if ($retry < self::MAX_RETRIES) {
sleep(pow(2, $retry)); // Exponential backoff
return $this->request($endpoint, $params, $retry + 1);
return $this->request($endpoint, $params, $retry + 1, $channel);
}
return $response;
@@ -195,7 +196,7 @@ class MLS_API_Client {
if (($status_code === 429 || $status_code >= 500) && $retry < self::MAX_RETRIES) {
$wait = $status_code === 429 ? 60 : pow(2, $retry);
sleep($wait);
return $this->request($endpoint, $params, $retry + 1);
return $this->request($endpoint, $params, $retry + 1, $channel);
}
return new WP_Error('api_error', $error_message, array('status' => $status_code));
@@ -382,7 +383,8 @@ class MLS_API_Client {
* Get a single property by listing ID with media
*
* Used to refresh media URLs for a specific listing without
* fetching the entire dataset.
* fetching the entire dataset. Uses the 'image' rate limit channel
* with a 2-second interval for on-demand image requests.
*
* Note: MLS Grid only allows filtering by ListingId (not ListingKey)
* for the Property resource. The caller must provide the listing_id.
@@ -400,7 +402,8 @@ class MLS_API_Client {
$params['$expand'] = 'Media';
$params['$top'] = 1;
$response = $this->request('Property', $params);
// Use 'image' channel with 2-second rate limiting for on-demand media fetches
$response = $this->request('Property', $params, 0, 'image');
if (is_wp_error($response)) {
return $response;
@@ -414,6 +417,41 @@ class MLS_API_Client {
return null;
}
/**
* Get multiple properties by listing IDs with media (batched)
*
* Fetches up to 25 properties in a single API request using OData 'in' filter.
* Used for efficient media URL refresh without making individual API calls.
* Uses the 'image' rate limit channel with 2-second interval.
*
* @param array $listing_ids Array of MLS listing IDs (max 25)
* @return array|WP_Error Array of property data with Media, or error
*/
public function get_properties_by_ids($listing_ids) {
if (empty($listing_ids)) {
return array('value' => array());
}
// Limit to 25 (MLS Grid's max with $expand)
$listing_ids = array_slice($listing_ids, 0, 25);
$params = array();
$system = $this->options->get_originating_system();
// Build 'in' filter: ListingId in ('ID1', 'ID2', 'ID3')
$escaped_ids = array_map(function($id) {
return "'" . addslashes($id) . "'";
}, $listing_ids);
$in_list = implode(',', $escaped_ids);
$params['$filter'] = "OriginatingSystemName eq '{$system}' and ListingId in ({$in_list})";
$params['$expand'] = 'Media';
$params['$top'] = 25;
// Use 'image' channel with 2-second rate limiting for media fetches
return $this->request('Property', $params, 0, 'image');
}
/**
* Get next page of results
*