get(); return view('admin.blocks.index', compact('blocks')); } public function create(): View { return view('admin.blocks.form', [ 'block' => new Block, 'layouts' => BlockLayoutRegistry::all(), 'layoutDef' => null, ]); } public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'name' => 'required|string|max:100|regex:/^[a-z0-9_]+$/|unique:blocks,name', 'title' => 'required|string|max:255', 'layout' => 'required|string|in:'.implode(',', BlockLayoutRegistry::keys()), 'is_active' => 'nullable|boolean', ]); $validated['is_active'] = $request->boolean('is_active'); $validated['data'] = $this->buildData($request, $validated['layout'], null); Block::create($validated); return redirect()->route('admin.blocks.index') ->with('success', 'Блок «'.$validated['title'].'» создан.'); } public function edit(Block $block): View { return view('admin.blocks.form', [ 'block' => $block, 'layouts' => BlockLayoutRegistry::all(), 'layoutDef' => BlockLayoutRegistry::get($block->layout ?? ''), ]); } public function update(Request $request, Block $block): RedirectResponse { $validated = $request->validate([ 'title' => 'required|string|max:255', 'is_active' => 'nullable|boolean', ]); $validated['is_active'] = $request->boolean('is_active'); $validated['data'] = $this->buildData($request, $block->layout, $block->data ?? []); $block->update($validated); Cache::forget('block.'.$block->name); // Cache::tags() не поддерживается database-драйвером — сбрасываем секции всех страниц явно foreach (['home', 'services', 'contacts', 'privacy', 'offer'] as $slug) { Cache::forget('page_sections.'.$slug); Cache::forget('page.'.$slug); } return redirect()->route('admin.blocks.index') ->with('success', 'Блок «'.$block->title.'» обновлён.'); } public function destroy(Block $block): RedirectResponse { Cache::forget('block.'.$block->name); $title = $block->title; $block->delete(); return redirect()->route('admin.blocks.index') ->with('success', 'Блок «'.$title.'» удалён.'); } // ── Сборка data: текстовые поля + загруженные изображения ──────────── private function buildData(Request $request, ?string $layoutKey, ?array $oldData): array { $layoutDef = $layoutKey ? BlockLayoutRegistry::get($layoutKey) : null; if (! $layoutDef) { return []; } $raw = $request->input('data', []); $uploads = $request->file('uploads', []); $result = []; foreach ($layoutDef['fields'] as $field) { $name = $field['name']; if ($field['type'] === 'repeater') { $rows = array_values($raw[$name] ?? []); $oldRows = $oldData[$name] ?? []; $result[$name] = array_map(function (array $row, int $i) use ($field, $uploads, $name, $oldRows, $layoutKey) { $clean = []; foreach ($field['sub_fields'] as $sub) { $subName = $sub['name']; $oldVal = $oldRows[$i][$subName] ?? ''; $uploadFile = data_get($uploads, "{$name}.{$i}.{$subName}"); if ($sub['type'] === 'image' && $uploadFile?->isValid()) { $this->images->delete($oldVal); $clean[$subName] = $this->images->store($uploadFile, $layoutKey, $sub); } else { $clean[$subName] = $sub['type'] === 'image' ? ($row[$subName] ?? $oldVal) // hidden input сохраняет старый путь : trim($row[$subName] ?? ''); } } return $clean; }, $rows, array_keys($rows)); } elseif ($field['type'] === 'checkbox') { // Булев флаг: hidden-поле даёт "0", чекбокс при отмеченном даёт "1" $result[$name] = (bool) ($raw[$name] ?? false); } elseif (in_array($field['type'], ['cars_picker', 'makes_picker'])) { // Мультиселект — хранится как plain array значений $result[$name] = array_values(array_filter((array) ($raw[$name] ?? []))); } elseif ($field['type'] === 'reviews_picker') { // Мультиселект отзывов — хранится как plain array целочисленных ID $result[$name] = array_values(array_map('intval', array_filter((array) ($raw[$name] ?? [])))); } elseif ($field['type'] === 'services_picker') { // Мультиселект услуг — plain array целочисленных ID $result[$name] = array_values(array_map('intval', array_filter((array) ($raw[$name] ?? [])))); } elseif ($field['type'] === 'image') { $oldVal = $oldData[$name] ?? ''; $uploadFile = data_get($uploads, $name); if ($uploadFile?->isValid()) { $this->images->delete($oldVal); $result[$name] = $this->images->store($uploadFile, $layoutKey, $field); } else { // Сохраняем путь из hidden-input (не трогаем старый файл) $result[$name] = $raw[$name] ?? $oldVal; } } else { $result[$name] = trim($raw[$name] ?? ''); } } return $result; } }