id_ws_customhtml_group = $idGroup; $block->title = $formData['title']; $block->type = (string) $formData['type']; $block->show_title = (int) $formData['show_title']; $block->custom_class = (string) ($formData['custom_class'] ?? ''); $block->content = $formData['content']; $block->description = $formData['description'] ?? []; $block->extra_description = $formData['extra_description'] ?? []; $block->link = $formData['link'] ?? []; $block->link_text = $formData['link_text'] ?? []; $block->alert_type = (string) ($formData['alert_type'] ?? 'info'); $block->timeout = (int) ($formData['timeout'] ?? 0); $block->slider_mode = (string) ($formData['slider_mode'] ?? Config::SLIDER_DEFAULT_MODE); $block->slides_per_view_mobile = (float) ($formData['slides_per_view_mobile'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_MOBILE); $block->slides_per_view_tablet = (float) ($formData['slides_per_view_tablet'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_TABLET); $block->slides_per_view_desktop = (float) ($formData['slides_per_view_desktop'] ?? 0); $block->autoplay_delay = (int) ($formData['autoplay_delay'] ?? 0); $block->image = $this->saveUploadedImage($formData['image'] ?? null, ''); $block->active = (int) $formData['active']; $block->ordering = $this->getNextOrdering($idGroup); if (!$block->add()) { return false; } $this->saveFaqs((int) $block->id, $formData['faqs'] ?? []); $this->saveSlides((int) $block->id, $formData['slides'] ?? []); return $block->id; } public function update($id, array $formData) { $block = new CustomHtmlBlock((int) $id); if (!Validate::isLoadedObject($block)) { return false; } $block->id_ws_customhtml_group = (int) $formData['id_ws_customhtml_group']; $block->title = $formData['title']; $block->type = (string) $formData['type']; $block->show_title = (int) $formData['show_title']; $block->custom_class = (string) ($formData['custom_class'] ?? ''); $block->content = $formData['content']; $block->description = $formData['description'] ?? []; $block->extra_description = $formData['extra_description'] ?? []; $block->link = $formData['link'] ?? []; $block->link_text = $formData['link_text'] ?? []; $block->alert_type = (string) ($formData['alert_type'] ?? 'info'); $block->timeout = (int) ($formData['timeout'] ?? 0); $block->slider_mode = (string) ($formData['slider_mode'] ?? Config::SLIDER_DEFAULT_MODE); $block->slides_per_view_mobile = (float) ($formData['slides_per_view_mobile'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_MOBILE); $block->slides_per_view_tablet = (float) ($formData['slides_per_view_tablet'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_TABLET); $block->slides_per_view_desktop = (float) ($formData['slides_per_view_desktop'] ?? 0); $block->autoplay_delay = (int) ($formData['autoplay_delay'] ?? 0); $block->image = $this->saveUploadedImage($formData['image'] ?? null, $block->image); $block->active = (int) $formData['active']; if (!$block->update()) { return false; } $this->saveFaqs((int) $id, $formData['faqs'] ?? []); $this->saveSlides((int) $id, $formData['slides'] ?? []); return true; } /** * Validates and stores an uploaded banner image, replacing the previous * one if any. Never trusts the client-supplied filename or MIME header — * the real MIME type is detected server-side and a random filename is * generated, so nothing from the upload itself ends up on disk. */ private function saveUploadedImage(?UploadedFile $file, string $currentFilename): string { if (null === $file) { return $currentFilename; } if (!$file->isValid()) { throw new Exception('The uploaded image could not be read.'); } if ($file->getSize() > Config::BANNER_IMAGE_MAX_SIZE) { throw new Exception('The uploaded image is too large (max 5 MB).'); } $mimeType = $file->getMimeType(); $extension = array_search($mimeType, Config::BANNER_IMAGE_ALLOWED_EXTENSIONS, true); if (false === $extension) { throw new Exception('The uploaded file is not a supported image type (jpg, png, gif, webp).'); } $targetDir = CustomHtmlBlock::getBannerImageDir(); if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true) && !is_dir($targetDir)) { throw new Exception('Unable to create the banner image directory.'); } $newFilename = bin2hex(random_bytes(16)) . '.' . $extension; $file->move($targetDir, $newFilename); if (!empty($currentFilename) && is_file($targetDir . $currentFilename)) { unlink($targetDir . $currentFilename); } return $newFilename; } private function getNextOrdering(int $idGroup): int { $maxOrdering = Db::getInstance()->getValue( 'SELECT MAX(`ordering`) FROM `' . _DB_PREFIX_ . 'ws_customhtml` WHERE `id_ws_customhtml_group` = ' . (int) $idGroup ); return $maxOrdering === false || $maxOrdering === null ? 0 : ((int) $maxOrdering + 1); } private function saveFaqs(int $idBlock, array $faqs): void { CustomHtmlFaq::deleteByBlockId($idBlock); foreach (array_values($faqs) as $position => $faqData) { $question = $faqData['question'] ?? []; if (empty(array_filter($question, static function ($value) { return trim((string) $value) !== ''; }))) { continue; } $faq = new CustomHtmlFaq(); $faq->id_ws_customhtml = $idBlock; $faq->question = $question; $faq->answer = $faqData['answer'] ?? []; $faq->ordering = $position; $faq->add(); } } /** * Slides are fully replaced on every save, like FAQ items — except a * slide's image can't round-trip through its file input, so the previous * filename is looked up by the slide's hidden id and reused whenever no * new file was uploaded for that row. */ private function saveSlides(int $idBlock, array $slides): void { $existingImages = []; foreach (CustomHtmlSlide::getByBlockId($idBlock) as $existingSlide) { $existingImages[(int) $existingSlide['id_ws_customhtml_slide']] = $existingSlide['image']; } CustomHtmlSlide::deleteByBlockId($idBlock); foreach (array_values($slides) as $position => $slideData) { $existingId = (int) ($slideData['id'] ?? 0); $existingImage = $existingImages[$existingId] ?? ''; $slide = new CustomHtmlSlide(); $slide->id_ws_customhtml = $idBlock; $slide->title = $slideData['title'] ?? []; $slide->description = $slideData['description'] ?? []; $slide->extra_description = $slideData['extra_description'] ?? []; $slide->link = $slideData['link'] ?? []; $slide->link_text = $slideData['link_text'] ?? []; $slide->image = $this->saveUploadedImage($slideData['image'] ?? null, $existingImage); $slide->ordering = $position; $slide->add(); } } }