validateContactData($request); $store = $this->storeManager->getStore(); $website = $store->getWebsite(); $parent = $this->productRepository->getById((int)$request->getParam('parent_product_id'), false, (int)$store->getId()); $product = $this->productRepository->getById((int)$request->getParam('product_id'), false, (int)$store->getId()); if (!$parent instanceof Product || $parent->getTypeId() !== Configurable::TYPE_CODE) { throw new LocalizedException(__('The inquiry product is invalid.')); } if (!$product instanceof Product || (int)$product->getStatus() !== ProductStatus::STATUS_ENABLED) { throw new LocalizedException(__('The selected product is unavailable.')); } if (!in_array((int)$parent->getId(), array_map('intval', $this->configurableResource->getParentIdsByChild((int)$product->getId())), true)) { throw new LocalizedException(__('The selected product does not belong to this product.')); } $availability = $this->availabilityResolver->resolve($product, $website); if (!$availability['requires_inquiry']) { throw new LocalizedException(__('This product is currently available for purchase.')); } $messageHash = hash('sha256', $data['email'] . '|' . $product->getId() . '|' . $this->normalizeMessage($data['message'])); $remoteIpHash = $this->getRemoteIpHash(); if ($this->isExactDuplicate($messageHash)) { return ['duplicate' => true]; } $this->assertWithinRateLimit($data['email'], $remoteIpHash); $country = $this->countryFactory->create()->loadByCode($data['country_id']); if (!$country->getId()) { throw new LocalizedException(__('Select a valid country.')); } $inquiry = $this->inquiryFactory->create(); $inquiry->setData([ 'parent_product_id' => (int)$parent->getId(), 'parent_sku' => (string)$parent->getSku(), 'parent_name' => (string)$parent->getName(), 'product_id' => (int)$product->getId(), 'product_sku' => (string)$product->getSku(), 'product_name' => (string)$product->getName(), 'selected_options' => $this->json->serialize($this->getSelectedOptions($parent, $product)), 'store_id' => (int)$store->getId(), 'website_id' => (int)$website->getId(), 'stock_id' => (int)$availability['stock_id'], 'customer_id' => $this->customerSession->isLoggedIn() ? (int)$this->customerSession->getCustomerId() : null, 'name' => $data['name'], 'email' => $data['email'], 'country_id' => (string)$country->getId(), 'country_name' => (string)$country->getName(), 'company' => $data['company'], 'telephone' => $data['telephone'], 'message' => $data['message'], 'status' => Inquiry::STATUS_NEW, 'stock_status' => (int)$availability['stock_status'], 'is_salable' => (int)$availability['is_salable'], 'availability_reason' => (string)$availability['reason'], 'message_hash' => $messageHash, 'remote_ip_hash' => $remoteIpHash, ]); $this->inquiryResource->save($inquiry); return ['duplicate' => false, 'inquiry_id' => (int)$inquiry->getId()]; } private function validateContactData(RequestInterface $request): array { if (trim((string)$request->getParam('hideit', '')) !== '') { throw new LocalizedException(__('Unable to submit the form.')); } $data = [ 'name' => trim((string)$request->getParam('name', '')), 'email' => mb_strtolower(trim((string)$request->getParam('email', ''))), 'country_id' => strtoupper(trim((string)$request->getParam('country_id', ''))), 'company' => trim((string)$request->getParam('company', '')), 'telephone' => trim((string)$request->getParam('telephone', '')), 'message' => trim((string)$request->getParam('message', '')), ]; if ($data['name'] === '' || $data['message'] === '' || $data['country_id'] === '') { throw new LocalizedException(__('Complete all required fields.')); } if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new LocalizedException(__('Enter a valid email address.')); } if (mb_strlen($data['name']) > 255 || mb_strlen($data['email']) > 255 || mb_strlen($data['company']) > 255 || mb_strlen($data['telephone']) > 64) { throw new LocalizedException(__('One or more fields are too long.')); } if (mb_strlen($data['message']) > 5000 || strlen($data['country_id']) !== 2) { throw new LocalizedException(__('The submitted information is invalid.')); } return $data; } private function getSelectedOptions(Product $parent, Product $product): array { $options = []; foreach ($parent->getTypeInstance()->getConfigurableAttributes($parent) as $configurableAttribute) { $attribute = $configurableAttribute->getProductAttribute(); $value = $product->getAttributeText((string)$attribute->getAttributeCode()); $options[] = [ 'label' => (string)$attribute->getStoreLabel(), 'value' => is_array($value) ? implode(', ', $value) : (string)$value, ]; } return $options; } private function isExactDuplicate(string $messageHash): bool { return $this->collectionFactory->create() ->addFieldToFilter('message_hash', $messageHash) ->addFieldToFilter('created_at', ['gteq' => $this->dateTime->gmtDate('Y-m-d H:i:s', time() - 600)]) ->getSize() > 0; } private function assertWithinRateLimit(string $email, string $remoteIpHash): void { $since = $this->dateTime->gmtDate('Y-m-d H:i:s', time() - 86400); $emailCount = $this->collectionFactory->create() ->addFieldToFilter('email', $email) ->addFieldToFilter('created_at', ['gteq' => $since]) ->getSize(); $ipCount = $remoteIpHash === '' ? 0 : $this->collectionFactory->create() ->addFieldToFilter('remote_ip_hash', $remoteIpHash) ->addFieldToFilter('created_at', ['gteq' => $since]) ->getSize(); if ($emailCount >= self::MAX_DAILY_SUBMISSIONS || $ipCount >= self::MAX_DAILY_IP_SUBMISSIONS) { throw new LocalizedException(__('Too many inquiries have been submitted. Please try again later.')); } } private function normalizeMessage(string $message): string { return mb_strtolower((string)preg_replace('/\s+/u', ' ', trim($message))); } private function getRemoteIpHash(): string { $ip = trim((string)$this->remoteAddress->getRemoteAddress()); return $ip === '' ? '' : hash('sha256', $ip); } }