From e89467b6b7975a49e0a0728695d401dbbf13fc52 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 17:55:12 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20review=20#19=20=E2=80=94=20offer=20accep?= =?UTF-8?q?t=20race=20condition=20(FOR=20UPDATE),=20geo=20search=20HAVING?= =?UTF-8?q?=E2=86=92subquery,=20count=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Helper/OfferHelper.php | 37 ++++++++++++------- .../src/Helper/PropertySearchHelper.php | 26 +++++++++---- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php b/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php index 54ad2c5..a29a3f1 100644 --- a/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php +++ b/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php @@ -60,27 +60,37 @@ class OfferHelper { $db = Factory::getContainer()->get(DatabaseInterface::class); - $db->setQuery($db->getQuery(true) - ->select('listing_id') - ->from('#__mokosuiterealty_offers') - ->where('id = ' . (int) $offerId)); - $listingId = (int) $db->loadResult(); - - if (!$listingId) { - return (object) ['success' => false, 'error' => 'Offer not found']; - } - $db->transactionStart(); try { + // Lock and verify the offer is still in submitted status + $db->setQuery( + 'SELECT listing_id FROM #__mokosuiterealty_offers' + . ' WHERE id = ' . (int) $offerId + . ' AND ' . $db->quoteName('status') . ' = ' . $db->quote('submitted') + . ' FOR UPDATE' + ); + $listingId = (int) $db->loadResult(); + + if (!$listingId) { + $db->transactionRollback(); + return (object) ['success' => false, 'error' => 'Offer not found or already processed']; + } + // Accept this offer $db->setQuery($db->getQuery(true) ->update('#__mokosuiterealty_offers') ->set($db->quoteName('status') . ' = ' . $db->quote('accepted')) ->set('accepted_at = ' . $db->quote(Factory::getDate()->toSql())) - ->where('id = ' . (int) $offerId)); + ->where('id = ' . (int) $offerId) + ->where($db->quoteName('status') . ' = ' . $db->quote('submitted'))); $db->execute(); + if ($db->getAffectedRows() === 0) { + $db->transactionRollback(); + return (object) ['success' => false, 'error' => 'Offer already accepted or rejected']; + } + // Reject all other offers on this listing $db->setQuery($db->getQuery(true) ->update('#__mokosuiterealty_offers') @@ -90,11 +100,12 @@ class OfferHelper ->where($db->quoteName('status') . ' = ' . $db->quote('submitted'))); $db->execute(); - // Update listing to pending + // Update listing to pending (only if still active) $db->setQuery($db->getQuery(true) ->update('#__mokosuiterealty_listings') ->set($db->quoteName('status') . ' = ' . $db->quote('pending')) - ->where('id = ' . $listingId)); + ->where('id = ' . $listingId) + ->where($db->quoteName('status') . ' = ' . $db->quote('active'))); $db->execute(); $db->transactionCommit(); diff --git a/source/packages/plg_system_mokosuiterealty/src/Helper/PropertySearchHelper.php b/source/packages/plg_system_mokosuiterealty/src/Helper/PropertySearchHelper.php index 8fbed92..5d57275 100644 --- a/source/packages/plg_system_mokosuiterealty/src/Helper/PropertySearchHelper.php +++ b/source/packages/plg_system_mokosuiterealty/src/Helper/PropertySearchHelper.php @@ -45,21 +45,33 @@ class PropertySearchHelper $radius = (float) $filters['radius_miles']; $query->select("(3959 * ACOS(COS(RADIANS({$lat})) * COS(RADIANS(l.latitude)) * COS(RADIANS(l.longitude) - RADIANS({$lng})) + SIN(RADIANS({$lat})) * SIN(RADIANS(l.latitude)))) AS distance"); - $query->having('distance <= ' . $radius); - $query->order('distance ASC'); + + // Bounding box pre-filter for index usage + $latDelta = $radius / 69.0; + $lngDelta = $radius / (69.0 * cos(deg2rad($lat))); + $query->where('l.latitude BETWEEN ' . ($lat - $latDelta) . ' AND ' . ($lat + $latDelta)); + $query->where('l.longitude BETWEEN ' . ($lng - $lngDelta) . ' AND ' . ($lng + $lngDelta)); + + // Wrap in subquery to filter by computed distance + $db->setQuery('SELECT * FROM (' . $query . ') AS geo WHERE distance <= ' . $radius . ' ORDER BY distance ASC', $offset, min(max(1, $limit), 100)); } else { $query->order('l.listed_at DESC'); + $db->setQuery($query, $offset, min(max(1, $limit), 100)); } - // Count total before limit - $countQuery = clone $query; - $countQuery->clear('select')->clear('order')->select('COUNT(DISTINCT l.id)'); - - $db->setQuery($query, $offset, min(max(1, $limit), 100)); $results = $db->loadObjectList() ?: []; + // Get total count (without pagination) + $countQuery = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__mokosuiterealty_listings', 'l')) + ->where($db->quoteName('l.status') . ' = ' . $db->quote('active')); + $db->setQuery($countQuery); + $total = (int) $db->loadResult(); + return (object) [ 'listings' => $results, + 'total' => $total, 'count' => count($results), 'offset' => $offset, 'limit' => $limit,