{"sha":"983e978a4a8388b2506cfd6bdc9262a8f9d16292","node_id":"C_kwDOLgQlp9oAKDk4M2U5NzhhNGE4Mzg4YjI1MDZjZmQ2YmRjOTI2MmE4ZjlkMTYyOTI","commit":{"author":{"name":"Sigurd Nes","email":"sigurdne@users.noreply.github.com","date":"2026-05-05T06:56:55Z"},"committer":{"name":"GitHub","email":"noreply@github.com","date":"2026-05-05T06:56:55Z"},"message":"Merge pull request #1005 from PorticoEstate/Version-3_0-branch","tree":{"sha":"dbd75306eb80c1234fc11d4ea53c70fb83fe2f7b","url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/git/trees/dbd75306eb80c1234fc11d4ea53c70fb83fe2f7b"},"url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/git/commits/983e978a4a8388b2506cfd6bdc9262a8f9d16292","comment_count":0,"verification":{"verified":true,"reason":"valid","signature":"-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJp+ZS3CRC1aQ7uu5UhlAAAYRIQAE24gPO9tl5fvhjuY8DlOQHD\n9r0QTMovMbPS+aq6a6CBtuRNXpTzQY0eFTBNkksHZ1SZNQRULXfWvH0i/6S8qnQz\nDKqtjmBZKf0SNZeoZzCOlxjjDycm29IrxNgaf0Z4LPm3fZBBy4frgRY64yvkshla\nOcMhJXWYPLD/hpT0ePt/8JT0yfF0EsGTZaO00z+O333RueowvbZMzLai0W5meVth\nC1Qcn5o7u06sKcukp0GudNDQqJGNgldMbBx+UkfROTZcUjj2fGZNPyZ0Oc9BsVqk\n9sq7ha7sbvZczfn+EwjG4INHpS5RTizLtiR2EBS4VrPi5PYKLTA8DtS1UD7zX2Kr\nm4uFHC9iMKEJTk7/sIJPA5V0GK1c2dkaBZTQpXM77icGcqw8IV7p2arNYWVYi4Oh\nitopl8Tn0C3pCcOUsJU2dyUeKedUgdPmokUjoskhU5gRigQztHz9U9c+kykZlIM2\nP2Ej4JNCtrP8iGH7gyWMknP9flUoy113YgCzZaDd8oA5EUSL1Qprn0iqtNswfQLg\n0VKCf1kdtBNMrkqyWsBzmbbRlNFTtNYzWqJ/atrKRcpDWXfY8UCD9AiK8Oj9qEll\nJn368OQx0GNeHiXc9xKk01IeH/nEk7EJzaiyBRQ2Jzg0nudVYo98GF8i7I93qEOC\nND2UftZCWrTZ0jiqfZ40\n=wj14\n-----END PGP SIGNATURE-----\n","payload":"tree dbd75306eb80c1234fc11d4ea53c70fb83fe2f7b\nparent 9e34bdb99fc439d9f2169b5ac5ce403410b3c0f3\nparent d024ed01db8c7e0c15ebcad0e5c9c7523826b9c8\nauthor Sigurd Nes <sigurdne@users.noreply.github.com> 1777964215 +0200\ncommitter GitHub <noreply@github.com> 1777964215 +0200\n\nMerge pull request #1005 from PorticoEstate/Version-3_0-branch","verified_at":"2026-05-05T06:56:55Z"}},"url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/983e978a4a8388b2506cfd6bdc9262a8f9d16292","html_url":"https://github.com/PorticoEstate/PorticoEstate-v2/commit/983e978a4a8388b2506cfd6bdc9262a8f9d16292","comments_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/983e978a4a8388b2506cfd6bdc9262a8f9d16292/comments","author":{"login":"sigurdne","id":12719970,"node_id":"MDQ6VXNlcjEyNzE5OTcw","avatar_url":"https://avatars.githubusercontent.com/u/12719970?v=4","gravatar_id":"","url":"https://api.github.com/users/sigurdne","html_url":"https://github.com/sigurdne","followers_url":"https://api.github.com/users/sigurdne/followers","following_url":"https://api.github.com/users/sigurdne/following{/other_user}","gists_url":"https://api.github.com/users/sigurdne/gists{/gist_id}","starred_url":"https://api.github.com/users/sigurdne/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sigurdne/subscriptions","organizations_url":"https://api.github.com/users/sigurdne/orgs","repos_url":"https://api.github.com/users/sigurdne/repos","events_url":"https://api.github.com/users/sigurdne/events{/privacy}","received_events_url":"https://api.github.com/users/sigurdne/received_events","type":"User","user_view_type":"public","site_admin":false},"committer":{"login":"web-flow","id":19864447,"node_id":"MDQ6VXNlcjE5ODY0NDQ3","avatar_url":"https://avatars.githubusercontent.com/u/19864447?v=4","gravatar_id":"","url":"https://api.github.com/users/web-flow","html_url":"https://github.com/web-flow","followers_url":"https://api.github.com/users/web-flow/followers","following_url":"https://api.github.com/users/web-flow/following{/other_user}","gists_url":"https://api.github.com/users/web-flow/gists{/gist_id}","starred_url":"https://api.github.com/users/web-flow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/web-flow/subscriptions","organizations_url":"https://api.github.com/users/web-flow/orgs","repos_url":"https://api.github.com/users/web-flow/repos","events_url":"https://api.github.com/users/web-flow/events{/privacy}","received_events_url":"https://api.github.com/users/web-flow/received_events","type":"User","user_view_type":"public","site_admin":false},"parents":[{"sha":"9e34bdb99fc439d9f2169b5ac5ce403410b3c0f3","url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/9e34bdb99fc439d9f2169b5ac5ce403410b3c0f3","html_url":"https://github.com/PorticoEstate/PorticoEstate-v2/commit/9e34bdb99fc439d9f2169b5ac5ce403410b3c0f3"},{"sha":"d024ed01db8c7e0c15ebcad0e5c9c7523826b9c8","url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/commits/d024ed01db8c7e0c15ebcad0e5c9c7523826b9c8","html_url":"https://github.com/PorticoEstate/PorticoEstate-v2/commit/d024ed01db8c7e0c15ebcad0e5c9c7523826b9c8"}],"stats":{"total":127,"additions":74,"deletions":53},"files":[{"sha":"34c34f91cbb74abf87a91a6a4dca202c62f4e620","filename":"src/modules/booking/inc/class.sobooking.inc.php","status":"modified","additions":20,"deletions":0,"changes":20,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbooking%2Finc%2Fclass.sobooking.inc.php","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbooking%2Finc%2Fclass.sobooking.inc.php","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fbooking%2Finc%2Fclass.sobooking.inc.php?ref=983e978a4a8388b2506cfd6bdc9262a8f9d16292","patch":"@@ -344,6 +344,11 @@ function allocation_ids_for_organization($organization_id, $resource_id, $start,\n \t\t\t$resource_ids = array((int)$resource_id);\n \t\t}\n \n+\t\tif (!$resource_ids)\n+\t\t{\n+\t\t\treturn array();\n+\t\t}\n+\n \t\t$start = $start->format('Y-m-d H:i');\n \t\t$end = $end->format('Y-m-d H:i');\n \n@@ -390,6 +395,11 @@ function booking_ids_for_organization($organization_group_id, $resource_id, $sta\n \t\t\t$resource_ids = array((int)$resource_id);\n \t\t}\n \n+\t\tif (!$resource_ids)\n+\t\t{\n+\t\t\treturn array();\n+\t\t}\n+\n \t\t$start = $start->format('Y-m-d H:i');\n \t\t$end = $end->format('Y-m-d H:i');\n \n@@ -512,6 +522,11 @@ function allocation_ids_for_resource($resource_id, $start, $end)\n \t\t\t$resource_ids = array((int)$resource_id);\n \t\t}\n \n+\t\tif (!$resource_ids)\n+\t\t{\n+\t\t\treturn array();\n+\t\t}\n+\n \t\t$start = $start->format('Y-m-d H:i');\n \t\t$end = $end->format('Y-m-d H:i');\n \t\t$results = array();\n@@ -546,6 +561,11 @@ function booking_ids_for_resource($resource_id, $start, $end)\n \t\t\t$resource_ids = array((int)$resource_id);\n \t\t}\n \n+\t\tif (!$resource_ids)\n+\t\t{\n+\t\t\treturn array();\n+\t\t}\n+\n \t\t$start = $start->format('Y-m-d H:i');\n \t\t$end = $end->format('Y-m-d H:i');\n \t\t$results = array();"},{"sha":"5a8165eb8914dde55f27f7369656563075e82ae8","filename":"src/modules/bookingfrontend/client/src/service/api/api-utils.ts","status":"modified","additions":6,"deletions":9,"changes":15,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbookingfrontend%2Fclient%2Fsrc%2Fservice%2Fapi%2Fapi-utils.ts","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbookingfrontend%2Fclient%2Fsrc%2Fservice%2Fapi%2Fapi-utils.ts","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fbookingfrontend%2Fclient%2Fsrc%2Fservice%2Fapi%2Fapi-utils.ts?ref=983e978a4a8388b2506cfd6bdc9262a8f9d16292","patch":"@@ -114,15 +114,12 @@ export async function fetchBuildingSeasons(building_id: number, instance?: strin\n \n \n export async function fetchFreeTimeSlotsForRange(building_id: number, start: DateTime, end: DateTime, instance?: string): Promise<Record<string, IFreeTimeSlot[]>> {\n-\tconst url = phpGWLink('bookingfrontend/', {\n-\t\tmenuaction: 'bookingfrontend.uibooking.get_freetime',\n-\t\tbuilding_id,\n-\t\tstart_date: start.toFormat('dd/LL-yyyy'),\n-\t\tend_date: end.toFormat('dd/LL-yyyy'),\n-\t\tdetailed_overlap: true,\n-\t\tstop_on_end_date: true\n-\t}, true, instance);\n-\tconsole.log(\"FETCHING FREE TIME SLOTS FOR RANGE\", url);\n+\tconst url = phpGWLink(['bookingfrontend', 'buildings', building_id, 'freetime'], {\n+\t\tstart_date: start.toFormat('yyyy-MM-dd'),\n+\t\tend_date: end.toFormat('yyyy-MM-dd'),\n+\t\tdetailed_overlap: 'true',\n+\t\tstop_on_end_date: 'true'\n+\t}, false, instance);\n \tconst response = await fetch(url);\n \tconst result = await response.json();\n \treturn result;"},{"sha":"90cacc3f581d004680fe3d7a4568e14f5dc13be5","filename":"src/modules/bookingfrontend/controllers/applications/ApplicationController.php","status":"modified","additions":10,"deletions":26,"changes":36,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbookingfrontend%2Fcontrollers%2Fapplications%2FApplicationController.php","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbookingfrontend%2Fcontrollers%2Fapplications%2FApplicationController.php","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fbookingfrontend%2Fcontrollers%2Fapplications%2FApplicationController.php?ref=983e978a4a8388b2506cfd6bdc9262a8f9d16292","patch":"@@ -11,6 +11,7 @@\n use App\\modules\\bookingfrontend\\repositories\\ApplicationRepository;\n use App\\modules\\bookingfrontend\\repositories\\ArticleRepository;\n use App\\modules\\bookingfrontend\\services\\applications\\ApplicationService;\n+use App\\modules\\bookingfrontend\\services\\FreeTimeService;\n use App\\modules\\phpgwapi\\security\\Sessions;\n use App\\modules\\phpgwapi\\services\\Settings;\n use Psr\\Container\\ContainerInterface;\n@@ -1399,32 +1400,15 @@ protected function broadcastPartialApplicationCreated(int $id, array $data): voi\n      */\n     protected function getAffectedTimeslots(int $buildingId, int $resourceId, \\DateTime $startDate, \\DateTime $endDate): array\n     {\n-        // Create a booking business object to access the get_free_events method\n-        $bo = \\CreateObject('booking.bobooking');\n-\n-        // Format dates for the get_free_events method\n-        $formattedStartDate = $startDate->format('d/m-Y');\n-        $formattedEndDate = $endDate->format('d/m-Y');\n-\n-        // Save current request parameters\n-        $originalRequest = $_REQUEST;\n-\n-        // Set up the request parameters for get_freetime\n-        $_REQUEST['building_id'] = $buildingId;\n-        $_REQUEST['resource_id'] = $resourceId;\n-        $_REQUEST['start_date'] = $formattedStartDate;\n-        $_REQUEST['end_date'] = $formattedEndDate;\n-        $_REQUEST['detailed_overlap'] = true;\n-        $_REQUEST['stop_on_end_date'] = true;\n-\n-        // Use the existing logic to get timeslots\n-        $uibooking = \\CreateObject('bookingfrontend.uibooking');\n-        $timeslots = $uibooking->get_freetime();\n-\n-        // Restore original request parameters\n-        $_REQUEST = $originalRequest;\n-\n-        // Return the timeslots for this resource\n+        $freeTimeService = new FreeTimeService();\n+        $timeslots = $freeTimeService->getFreeTime(\n+            $buildingId,\n+            $resourceId,\n+            $startDate->format('Y-m-d'),\n+            $endDate->format('Y-m-d'),\n+            true,  // detailed_overlap\n+            true   // stop_on_end_date\n+        );\n         return isset($timeslots[$resourceId]) ? $timeslots[$resourceId] : [];\n     }\n "},{"sha":"61f89dd41a8055385448bf7877a1f8176b32dc9b","filename":"src/modules/bookingfrontend/services/FreeTimeService.php","status":"modified","additions":38,"deletions":18,"changes":56,"blob_url":"https://github.com/PorticoEstate/PorticoEstate-v2/blob/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbookingfrontend%2Fservices%2FFreeTimeService.php","raw_url":"https://github.com/PorticoEstate/PorticoEstate-v2/raw/983e978a4a8388b2506cfd6bdc9262a8f9d16292/src%2Fmodules%2Fbookingfrontend%2Fservices%2FFreeTimeService.php","contents_url":"https://api.github.com/repos/PorticoEstate/PorticoEstate-v2/contents/src%2Fmodules%2Fbookingfrontend%2Fservices%2FFreeTimeService.php?ref=983e978a4a8388b2506cfd6bdc9262a8f9d16292","patch":"@@ -220,9 +220,13 @@ private function getFreeEvents(\n \n \t\t\t// Fetch overlapping entity IDs (only for simple_booking resources)\n \t\t\tif ($resource['simple_booking'] && empty($resource['skip_timeslot'])) {\n+\t\t\t\t$this->tick(\"resource_{$resource['id']}_event_ids_start\");\n \t\t\t\t$eventIds = array_merge($eventIds, $this->eventIdsForResource($resource['id'], $_from, $to));\n+\t\t\t\t$this->tick(\"resource_{$resource['id']}_event_ids_done\");\n \t\t\t\t$allocationIds = array_merge($allocationIds, $this->allocationIdsForResource($resource['id'], $from, $to));\n+\t\t\t\t$this->tick(\"resource_{$resource['id']}_allocation_ids_done\");\n \t\t\t\t$bookingIds = array_merge($bookingIds, $this->bookingIdsForResource($resource['id'], $from, $to));\n+\t\t\t\t$this->tick(\"resource_{$resource['id']}_booking_ids_done\");\n \t\t\t}\n \n \t\t\t$resource['from'] = $from;\n@@ -242,7 +246,7 @@ private function getFreeEvents(\n \n \t\t// Get partials and blocks\n \t\t$this->tick('fetch_partials_start');\n-\t\t$this->getPartials($events, $resourceIds);\n+\t\t$this->getPartials($events, $resourceIds, $_from, $_to);\n \t\t$this->tick('fetch_partials_done');\n \n \t\t// Combine all into events\n@@ -308,20 +312,22 @@ private function allocationIdsForResource(int $resourceId, \\DateTime $start, \\Da\n \t\t$startStr = $start->format('Y-m-d H:i');\n \t\t$endStr = $end->format('Y-m-d H:i');\n \n+\t\t// Simplified from legacy: removed unnecessary bb_resource + bb_building_resource joins.\n+\t\t// The legacy query joined those to verify season.building_id = resource.building_id,\n+\t\t// but since we filter by resource_id in bb_allocation_resource, and seasons are already\n+\t\t// linked to allocations via season_id, the building check is redundant.\n \t\t$sql = \"SELECT DISTINCT bb_allocation.id AS id\n                 FROM bb_allocation\n                 JOIN bb_allocation_resource ON (allocation_id = bb_allocation.id AND resource_id = ?)\n-                JOIN bb_resource AS res ON (res.id = ?)\n-                JOIN bb_season ON (bb_allocation.season_id = bb_season.id AND bb_allocation.active = 1)\n-                JOIN bb_building_resource ON bb_building_resource.resource_id = res.id\n-                WHERE bb_season.building_id = bb_building_resource.building_id\n+                JOIN bb_season ON (bb_allocation.season_id = bb_season.id)\n+                WHERE bb_allocation.active = 1\n                 AND bb_season.active = 1\n                 AND bb_season.status = 'PUBLISHED'\n                 AND ((bb_allocation.from_ >= ? AND bb_allocation.from_ < ?)\n                     OR (bb_allocation.to_ > ? AND bb_allocation.to_ <= ?)\n                     OR (bb_allocation.from_ < ? AND bb_allocation.to_ > ?))\";\n \t\t$stmt = $this->db->prepare($sql);\n-\t\t$stmt->execute([$resourceId, $resourceId, $startStr, $endStr, $startStr, $endStr, $startStr, $endStr]);\n+\t\t$stmt->execute([$resourceId, $startStr, $endStr, $startStr, $endStr, $startStr, $endStr]);\n \n \t\treturn array_column($stmt->fetchAll(\\PDO::FETCH_ASSOC), 'id');\n \t}\n@@ -334,20 +340,19 @@ private function bookingIdsForResource(int $resourceId, \\DateTime $start, \\DateT\n \t\t$startStr = $start->format('Y-m-d H:i');\n \t\t$endStr = $end->format('Y-m-d H:i');\n \n+\t\t// Simplified from legacy: same optimization as allocationIdsForResource\n \t\t$sql = \"SELECT bb_booking.id AS id\n                 FROM bb_booking\n                 JOIN bb_booking_resource ON (booking_id = bb_booking.id AND resource_id = ?)\n-                JOIN bb_resource AS res ON (res.id = ?)\n-                JOIN bb_season ON (bb_booking.season_id = bb_season.id AND bb_booking.active = 1)\n-                JOIN bb_building_resource ON bb_building_resource.resource_id = res.id\n-                WHERE bb_season.building_id = bb_building_resource.building_id\n+                JOIN bb_season ON (bb_booking.season_id = bb_season.id)\n+                WHERE bb_booking.active = 1\n                 AND bb_season.active = 1\n                 AND bb_season.status = 'PUBLISHED'\n                 AND ((bb_booking.from_ >= ? AND bb_booking.from_ < ?)\n                     OR (bb_booking.to_ > ? AND bb_booking.to_ <= ?)\n                     OR (bb_booking.from_ < ? AND bb_booking.to_ > ?))\";\n \t\t$stmt = $this->db->prepare($sql);\n-\t\t$stmt->execute([$resourceId, $resourceId, $startStr, $endStr, $startStr, $endStr, $startStr, $endStr]);\n+\t\t$stmt->execute([$resourceId, $startStr, $endStr, $startStr, $endStr, $startStr, $endStr]);\n \n \t\treturn array_column($stmt->fetchAll(\\PDO::FETCH_ASSOC), 'id');\n \t}\n@@ -418,12 +423,16 @@ private function fetchEntities(string $type, array $ids): array\n \t * Port of bobooking::get_partials\n \t * Fetches partial applications for current session and active blocks.\n \t */\n-\tprivate function getPartials(array &$events, array $resourceIds): void\n+\tprivate function getPartials(array &$events, array $resourceIds, \\DateTime $from, \\DateTime $to): void\n \t{\n \t\t$sessions = Sessions::getInstance();\n \t\t$sessionId = $sessions->get_session_id();\n \n-\t\t// Fetch partial applications for current session\n+\t\t$fromStr = $from->format('Y-m-d H:i');\n+\t\t$toStr = $to->format('Y-m-d H:i');\n+\n+\t\t// Fetch partial applications for current session, filtered by date range\n+\t\t$this->tick('partials_session_query_start');\n \t\tif (!empty($sessionId)) {\n \t\t\t$sql = \"SELECT a.id, a.status,\n                     ad.from_, ad.to_,\n@@ -432,9 +441,12 @@ private function getPartials(array &$events, array $resourceIds): void\n                     JOIN bb_application_date ad ON ad.application_id = a.id\n                     JOIN bb_application_resource ar ON ar.application_id = a.id\n                     WHERE a.status = 'NEWPARTIAL1'\n-                    AND a.session_id = ?\";\n+                    AND a.session_id = ?\n+                    AND ((ad.from_ >= ? AND ad.from_ < ?)\n+                        OR (ad.to_ > ? AND ad.to_ <= ?)\n+                        OR (ad.from_ < ? AND ad.to_ > ?))\";\n \t\t\t$stmt = $this->db->prepare($sql);\n-\t\t\t$stmt->execute([$sessionId]);\n+\t\t\t$stmt->execute([$sessionId, $fromStr, $toStr, $fromStr, $toStr, $fromStr, $toStr]);\n \t\t\t$rows = $stmt->fetchAll(\\PDO::FETCH_ASSOC);\n \n \t\t\t$grouped = [];\n@@ -458,15 +470,23 @@ private function getPartials(array &$events, array $resourceIds): void\n \t\t\t}\n \t\t}\n \n-\t\t// Fetch active blocks for these resources\n+\t\t$this->tick('partials_session_query_done');\n+\t\t// Fetch active blocks for these resources, filtered by date range\n+\t\t$this->tick('blocks_query_start');\n \t\tif (!empty($resourceIds)) {\n \t\t\t$placeholders = implode(',', array_fill(0, count($resourceIds), '?'));\n \t\t\t$sql = \"SELECT id, from_, to_, resource_id, session_id\n                     FROM bb_block\n-                    WHERE active = 1 AND resource_id IN ($placeholders)\";\n+                    WHERE active = 1 AND resource_id IN ($placeholders)\n+                    AND ((from_ >= ? AND from_ < ?)\n+                        OR (to_ > ? AND to_ <= ?)\n+                        OR (from_ < ? AND to_ > ?))\";\n+\t\t\t$params = array_values($resourceIds);\n+\t\t\tarray_push($params, $fromStr, $toStr, $fromStr, $toStr, $fromStr, $toStr);\n \t\t\t$stmt = $this->db->prepare($sql);\n-\t\t\t$stmt->execute(array_values($resourceIds));\n+\t\t\t$stmt->execute($params);\n \t\t\t$blocks = $stmt->fetchAll(\\PDO::FETCH_ASSOC);\n+\t\t\t$this->tick('blocks_query_done (' . count($blocks) . ' blocks)');\n \n \t\t\tforeach ($blocks as $block) {\n \t\t\t\t// Skip blocks from current session (same as legacy)"}]}