'wi-fi', 'free wifi' => 'wi-fi', 'wifi' => 'wi-fi', 'wi-fi' => 'wi-fi', 'wifi in all areas' => 'wi-fi', 'free wi-fi' => 'wi-fi', 'internet' => 'wi-fi', 'internet access' => 'wi-fi', 'airport shuttle' => 'airport transfer', 'airport pick up' => 'airport to hotel transfer', 'airport drop off' => 'hotel to airport transfer', 'car hire' => 'rent a car', 'car rental' => 'rent a car', 'shuttle service' => 'airport transfer', '24-hour front desk' => 'reception', 'concierge service' => 'concierge', 'luggage storage' => 'luggage storage', 'laundry' => 'cleaning service', 'daily housekeeping' => 'housekeeping', 'tour desk' => 'tour desk', 'private check-in/check-out' => 'check-in service', 'express check-in/check-out' => 'check-in service', 'wake-up service' => 'wake up service', 'dry cleaning' => 'dry cleaning', 'currency exchange' => 'currency exchange', 'facilities for disabled guests' => 'disabled', 'wheelchair accessible' => 'wheelchair', 'upper floors accessible by elevator' => 'elevator', 'lower bathroom sink' => 'lower bathroom sink', 'toilet with grab rails' => 'toilet with grab rails', 'breakfast' => 'bed and breakfast', 'snack bar' => 'snack bar', 'meeting/banquet facilities' => 'meeting room', 'business centre' => 'working area', 'business center' => 'working area', 'fax/photocopying' => 'photocopy', 'air conditioning' => 'air conditioner', 'flat-screen tv' => 'tv', 'flat screen tv' => 'tv', 'bath or shower' => 'shower', 'free toiletries' => 'shower', 'single-room air conditioning for guest accommodation' => 'air conditioner', 'single-room ac for guest accommodation' => 'air conditioner', 'minibar' => 'mini bar', 'hairdryer' => 'hair dryer', 'hair dryer' => 'hair dryer', 'wardrobe or closet' => 'clothes cabinet', 'socket near the bed' => 'planning electrical sockets', 'family rooms' => 'family room', 'clothes rack' => 'clotheshorse', 'cctv in common areas' => 'security camera', 'cctv outside property' => 'security camera', 'smoke alarms' => 'smoke alarm', 'safety deposit box' => 'safety deposit box', '24-hour security' => 'security service', 'key card access' => 'security service', 'hand sanitizer in guest accommodation and key areas' => 'hand sanitiser', 'hand sanitiser' => 'hand sanitiser', 'hand sanitizer' => 'hand sanitiser', 'face masks for guests available' => 'protective masks for guests', 'physical distancing rules followed' => 'social distancing regulations', 'screens or physical barriers placed between staff and guests in appropriate areas' => 'protective hygiene screens', 'guest accommodation is disinfected between stays' => 'disinfection in all rooms', 'cashless payment available' => 'contactless payment', 'food can be delivered to guest accommodation' => 'food delivery', 'tennis court' => 'tennis', 'golf course' => 'golf', 'table tennis' => 'ping pong', 'jogging track' => 'jogging', 'lift' => 'elevator', 'elevator' => 'elevator', ); // ───────────────────────────────────────────────────────────────────────────── // Oda olanaklarına özel alias: Booking.com oda facility adı → property_fact.name // ───────────────────────────────────────────────────────────────────────────── private $roomAliasMap = array( 'safe' => 'safe box', 'towels' => 'towel', 'linens' => 'towel', 'tea/coffee maker' => 'coffee maker', 'coffee/tea maker' => 'coffee maker', 'electric kettle' => 'kettle', 'private bathroom' => 'bathroom', 'telephone' => 'telephone', 'toilet paper' => 'toilet', 'free toiletries' => 'hair dryer', 'slippers' => 'bathrobe', 'bath or shower' => 'shower', 'bathtub or shower' => 'shower', 'shower only' => 'shower', ); // ───────────────────────────────────────────────────────────────────────────── // GuzzleHttp → Booking.com API // ───────────────────────────────────────────────────────────────────────────── private function bookingClient() { return new Client(array( 'timeout' => 30, 'headers' => array( 'x-rapidapi-host' => env('BULUT_RAPIDAPI_HOST', self::API_HOST), 'x-rapidapi-key' => env('BULUT_RAPIDAPI_KEY'), 'Content-Type' => 'application/json', ), )); } // ───────────────────────────────────────────────────────────────────────────── // ADIM 0: Booking.com'dan otel verilerini çek // ───────────────────────────────────────────────────────────────────────────── private function fetchBookingData($hotelId, $checkin, $checkout) { $client = $this->bookingClient(); // Otel detayı (property bilgileri: isim, konum, dil, timezone vb.) $detailResp = $client->get(self::API_BASE . '/api/v1/hotels/getHotelDetails', array( 'query' => array( 'hotel_id' => $hotelId, 'arrival_date' => $checkin, 'departure_date' => $checkout, 'adults' => 1, 'room_qty' => 1, 'languagecode' => 'en-us', 'currency_code' => 'USD', 'units' => 'metric', 'temperature_unit' => 'c', ), )); $detail = json_decode($detailResp->getBody()->getContents(), true); $detail = isset($detail['data']) ? $detail['data'] : array(); // Oda listesi — tüm oda tiplerini çek (getRoomList daha kapsamlı) // getHotelDetails sadece o güne müsait odaları döndürür, getRoomList tüm tipleri verir $checkinRooms = date('Y-m-d', strtotime('+30 days')); $checkoutRooms = date('Y-m-d', strtotime('+37 days')); $roomListResp = $client->get(self::API_BASE . '/api/v1/hotels/getRoomList', array( 'query' => array( 'hotel_id' => $hotelId, 'arrival_date' => $checkinRooms, 'departure_date' => $checkoutRooms, 'adults' => 2, 'room_qty' => 1, 'currency_code' => 'EUR', 'languagecode' => 'en-us', ), )); $roomListData = json_decode($roomListResp->getBody()->getContents(), true); $roomListData = isset($roomListData['data']) ? $roomListData['data'] : array(); // getRoomList'ten gelen block[] ve rooms[] ile detail'i zenginleştir if (!empty($roomListData['block'])) { $detail['block'] = $roomListData['block']; } if (!empty($roomListData['rooms'])) { $detail['rooms'] = $roomListData['rooms']; } // Facility listesi $facilResp = $client->get(self::API_BASE . '/api/v1/hotels/getHotelFacilities', array( 'query' => array('hotel_id' => $hotelId), )); $facilData = json_decode($facilResp->getBody()->getContents(), true); $facilData = isset($facilData['data']) ? $facilData['data'] : array(); // Otel fotograflari $photoResp = $client->get(self::API_BASE . '/api/v1/hotels/getHotelPhotos', array( 'query' => array('hotel_id' => $hotelId), )); $photoData = json_decode($photoResp->getBody()->getContents(), true); return array('detail' => $detail, 'facilities' => $facilData, 'photos' => $photoData); } // ───────────────────────────────────────────────────────────────────────────── // ADIM 1: Kullanıcı + Property oluştur // → user/register-user-with-property mantığı // ───────────────────────────────────────────────────────────────────────────── private function createUserAndProperty(array $params, $propertyName, $userId = null) { // Kullanıcı zaten varsa sadece property ekle $userService = app(\App\Core\Service\UserService::class); $propertyService = app(\App\Core\Service\PropertyService::class); $userPropertyMappingService = app(\App\Core\Service\UserPropertyMappingService::class); $productService = app(\App\Core\Service\ProductService::class); $jwtService = app(\App\Core\Service\JwtService::class); $apiAccessTokenService = app(\App\Core\Service\ApiAccessTokenService::class); // Kullanıcı oluştur $userParams = array( 'name' => $params['name'], 'surname' => $params['surname'], 'email' => $params['email'], 'password' => $params['password'], 'status' => 1, 'user_type' => 1, 'property_name' => $propertyName, ); $userCreate = $userService->create($userParams); if ($userCreate['status'] != 'success') { throw new ApiErrorException('Kullanıcı oluşturulamadı: ' . $userCreate['message']); } $user = $userCreate['data']; // UserService::create rastgele şifre üretiyor (Str::random(6)) // Kullanıcının istediği şifreyi DB'ye doğrudan güncelle DB::table('user') ->where('id', $user['id']) ->update(array('password' => \Illuminate\Support\Facades\Hash::make($params['password']))); // Property oluştur $propertyCreate = $propertyService->create(array( 'name' => $propertyName, 'status' => 1, 'created_by' => $user['id'], 'updated_by' => $user['id'], 'created_at' => time(), 'updated_at' => time(), )); if ($propertyCreate['status'] != 'success') { throw new ApiErrorException('Property oluşturulamadı: ' . $propertyCreate['message']); } $property = $propertyCreate['data']; // User-Property mapping $mappingCreate = $userPropertyMappingService->create(array( 'user_id' => $user['id'], 'property_id' => $property['id'], 'status' => 1, 'created_by' => $user['id'], 'updated_by' => $user['id'], 'created_at' => time(), 'updated_at' => time(), )); if ($mappingCreate['status'] != 'success') { throw new ApiErrorException('Kullanıcı-Property eşleştirme hatası: ' . $mappingCreate['message']); } // Default ürünleri ata $productService->setDefaultPropertyProducts(array( 'user_id' => $user['id'], 'property_id' => $property['id'], )); // JWT token al $jwtResult = $jwtService->jwtCreate(array('user_id' => $user['id'])); if ($jwtResult['status'] != 'success') { throw new ApiErrorException('Token oluşturulamadı'); } $jwt = $jwtResult['data']; $apiAccessTokenService->create(array( 'token' => md5(fillOnUndefined($jwt, 'token')), 'expire_date' => fillOnUndefined($jwt, 'exp'), 'user_id' => $user['id'], 'invalidate' => fillOnUndefined($jwt, 'invalidate', 0), )); return array( 'user' => $user, 'property_id' => $property['id'], 'token' => $jwt['token'], ); } // ───────────────────────────────────────────────────────────────────────────── // ADIM 2: Property bilgilerini güncelle // → property/info/update // Booking.com detail → property_info, additional_info, locale // ───────────────────────────────────────────────────────────────────────────── private function updatePropertyInfo($propertyId, $userId, array $detail) { $propertyService = app(\App\Core\Service\PropertyService::class); $name = isset($detail['hotel_name']) ? $detail['hotel_name'] : null; $raw = isset($detail['rawData']) ? $detail['rawData'] : array(); // Stars: rawData.accuratePropertyClass > rawData.propertyClass > rawData.qualityClass $rating = null; foreach (array('accuratePropertyClass', 'propertyClass', 'qualityClass') as $rk) { if (!empty($raw[$rk])) { $rating = (int) $raw[$rk]; break; } } // Check-in/out: rawData.checkin.fromTime / rawData.checkout.untilTime $checkIn = isset($raw['checkin']['fromTime']) ? $raw['checkin']['fromTime'] : null; $checkOut = isset($raw['checkout']['untilTime']) ? $raw['checkout']['untilTime'] : null; // Ülke kodu (max 5 char) $countryCode = null; foreach (array('countryCode', 'countrycode', 'country_code') as $ck) { if (!empty($raw[$ck])) { $countryCode = strtoupper($raw[$ck]); break; } } if (!$countryCode && !empty($detail['countrycode'])) { $countryCode = strtoupper($detail['countrycode']); } // Konuşulan diller: "en-gb" → "en_gb", "tr" → "tr" $spokenRaw = isset($detail['spoken_languages']) ? $detail['spoken_languages'] : array('en'); $langCodes = array(); foreach ($spokenRaw as $lang) { $normalized = str_replace('-', '_', strtolower($lang)); // Sadece 2 veya 5 harfli geçerli kodlar if (preg_match('/^[a-z]{2}(_[a-z]{2})?$/', $normalized)) { $langCodes[] = $normalized; } } if (empty($langCodes)) { $langCodes = array('en'); } $updateParams = array( 'property_id' => $propertyId, 'user_id' => $userId, 'locale' => 'en', 'has_locale_name' => false, 'property_language_spoken' => $langCodes, 'property_info' => array( 'name' => $name, 'property_type_id' => 1, // 1 = Hotel 'chain_id' => 1, // 1 = Independent 'rating' => $rating, 'country' => $countryCode, ), 'additional_info' => array( 'checkin_time' => $checkIn, 'checkout_time' => $checkOut, 'number_of_rooms' => !empty($detail['number_of_rooms']) ? (string)$detail['number_of_rooms'] : null, 'property_timezone' => !empty($detail['timezone']) ? $this->resolveTimezoneId($detail['timezone']) : null, ), ); $result = $propertyService->propertyUpdate($updateParams); if ($result['status'] != 'success') { throw new ApiErrorException('Property güncelleme hatası: ' . $result['message']); } return $result['data']; } // ───────────────────────────────────────────────────────────────────────────── // ADIM 3: İletişim bilgilerini kaydet // → property/contact/update // ───────────────────────────────────────────────────────────────────────────── private function updatePropertyContact($propertyId, $userId, array $detail, $phone = null) { $propertyContactService = app(\App\Core\Service\PropertyContactService::class); // Booking.com top-level alanlar $email = isset($detail['email']) ? $detail['email'] : null; $web = isset($detail['url']) ? $detail['url'] : null; $address = isset($detail['address']) ? $detail['address'] : null; $zip = isset($detail['zip']) ? $detail['zip'] : null; $lat = isset($detail['latitude']) ? $detail['latitude'] : null; $lng = isset($detail['longitude']) ? $detail['longitude'] : null; if (!$phone && isset($detail['phone'])) { $phone = $detail['phone']; } // Validator: email required|email — boşsa placeholder kullan if (!$email) { $email = 'property' . $propertyId . '@extranetwork.com'; } // Validator: phone veya mobile gerekli if (!$phone) { $phone = '0000000000'; } $contactParams = array( 'property_id' => $propertyId, 'user_id' => $userId, 'contact' => array( 'phone' => $phone, 'email' => $email, 'web' => $web, 'address' => $address, 'zip_code' => $zip, 'latitude' => $lat, 'longitude' => $lng, ), ); $result = $propertyContactService->propertyContactUpdateOrCreate($contactParams); if ($result['status'] != 'success') { throw new ApiErrorException('İletişim kaydetme hatası: ' . $result['message']); } return $result['data']; } // ───────────────────────────────────────────────────────────────────────────── // ADIM 4: Facility eşleştir → property_fact_mapping'e ekle // → property/fact-mapping/add // ───────────────────────────────────────────────────────────────────────────── private function mapAndSaveFacilities($propertyId, $userId, array $facilData) { $propertyFactMappingService = app(\App\Core\Service\PropertyFactMappingService::class); // Tüm API isimlerini topla $apiNames = array(); $addUniq = function ($name) use (&$apiNames) { $name = trim($name); if ($name !== '' && !in_array($name, $apiNames)) { $apiNames[] = $name; } }; if (isset($facilData['facilities']) && is_array($facilData['facilities'])) { foreach ($facilData['facilities'] as $fac) { if (!empty($fac['instances'])) { foreach ($fac['instances'] as $inst) { if (!empty($inst['title'])) { $addUniq($inst['title']); } } } } } if (isset($facilData['accommodationHighlights']) && is_array($facilData['accommodationHighlights'])) { foreach ($facilData['accommodationHighlights'] as $h) { if (!empty($h['title'])) { $addUniq($h['title']); } } } if (isset($facilData['highlights']) && is_array($facilData['highlights'])) { foreach ($facilData['highlights'] as $h) { if (!empty($h['instances'])) { foreach ($h['instances'] as $inst) { if (!empty($inst['title'])) { $addUniq($inst['title']); } } } } } // DB exact index $allFacts = PropertyFact::select(array('id', 'name', 'type'))->get(); $exactIndex = array(); foreach ($allFacts as $fact) { $key = mb_strtolower(trim($fact->name)); if (!isset($exactIndex[$key]) || $fact->type == 1) { $exactIndex[$key] = $fact; } } $saved = array(); $skipped = array(); foreach ($apiNames as $apiName) { $lower = mb_strtolower(trim($apiName)); $searchKey = isset($this->aliasMap[$lower]) ? $this->aliasMap[$lower] : $lower; $fact = null; $matchType = 'none'; // 1. Exact / alias if (isset($exactIndex[$searchKey])) { $fact = $exactIndex[$searchKey]; $matchType = ($searchKey !== $lower) ? 'alias' : 'exact'; } // 2. DB LIKE if (!$fact) { $like = PropertyFact::where('type', 1) ->whereRaw('LOWER(name) LIKE ?', array('%' . $lower . '%')) ->orderByRaw('LENGTH(name) ASC') ->first(); if ($like) { $fact = $like; $matchType = 'like'; } } // 3. Ters LIKE if (!$fact) { $reverse = PropertyFact::where('type', 1) ->whereRaw('LOWER(?) LIKE CONCAT(\'%\', LOWER(name), \'%\')', array($lower)) ->whereRaw('LENGTH(name) >= 5') ->orderByRaw('LENGTH(name) DESC') ->first(); if ($reverse) { $fact = $reverse; $matchType = 'reverse_like'; } } if ($fact) { // Kaydet $mappingResult = $propertyFactMappingService->addPropertyFactMapping(array( 'property_id' => $propertyId, 'user_id' => $userId, 'data' => array( array('id' => $fact->id), ), )); $saved[] = array( 'api_name' => $apiName, 'fact_id' => $fact->id, 'fact_name' => $fact->name, 'match_type' => $matchType, 'saved' => ($mappingResult['status'] == 'success'), ); } else { $skipped[] = $apiName; } } return array( 'api_total' => count($apiNames), 'saved_count' => count($saved), 'skipped_count' => count($skipped), 'saved' => $saved, 'skipped' => $skipped, ); } // ───────────────────────────────────────────────────────────────────────────── // ADIM 5: Booking.com'daki oda bilgilerinden oda ekle // → property/room/add-room-bed // ───────────────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────────────── // Booking.com bed_type ID → sistemdeki property_room_bed_type.id // // Booking.com standart IDs (DataCrawler API): // 1=Twin 2=Double 3=King 4=Queen 5=Single 6=Sofa 7=Bunk 8=Futon // Sistemdeki property_room_bed_type: // 1=Bunk Bed 2=Double Bed 3=French Bed 4=Futon 5=King Bed // 6=Queen Bed 7=Single Bed 8=Sofa Bed 9=Twin Bed 10=Foldable Bed // 11=Sleeping Bag 12=Triple Bunk Bed 13=Extra bed 14=Cot // ───────────────────────────────────────────────────────────────────────────── private $bedTypeMap = array( 1 => 9, // Twin Bed(s) → Twin Bed 2 => 2, // Double Bed → Double Bed 3 => 5, // King Bed → King Bed 4 => 6, // Queen Bed → Queen Bed 5 => 7, // Single Bed → Single Bed 6 => 8, // Sofa Bed → Sofa Bed 7 => 1, // Bunk Bed → Bunk Bed 8 => 4, // Futon → Futon 9 => 3, // French Bed → French Bed 10 => 10, // Foldable Bed → Foldable Bed 37 => 14, // Cot/Cribs → Cot 40 => 13, // Extra Bed → Extra bed ); // ───────────────────────────────────────────────────────────────────────────── // Booking.com view adı (highlight.translated_name) → property_room_view_type.id // ───────────────────────────────────────────────────────────────────────────── private $viewTypeMap = array( 'park view' => 1, 'pool view' => 2, 'river view' => 3, 'sea view' => 4, 'ocean view' => 22, 'street view' => 5, 'valley view' => 6, 'monument view' => 7, 'bay view' => 8, 'beach view' => 9, 'city view' => 10, 'countryside view' => 11, 'courtyard view' => 12, 'garden view' => 13, 'gulf view' => 14, 'harbor view' => 15, 'lagoon view' => 16, 'lake view' => 17, 'marina view' => 18, 'mountain view' => 19, 'nature view' => 20, 'no window' => 21, 'land view' => 23, 'forest view' => 24, 'island view' => 25, 'golf course view' => 26, 'landmark view' => 30, 'sea view' => 4, ); // ───────────────────────────────────────────────────────────────────────────── // Booking.com oda adı keyword → property_room_type.id // ───────────────────────────────────────────────────────────────────────────── private $roomTypeKeywords = array( 'standard' => 24, // Standard Room 'standart' => 24, 'single' => 42, // Single Room 'double' => 43, // Double Room 'twin' => 43, // Double Room (çoğu twin=double planı) 'triple' => 44, // Triple Room 'quad' => 45, // Quadruple Room 'family' => 18, // Family Room 'suite' => 25, // Suite 'junior suite' => 20, 'grand suite' => 19, 'king suite' => 21, 'deluxe' => 16, // Deluxe 'superior' => 26, // Superior 'executive' => 48, // Executive Room 'villa' => 27, // Villa 'bungalow' => 14, // Bungalow 'loft' => 31, // Loft Room 'studio' => 29, // Studio Room 'apart' => 12, // Apartment 'apartment' => 12, 'penthouse' => 22, // Pent House 'accessible' => 11, // Accessible Room 'honeymoon' => 34, // Honeymoon Room 'economy' => 17, // Economy 'premium' => 23, // Premium Room 'club' => 15, // Club Room 'swim up' => 46, // Swim Up Room 'swim-up' => 46, 'corner' => 49, // Corner Room 'large' => 47, // Standard Large Room ); // ───────────────────────────────────────────────────────────────────────────── // ADIM 5: Booking.com'daki oda bilgilerinden oda ekle // // Kaynak 1: detail['block'][] → room_name, max_occupancy, nr_adults, // room_surface_in_m2, number_of_bathrooms // Kaynak 2: detail['rooms'][id] → bed_configurations, highlights (view) // ───────────────────────────────────────────────────────────────────────────── private function insertRooms($propertyId, $userId, array $detail) { $propertyRoomService = app(\App\Core\Service\PropertyRoomService::class); // ── Kaynak 1: block[] → her room_id için tek kayıt ────────────────────── $blockByRoomId = array(); $blockRaw = isset($detail['block']) ? $detail['block'] : array(); foreach ($blockRaw as $b) { $rid = isset($b['room_id']) ? $b['room_id'] : null; if ($rid && !isset($blockByRoomId[$rid])) { $blockByRoomId[$rid] = $b; } } // ── Kaynak 2: rooms[room_id] → bed configs + view highlights ──────────── $roomsRaw = isset($detail['rooms']) ? $detail['rooms'] : array(); // rooms[] boşsa sadece block verisini kullan if (empty($roomsRaw) && empty($blockByRoomId)) { // Hiç oda yoksa tek generic oda $blockByRoomId = array( 0 => array( 'room_name' => 'Standard Room', 'max_occupancy' => 2, 'nr_adults' => 2, 'room_surface_in_m2' => null, 'number_of_bathrooms' => null, ), ); } // rooms[] boşsa block ID'lerini rooms olarak kullan if (empty($roomsRaw)) { foreach ($blockByRoomId as $rid => $b) { $roomsRaw[$rid] = array(); } } if (empty($blockByRoomId)) { foreach (array_keys($roomsRaw) as $rid) { $blockByRoomId[$rid] = array(); } } // Distinct room_id birleşimi $allRoomIds = array_unique(array_merge(array_keys($blockByRoomId), array_keys($roomsRaw))); $inserted = array(); foreach ($allRoomIds as $roomId) { $block = isset($blockByRoomId[$roomId]) ? $blockByRoomId[$roomId] : array(); $room = isset($roomsRaw[$roomId]) ? $roomsRaw[$roomId] : array(); // ── Oda adı ────────────────────────────────────────────────────────── $roomName = trim(isset($block['room_name']) ? $block['room_name'] : 'Standard Room'); // ── room_type_id: oda adındaki anahtar kelimeye göre ───────────────── $roomTypeId = $this->guessRoomTypeId($roomName); // ── Kapasite ───────────────────────────────────────────────────────── $maxOccupancy = isset($block['max_occupancy']) ? (int)$block['max_occupancy'] : 2; $maxAdult = isset($block['nr_adults']) ? (int)$block['nr_adults'] : $maxOccupancy; $maxChild = isset($block['nr_children']) ? (int)$block['nr_children'] : 0; // ── Oda boyutu ─────────────────────────────────────────────────────── // Validator required|max:30 → null yerine 0 gönder $roomSizeRaw = isset($block['room_surface_in_m2']) ? (int)$block['room_surface_in_m2'] : 0; $roomSize = $roomSizeRaw > 0 ? $roomSizeRaw : 0; $roomSizeType = $roomSizeRaw > 0 ? 1 : 0; // 1 = m² // ── Oda adedi (kaç adet bu tipten var) ────────────────────────────── $roomTypeCount = isset($block['room_count']) && $block['room_count'] > 0 ? (int)$block['room_count'] : 1; // ── Banyo sayısı ───────────────────────────────────────────────────── $bathroomCount = isset($block['number_of_bathrooms']) && $block['number_of_bathrooms'] > 0 ? (int)$block['number_of_bathrooms'] : null; // ── Açıklama ───────────────────────────────────────────────────────── $description = isset($room['description']) ? $room['description'] : null; // ── Yatak konfigürasyonları → room_bed_group ───────────────────────── // Sistem formatı: [[{bed_type_id:9}, {bed_type_id:9}], [{bed_type_id:2}]] // Her conf = bir grup, içindeki bed_types[].count kadar tekrar $bedConfigs = isset($room['bed_configurations']) ? $room['bed_configurations'] : array(); $roomBedGroup = array(); foreach ($bedConfigs as $conf) { $groupBeds = array(); $bedTypes = isset($conf['bed_types']) ? $conf['bed_types'] : array(); foreach ($bedTypes as $bt) { $bookingBedId = isset($bt['bed_type']) ? (int)$bt['bed_type'] : 0; $ourBedTypeId = isset($this->bedTypeMap[$bookingBedId]) ? $this->bedTypeMap[$bookingBedId] : null; $bedCount = isset($bt['count']) ? (int)$bt['count'] : 1; if ($ourBedTypeId) { for ($i = 0; $i < $bedCount; $i++) { $groupBeds[] = array('bed_type_id' => $ourBedTypeId); } } } if (!empty($groupBeds)) { $roomBedGroup[] = $groupBeds; } } // ── View tipleri → room_view_type ──────────────────────────────────── // Booking.com highlights'daki "translated_name" → viewTypeMap eşleşmesi // Eşleşmezse icon'a bak: "city"→10, "sea"→4, "pool"→2, "mountain"→19 $iconViewMap = array( 'city' => 10, 'sea' => 4, 'ocean' => 22, 'pool' => 2, 'mountain' => 19, 'garden' => 13, 'park' => 1, 'river' => 3, 'lake' => 17, 'beach' => 9, 'forest' => 24, 'harbor' => 15, 'marina' => 18, 'eye' => 4, // Booking.com Sea view ikonu "eye" olarak geliyor 'landmark' => 30, // Landmark view ); $highlights = isset($room['highlights']) ? $room['highlights'] : array(); $roomViewType = array(); foreach ($highlights as $h) { $hName = mb_strtolower(trim(isset($h['translated_name']) ? $h['translated_name'] : '')); $hIcon = mb_strtolower(trim(isset($h['icon']) ? $h['icon'] : '')); $viewId = null; // 1) Tam eşleşme: "city view" if (isset($this->viewTypeMap[$hName])) { $viewId = $this->viewTypeMap[$hName]; } // 2) Kısmi eşleşme: highlight adında keyword geçiyor if (!$viewId) { foreach ($this->viewTypeMap as $keyword => $vid) { $base = str_replace(' view', '', $keyword); if (strpos($hName, $base) !== false) { $viewId = $vid; break; } } } // 3) Icon eşleşmesi: "city" ikonu → 10 if (!$viewId && isset($iconViewMap[$hIcon])) { $viewId = $iconViewMap[$hIcon]; } if ($viewId && !in_array($viewId, $roomViewType)) { $roomViewType[] = $viewId; } } // ── Service'e gönder ───────────────────────────────────────────────── $roomParams = array( 'property_id' => $propertyId, 'user_id' => $userId, 'locale' => 'en', 'name' => $roomName, 'room_type_id' => $roomTypeId, 'max_occupancy' => $maxOccupancy, 'max_adult' => $maxAdult, 'max_child' => $maxChild, 'occupancy_lock' => 0, 'exclude_occupancy' => 0, 'room_size' => $roomSize, 'room_size_type' => $roomSizeType, 'room_type_count' => $roomTypeCount, 'bathroom_count' => $bathroomCount, 'toilet_count' => null, 'lounge_count' => null, 'room_count' => null, 'max_child_number' => null, 'description' => array( array('language_code' => 'en', 'description' => $description) ), 'room_bed_group' => $roomBedGroup, 'room_view_type' => $roomViewType, 'is_connected_room' => null, 'is_connected_room_price' => null, 'is_connected_room_availability' => null, 'connected_rooms' => array(), ); $result = $propertyRoomService->addPropertyRoomAndBed($roomParams); $isSaved = ($result['status'] == 'success' || $result['status'] === true); $facSummary = array(); $photoSummary = array(); if ($isSaved) { // addPropertyRoomAndBed data döndürmüyor — son eklenen room_id'yi çek $newRoom = DB::table('property_room') ->where('property_id', $propertyId) ->where('name', $roomName) ->orderBy('id', 'desc') ->first(); $newRoomId = $newRoom ? $newRoom->id : null; if ($newRoomId) { // Oda olanakları → property_room_fact_mapping if (!empty($room['facilities'])) { $facSummary = $this->mapAndSaveRoomFacilities( $newRoomId, $propertyId, $userId, $room['facilities'] ); } // Oda fotoğrafları → property_photo + property_room_photo_mapping if (!empty($room['photos'])) { $photoSummary = $this->mapAndSaveRoomPhotos( $newRoomId, $propertyId, $userId, $room['photos'], isset($detail['hotel_id']) ? $detail['hotel_id'] : 0 ); } } } $inserted[] = array( 'room_id' => $roomId, 'room_name' => $roomName, 'room_type_id' => $roomTypeId, 'beds' => array_map(function ($g) { return $g; }, $roomBedGroup), 'views' => $roomViewType, 'room_facts' => $facSummary, 'room_photos' => $photoSummary, 'saved' => $isSaved, 'message' => !$isSaved ? $result['message'] : null, ); } return array( 'room_count' => count($inserted), 'rooms' => $inserted, ); } // ───────────────────────────────────────────────────────────────────────────── // Oda fotoğraflarını indir, property_photo'ya kaydet, room_photo_mapping'e bağla // Booking.com: rooms[id]['photos'] → [{url_original, url_max750, ...}] // ───────────────────────────────────────────────────────────────────────────── private function mapAndSaveRoomPhotos($roomId, $propertyId, $userId, array $photos, $hotelId) { if (empty($photos)) { return array('saved_count' => 0, 'skipped_count' => 0); } $uploadRoot = rtrim(Config::get('app.fileSystemDriver'), '/'); $urlPath = '/property-photos/' . $propertyId . '/'; $filePath = $uploadRoot . $urlPath; if (!File::exists($filePath)) { File::makeDirectory($filePath, 0777, true); } $saved = array(); $skipped = array(); $index = 0; foreach ($photos as $photo) { // URL: url_original > url_max750 > url_max1280 > url_max300 $url = ''; foreach (array('url_original', 'url_max750', 'url_max1280', 'url_max300') as $key) { if (!empty($photo[$key])) { $url = $photo[$key]; break; } } if ($url === '') { $skipped[] = 'empty_url'; continue; } try { $photoId = isset($photo['photo_id']) ? $photo['photo_id'] : ''; $fileName = time() . '_bk' . $hotelId . '_r' . $roomId . '_' . $index; $fileExt = 'jpg'; $tempPath = sys_get_temp_dir() . '/' . $fileName . '_orig.jpg'; $this->downloadRawImage($url, $tempPath); $image = Image::make($tempPath); $imageWidth = $image->width(); $imageHeight = $image->height(); $fileSize = (int) ceil(filesize($tempPath) / 1024); $quality = 80; // Orijinal — 2000px üzeriyse küçült if ($imageHeight > 2000 || $imageWidth > 2000) { if ($imageHeight > $imageWidth) { $r = $imageHeight / 2000; $image->fit((int)($imageWidth / $r), 2000); } else { $r = $imageWidth / 2000; $image->fit(2000, (int)($imageHeight / $r)); } } $image->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '.' . $fileExt); // _medium (max 1600x768) $mediumImage = Image::make($tempPath); $ratio = $imageHeight > 0 ? ($imageHeight / 768) : 1; $resizeWidth = (int)($imageWidth / max($ratio, 1)); if ($resizeWidth > 1599) { $mediumImage->fit(1600, 768); } else { $mediumImage->fit(max($resizeWidth, 1), 768); } $mediumImage->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '_medium.' . $fileExt); // _thumbnail (300x300) Image::make($tempPath) ->fit(300, 300) ->encode($fileExt, $quality) ->orientate() ->save($filePath . $fileName . '_thumbnail.' . $fileExt); if (File::exists($tempPath)) { File::delete($tempPath); } $isCompatible = ($imageWidth >= 1150 && $imageHeight >= 600) ? 1 : 0; $photoRecord = PropertyPhoto::create(array( 'property_id' => $propertyId, 'property_photo_category_id' => 1, 'photo_path' => $urlPath, 'photo_name' => $fileName, 'file_size' => $fileSize, 'file_ext' => $fileExt, 'photo_resolution' => $imageWidth . 'x' . $imageHeight, 'is_default' => 0, 'is_compatible_with_myweb_slider' => $isCompatible, 'photo_order' => 0, 'photo_rank' => 0, 'is_temp' => 1, 'status' => 1, 'created_by' => 1, 'updated_by' => 1, 'created_at' => time(), 'updated_at' => time(), )); // property_room_photo_mapping'e bağla $alreadyLinked = DB::table('property_room_photo_mapping') ->where('room_id', $roomId) ->where('photo_id', $photoRecord->id) ->exists(); if (!$alreadyLinked) { DB::table('property_room_photo_mapping')->insert(array( 'property_id' => $propertyId, 'room_id' => $roomId, 'photo_id' => $photoRecord->id, 'created_by' => $userId, 'updated_by' => $userId, 'created_at' => time(), 'updated_at' => time(), )); } $saved[] = array( 'photo_id' => $photoRecord->id, 'file_name' => $fileName . '.' . $fileExt, 'resolution' => $imageWidth . 'x' . $imageHeight, 'source_url' => $url, ); } catch (Exception $e) { Log::error('propertyInsert.roomPhoto [room=' . $roomId . ', i=' . $index . ']: ' . $e->getMessage()); $skipped[] = $url; } $index++; } return array( 'saved_count' => count($saved), 'skipped_count' => count($skipped), 'photos' => $saved, ); } // ───────────────────────────────────────────────────────────────────────────── // Oda olanaklarını (room facilities) property_room_fact_mapping'e kaydet // Booking.com: rooms[id]['facilities'] → [{id, name, alt_facilitytype_id, ...}] // View tiplerini atla (viewTypeMap ile zaten ekleniyor) // ───────────────────────────────────────────────────────────────────────────── private function mapAndSaveRoomFacilities($roomId, $propertyId, $userId, array $facilities) { // View kategorisi facilitytype_id'leri (9=View, 14=View alt) — zaten view mapping'de ekleniyor $skipFacilityTypeIds = array(9, 14); // Sistemdeki tüm fact'leri küçük harfli isimle index'e al (type=1: property/room fact) $allFacts = PropertyFact::select(array('id', 'name', 'type'))->where('type', 1)->get(); $exactIndex = array(); foreach ($allFacts as $fact) { $key = mb_strtolower(trim($fact->name)); // Aynı isimde birden fazla fact varsa daha küçük id'liyi (ilk ekleneni) tut if (!isset($exactIndex[$key])) { $exactIndex[$key] = $fact; } } // roomAliasMap (oda özel) + aliasMap (genel) birleştir, room öncelikli $combinedAlias = array_merge($this->aliasMap, $this->roomAliasMap); $saved = array(); $skipped = array(); foreach ($facilities as $fac) { // View tiplerini atla $faciltypeId = isset($fac['facilitytype_id']) ? (int)$fac['facilitytype_id'] : 0; $altFaciltypeId = isset($fac['alt_facilitytype_id']) ? (int)$fac['alt_facilitytype_id'] : 0; if (in_array($faciltypeId, $skipFacilityTypeIds) || in_array($altFaciltypeId, $skipFacilityTypeIds)) { continue; } $name = trim(isset($fac['name']) ? $fac['name'] : ''); if ($name === '') { continue; } $lower = mb_strtolower($name); // Alias map'te varsa o karşılığı kullan, yoksa direkt ismi kullan $searchKey = isset($combinedAlias[$lower]) ? mb_strtolower($combinedAlias[$lower]) : $lower; $fact = null; // Sadece birebir eşleşme — tahmin yok, LIKE yok if (isset($exactIndex[$searchKey])) { $fact = $exactIndex[$searchKey]; } if ($fact) { // Yalnızca mevcut property_room_fact_mapping tablosuna yaz, yeni kayıt türü oluşturma $exists = DB::table('property_room_fact_mapping') ->where('property_id', $propertyId) ->where('room_id', $roomId) ->where('fact_id', $fact->id) ->exists(); if (!$exists) { DB::table('property_room_fact_mapping')->insert(array( 'property_id' => $propertyId, 'room_id' => $roomId, 'fact_id' => $fact->id, 'is_feature' => 0, 'created_by' => $userId, 'updated_by' => $userId, 'created_at' => time(), 'updated_at' => time(), )); } $saved[] = array( 'booking_name' => $name, 'fact_id' => $fact->id, 'fact_name' => $fact->name, 'via_alias' => ($searchKey !== $lower), ); } else { // Eşleşme yok — sisteme kesinlikle bir şey yazma, sadece logla Log::warning('propertyInsert.roomFacility.noMatch', array( 'room_id' => $roomId, 'property_id' => $propertyId, 'booking_name' => $name, 'search_key' => $searchKey, 'hint' => 'roomAliasMap veya aliasMap\'e eklenebilir', )); $skipped[] = $name; } } return array( 'saved_count' => count($saved), 'skipped_count' => count($skipped), 'saved' => $saved, 'skipped' => $skipped, ); } // ───────────────────────────────────────────────────────────────────────────── // Oda adındaki anahtar kelimelere göre room_type_id tahmin et // ───────────────────────────────────────────────────────────────────────────── private function guessRoomTypeId($roomName) { $lower = mb_strtolower($roomName); // Önce çift kelimeli eşleşmeleri dene (daha spesifik) foreach ($this->roomTypeKeywords as $keyword => $typeId) { if (strpos($keyword, ' ') !== false && strpos($lower, $keyword) !== false) { return $typeId; } } // Sonra tek kelimeli foreach ($this->roomTypeKeywords as $keyword => $typeId) { if (strpos($keyword, ' ') === false && strpos($lower, $keyword) !== false) { return $typeId; } } return 24; // Default: Standard Room } // ───────────────────────────────────────────────────────────────────────────── // ANA METOD: POST /bulut/property-insert // ───────────────────────────────────────────────────────────────────────────── public function insert(Request $request) { $hotelId = $request->input('hotel_id', ''); $email = $request->input('email', ''); $password = $request->input('password', ''); $name = $request->input('name', ''); $surname = $request->input('surname', ''); $propertyName = $request->input('property_name', ''); $phone = $request->input('phone', null); $dryRun = (bool) $request->input('dry_run', false); $checkin = $request->input('checkin', date('Y-m-d', strtotime('+30 days'))); $checkout = $request->input('checkout', date('Y-m-d', strtotime('+31 days'))); // Zorunlu alan validasyonu $required = array('hotel_id', 'email', 'password', 'name', 'surname', 'property_name'); $missing = array(); foreach ($required as $field) { if (!$request->input($field)) { $missing[] = $field; } } if (count($missing)) { return response()->json(array( 'status' => false, 'message' => 'Eksik alanlar: ' . implode(', ', $missing), ), 422); } $report = array( 'hotel_id' => $hotelId, 'dry_run' => $dryRun, 'steps' => array(), ); try { DB::beginTransaction(); // ── ADIM 0: Booking.com'dan veri çek ───────────────────────────────── $report['steps']['0_booking_fetch'] = array('status' => 'pending'); $bookingData = $this->fetchBookingData($hotelId, $checkin, $checkout); $detail = $bookingData['detail']; $facilData = $bookingData['facilities']; $photoData = $bookingData['photos']; $report['steps']['0_booking_fetch'] = array( 'status' => 'success', 'hotel_name' => isset($detail['hotel_name']) ? $detail['hotel_name'] : null, 'facility_count' => $this->countFacilities($facilData), 'photo_count' => count($this->extractPhotoUrls($photoData)), ); // Eğer property_name boş geldiyse Booking.com adını kullan if (!$propertyName && !empty($detail['hotel_name'])) { $propertyName = $detail['hotel_name']; } if ($dryRun) { DB::rollBack(); $report['dry_run_note'] = 'dry_run=true → DB\'ye hiçbir şey yazılmadı. Aşağıdaki veriler oluşturulacaktı.'; $report['steps']['1_user_property'] = array('status' => 'skipped (dry_run)', 'email' => $email, 'property_name' => $propertyName); $report['steps']['2_property_info'] = array('status' => 'skipped (dry_run)', 'data' => $this->previewPropertyInfo($detail)); $report['steps']['3_contact'] = array('status' => 'skipped (dry_run)', 'phone' => $phone ?: (isset($detail['phone']) ? $detail['phone'] : null), 'email' => isset($detail['email']) ? $detail['email'] : null); $report['steps']['4_fact_mapping'] = array('status' => 'skipped (dry_run)', 'facility_count' => $this->countFacilities($facilData)); $report['steps']['5_rooms'] = array('status' => 'skipped (dry_run)'); $report['steps']['6_photos'] = array('status' => 'skipped (dry_run)', 'photo_count' => count($this->extractPhotoUrls($photoData))); return response()->json(array('status' => true, 'message' => 'Dry run tamamlandı', 'report' => $report)); } // ── ADIM 1: Kullanıcı + Property oluştur ──────────────────────────── $report['steps']['1_user_property'] = array('status' => 'pending'); $userPropertyResult = $this->createUserAndProperty( array('name' => $name, 'surname' => $surname, 'email' => $email, 'password' => $password), $propertyName ); $createdUserId = $userPropertyResult['user']['id']; $createdPropertyId = $userPropertyResult['property_id']; $report['steps']['1_user_property'] = array( 'status' => 'success', 'user_id' => $createdUserId, 'property_id' => $createdPropertyId, 'token' => $userPropertyResult['token'], ); // ── ADIM 2: Property info güncelle ────────────────────────────────── $report['steps']['2_property_info'] = array('status' => 'pending'); $this->updatePropertyInfo($createdPropertyId, $createdUserId, $detail); $raw2 = isset($detail['rawData']) ? $detail['rawData'] : array(); $report['steps']['2_property_info'] = array( 'status' => 'success', 'hotel_name' => isset($detail['hotel_name']) ? $detail['hotel_name'] : null, 'rating' => isset($raw2['accuratePropertyClass']) ? (int)$raw2['accuratePropertyClass'] : null, 'country' => isset($detail['countrycode']) ? strtoupper($detail['countrycode']) : null, 'checkin' => isset($raw2['checkin']['fromTime']) ? $raw2['checkin']['fromTime'] : null, 'checkout' => isset($raw2['checkout']['untilTime']) ? $raw2['checkout']['untilTime'] : null, 'languages' => isset($detail['spoken_languages']) ? $detail['spoken_languages'] : array(), ); // ── ADIM 3: İletişim bilgilerini kaydet ────────────────────────────── $report['steps']['3_contact'] = array('status' => 'pending'); $this->updatePropertyContact($createdPropertyId, $createdUserId, $detail, $phone); $report['steps']['3_contact'] = array( 'status' => 'success', 'phone' => $phone ?: (isset($detail['phone']) ? $detail['phone'] : null), 'email' => isset($detail['email']) ? $detail['email'] : null, ); // ── ADIM 4: Facility → Fact mapping ───────────────────────────────── $report['steps']['4_fact_mapping'] = array('status' => 'pending'); $facilityResult = $this->mapAndSaveFacilities($createdPropertyId, $createdUserId, $facilData); $report['steps']['4_fact_mapping'] = array_merge(array('status' => 'success'), $facilityResult); // ── ADIM 5: Oda ekle ───────────────────────────────────────────────── $report['steps']['5_rooms'] = array('status' => 'pending'); $roomResult = $this->insertRooms($createdPropertyId, $createdUserId, $detail); $report['steps']['5_rooms'] = array_merge(array('status' => 'success'), $roomResult); // ── ADIM 6: Görselleri indir ve kaydet ─────────────────────────────── $report['steps']['6_photos'] = array('status' => 'pending'); $photoLimit = $request->input('photo_limit', 20); $photoResult = $this->processAndSavePhotos($photoData, $hotelId, $createdPropertyId, $photoLimit); $report['steps']['6_photos'] = array_merge(array('status' => 'success'), $photoResult); DB::commit(); return response()->json(array( 'status' => true, 'message' => 'Property başarıyla oluşturuldu', 'property_id' => $createdPropertyId, 'user_id' => $createdUserId, 'report' => $report, )); } catch (ApiErrorException $e) { DB::rollBack(); Log::error('propertyInsert.ApiError: ' . $e->getMessage()); return response()->json(array( 'status' => false, 'message' => implode(', ', $e->getMessageArr()), 'report' => $report, ), 400); } catch (Exception $e) { DB::rollBack(); Log::error('propertyInsert.Error: ' . $e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage()); return response()->json(array( 'status' => false, 'message' => $e->getMessage(), 'report' => $report, ), 500); } } // ───────────────────────────────────────────────────────────────────────────── // ADIM 6: Fotograflari indir, isle ve kaydet // ───────────────────────────────────────────────────────────────────────────── private function processAndSavePhotos(array $responseData, $hotelId, $propertyId, $limit = 20) { $photoUrls = $this->extractPhotoUrls($responseData); if (empty($photoUrls)) { return array( 'saved_count' => 0, 'total_in_api' => 0, 'message' => 'API yanitinda fotograf URL bulunamadi', ); } $uploadRoot = rtrim(Config::get('app.fileSystemDriver'), '/'); $urlPath = '/property-photos/' . $propertyId . '/'; $filePath = $uploadRoot . $urlPath; if (!File::exists($filePath)) { File::makeDirectory($filePath, 0777, true); } $hasDefault = PropertyPhoto::where('property_id', $propertyId) ->where('status', 1) ->where('is_default', 1) ->exists(); $saved = array(); $isFirstPhoto = true; $total = min(count($photoUrls), $limit); for ($i = 0; $i < $total; $i++) { $photoUrl = $photoUrls[$i]; try { $fileName = time() . '_bk' . $hotelId . '_' . $i; $fileExt = 'jpg'; $tempPath = sys_get_temp_dir() . '/' . $fileName . '_orig.jpg'; $this->downloadRawImage($photoUrl, $tempPath); $image = Image::make($tempPath); $imageWidth = $image->width(); $imageHeight = $image->height(); $fileSize = (int) ceil(filesize($tempPath) / 1024); $quality = 80; // Orijinal — 2000px uzeriyse kucult if ($imageHeight > 2000 || $imageWidth > 2000) { if ($imageHeight > $imageWidth) { $r = $imageHeight / 2000; $image->fit((int)($imageWidth / $r), 2000); } else { $r = $imageWidth / 2000; $image->fit(2000, (int)($imageHeight / $r)); } } $image->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '.' . $fileExt); // _medium (max 1600x768) $mediumImage = Image::make($tempPath); $ratio = $imageHeight > 0 ? ($imageHeight / 768) : 1; $resizeWidth = (int)($imageWidth / max($ratio, 1)); if ($resizeWidth > 1599) { $mediumImage->fit(1600, 768); } else { $mediumImage->fit(max($resizeWidth, 1), 768); } $mediumImage->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '_medium.' . $fileExt); // _thumbnail (300x300) Image::make($tempPath) ->fit(300, 300) ->encode($fileExt, $quality) ->orientate() ->save($filePath . $fileName . '_thumbnail.' . $fileExt); if (File::exists($tempPath)) { File::delete($tempPath); } $isCompatible = ($imageWidth >= 1150 && $imageHeight >= 600) ? 1 : 0; $isDefault = (!$hasDefault && $isFirstPhoto) ? 1 : 0; $isFirstPhoto = false; $photoRecord = PropertyPhoto::create(array( 'property_id' => $propertyId, 'property_photo_category_id' => 1, 'photo_path' => $urlPath, 'photo_name' => $fileName, 'file_size' => $fileSize, 'file_ext' => $fileExt, 'photo_resolution' => $imageWidth . 'x' . $imageHeight, 'is_default' => $isDefault, 'is_compatible_with_myweb_slider' => $isCompatible, 'photo_order' => 0, 'photo_rank' => 0, 'is_temp' => 1, 'status' => 1, 'created_by' => 1, 'updated_by' => 1, 'created_at' => time(), 'updated_at' => time(), )); $saved[] = array( 'db_id' => $photoRecord->id, 'file_name' => $fileName . '.' . $fileExt, 'resolution' => $imageWidth . 'x' . $imageHeight, 'source_url' => $photoUrl, ); } catch (Exception $e) { Log::error('propertyInsert.photo [' . $i . ']: ' . $e->getMessage() . ' | ' . $photoUrl); $saved[] = array('error' => $e->getMessage(), 'source_url' => $photoUrl); } } $okCount = count(array_filter($saved, function ($s) { return !isset($s['error']); })); return array( 'saved_count' => $okCount, 'total_in_api' => count($photoUrls), 'limit_applied' => $total, 'photos' => $saved, ); } private function extractPhotoUrls(array $data) { $urls = array(); // Format 1: data[] dizisi if (isset($data['data']) && is_array($data['data']) && !empty($data['data'])) { foreach ($data['data'] as $item) { if (!is_array($item)) { continue; } if (!empty($item['url_original'])) { $urls[] = $item['url_original']; } elseif (!empty($item['url_max'])) { $urls[] = $item['url_max']; } elseif (!empty($item['url'])) { $urls[] = $item['url']; } elseif (!empty($item['photo']['url_original'])) { $urls[] = $item['photo']['url_original']; } } } // Format 2: data.photos[] if (empty($urls) && isset($data['data']['photos']) && is_array($data['data']['photos'])) { foreach ($data['data']['photos'] as $item) { if (!empty($item['url_original'])) { $urls[] = $item['url_original']; } elseif (!empty($item['url'])) { $urls[] = $item['url']; } } } // Format 3: photos[] root'ta if (empty($urls) && isset($data['photos']) && is_array($data['photos'])) { foreach ($data['photos'] as $item) { if (!empty($item['url_original'])) { $urls[] = $item['url_original']; } elseif (!empty($item['url'])) { $urls[] = $item['url']; } } } return $urls; } private function downloadRawImage($url, $savePath) { $client = new Client(array( 'timeout' => 30, 'verify' => false, 'headers' => array('User-Agent' => 'Mozilla/5.0'), )); $response = $client->get($url, array('sink' => $savePath)); if ($response->getStatusCode() !== 200) { throw new Exception('Image download failed HTTP ' . $response->getStatusCode() . ' | ' . $url); } } // ───────────────────────────────────────────────────────────────────────────── // Yardımcı: timezone string → general_timezone.id // ───────────────────────────────────────────────────────────────────────────── private function resolveTimezoneId($timezoneLocation) { $row = DB::table('general_timezone') ->where('location', $timezoneLocation) ->first(); return $row ? (string)$row->id : null; } // ───────────────────────────────────────────────────────────────────────────── // Yardımcı: dry_run için property_info önizleme // ───────────────────────────────────────────────────────────────────────────── private function previewPropertyInfo(array $detail) { $raw = isset($detail['rawData']) ? $detail['rawData'] : array(); $rating = null; foreach (array('accuratePropertyClass', 'propertyClass', 'qualityClass') as $rk) { if (!empty($raw[$rk])) { $rating = (int)$raw[$rk]; break; } } return array( 'name' => isset($detail['hotel_name']) ? $detail['hotel_name'] : null, 'rating' => $rating, 'country' => isset($detail['countrycode']) ? strtoupper($detail['countrycode']) : null, 'address' => isset($detail['address']) ? $detail['address'] : null, 'city' => isset($detail['city_name_en']) ? $detail['city_name_en'] : null, 'district' => isset($detail['district']) ? $detail['district'] : null, 'zip' => isset($detail['zip']) ? $detail['zip'] : null, 'latitude' => isset($detail['latitude']) ? $detail['latitude'] : null, 'longitude' => isset($detail['longitude']) ? $detail['longitude'] : null, 'timezone' => isset($detail['timezone']) ? $detail['timezone'] : null, 'checkin' => isset($raw['checkin']['fromTime']) ? $raw['checkin']['fromTime'] : null, 'checkout' => isset($raw['checkout']['untilTime']) ? $raw['checkout']['untilTime'] : null, 'languages' => isset($detail['spoken_languages']) ? $detail['spoken_languages'] : array(), 'review_score' => isset($raw['reviewScore']) ? $raw['reviewScore'] : null, ); } // ───────────────────────────────────────────────────────────────────────────── // Yardımcı: facility sayısını döndür // ───────────────────────────────────────────────────────────────────────────── private function countFacilities(array $facilData) { $count = 0; if (isset($facilData['facilities'])) { foreach ($facilData['facilities'] as $fac) { $count += count(isset($fac['instances']) ? $fac['instances'] : array()); } } return $count; } // ───────────────────────────────────────────────────────────────────────────── // GET /bulut/property-check?email=... // Verilen email'e ait kullanıcı ve property bilgilerini döndür // ───────────────────────────────────────────────────────────────────────────── public function check(Request $request) { $email = trim($request->input('email', '')); if (!$email) { return response()->json(array('status' => false, 'message' => 'email parametresi gerekli'), 422); } $user = DB::table('user')->where('email', $email)->first(); if (!$user) { return response()->json(array( 'status' => false, 'message' => 'Kullanıcı bulunamadı', 'email' => $email, )); } // Kullanıcıya bağlı property'ler $mappings = DB::table('user_property_mapping as upm') ->join('property as p', 'p.id', '=', 'upm.property_id') ->where('upm.user_id', $user->id) ->select(array( 'p.id as property_id', 'p.name as property_name', 'p.status as property_status', 'upm.created_at', )) ->get(); $properties = array(); foreach ($mappings as $m) { $roomCount = DB::table('property_room')->where('property_id', $m->property_id)->count(); $photoCount = DB::table('property_photo')->where('property_id', $m->property_id)->count(); $factCount = DB::table('property_fact_mapping')->where('property_id', $m->property_id)->count(); $properties[] = array( 'property_id' => $m->property_id, 'property_name' => $m->property_name, 'status' => $m->property_status, 'room_count' => $roomCount, 'photo_count' => $photoCount, 'fact_count' => $factCount, 'created_at' => date('Y-m-d H:i:s', $m->created_at), ); } return response()->json(array( 'status' => true, 'user_id' => $user->id, 'email' => $user->email, 'name' => $user->name . ' ' . $user->surname, 'user_status' => $user->status, 'properties' => $properties, )); } // ───────────────────────────────────────────────────────────────────────────── // DELETE /bulut/property-delete // Body: { "property_id": 123 } veya { "email": "..." } (emailde tüm property'ler) // Odalar, fotoğraflar (dosya dahil), fact mapping, iletişim, user-property mapping, // property kaydı ve kullanıcı silinir. // ───────────────────────────────────────────────────────────────────────────── public function delete(Request $request) { $propertyId = $request->input('property_id'); $email = trim($request->input('email', '')); if (!$propertyId && !$email) { return response()->json(array('status' => false, 'message' => 'property_id veya email gerekli'), 422); } $report = array(); try { DB::beginTransaction(); // Email ile geldiyse önce kullanıcı + property_id'leri bul $userIdToDelete = null; $propertyIds = array(); if ($email) { $user = DB::table('user')->where('email', $email)->first(); if (!$user) { return response()->json(array('status' => false, 'message' => 'Kullanıcı bulunamadı')); } $userIdToDelete = $user->id; $pids = DB::table('user_property_mapping') ->where('user_id', $userIdToDelete) ->pluck('property_id') ->toArray(); $propertyIds = $pids; } else { $propertyIds = array((int)$propertyId); // Bu property'nin tek sahibi varsa onu da sil $ownerCount = DB::table('user_property_mapping') ->where('property_id', $propertyId) ->count(); if ($ownerCount === 1) { $userIdToDelete = DB::table('user_property_mapping') ->where('property_id', $propertyId) ->value('user_id'); } } foreach ($propertyIds as $pid) { $deleted = array('property_id' => $pid); // 1. Fotoğraf dosyalarını sil $photos = DB::table('property_photo')->where('property_id', $pid)->get(); $filesDeleted = 0; $uploadRoot = rtrim(Config::get('app.fileSystemDriver'), '/'); foreach ($photos as $photo) { $base = $uploadRoot . $photo->photo_path . $photo->photo_name; foreach (array('', '_medium', '_thumbnail') as $suffix) { $path = $base . $suffix . '.' . $photo->file_ext; if (File::exists($path)) { File::delete($path); $filesDeleted++; } } } $deleted['photos_db'] = DB::table('property_photo')->where('property_id', $pid)->delete(); $deleted['photos_files'] = $filesDeleted; // 2. Oda ilişkileri $roomIds = DB::table('property_room')->where('property_id', $pid)->pluck('id')->toArray(); if (!empty($roomIds)) { DB::table('property_room_bed')->whereIn('room_id', $roomIds)->delete(); DB::table('property_room_view_mapping')->whereIn('room_id', $roomIds)->delete(); DB::table('property_room_fact_mapping')->whereIn('room_id', $roomIds)->delete(); DB::table('property_room_photo_mapping')->whereIn('room_id', $roomIds)->delete(); } $deleted['rooms'] = DB::table('property_room')->where('property_id', $pid)->delete(); // 3. Fact mapping $deleted['fact_mapping'] = DB::table('property_fact_mapping')->where('property_id', $pid)->delete(); // 4. İletişim $deleted['contact'] = DB::table('property_contact')->where('property_id', $pid)->delete(); // 5. Property info tabloları DB::table('property_additional_info')->where('property_id', $pid)->delete(); DB::table('property_language_spoken')->where('property_id', $pid)->delete(); // 6. User-property mapping $deleted['user_mapping'] = DB::table('user_property_mapping')->where('property_id', $pid)->delete(); // 7. Property kaydı $deleted['property'] = DB::table('property')->where('id', $pid)->delete(); $report[] = $deleted; } // 8. Kullanıcıyı sil (başka property'si yoksa) if ($userIdToDelete) { $remaining = DB::table('user_property_mapping')->where('user_id', $userIdToDelete)->count(); if ($remaining === 0) { DB::table('api_access_token')->where('user_id', $userIdToDelete)->delete(); DB::table('user')->where('id', $userIdToDelete)->delete(); $report['user_deleted'] = $userIdToDelete; } } DB::commit(); return response()->json(array( 'status' => true, 'message' => 'Silme tamamlandı', 'report' => $report, )); } catch (Exception $e) { DB::rollBack(); Log::error('propertyDelete.Error: ' . $e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage()); return response()->json(array( 'status' => false, 'message' => $e->getMessage(), 'report' => $report, ), 500); } } }