| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- <?php
- namespace App\Http\Controllers\Admin;
- /*
- * BlockLayoutWizardController — мастер генерации макетов блоков.
- *
- * Шаг 1: пользователь вставляет HTML-фрагмент секции.
- * Шаг 2: парсер определяет поля, показывает черновик Blade и таблицу полей для правки.
- * Шаг 3: сохраняем два файла и редиректим на создание блока.
- *
- * Генерируемые файлы:
- * app/Support/layouts/{name}.php — определение полей для реестра
- * resources/views/blocks/{name}.blade.php — Blade-шаблон для рендера
- */
- use App\Http\Controllers\Controller;
- use App\Support\BlockLayoutParser;
- use App\Support\BlockLayoutRegistry;
- use Illuminate\Http\RedirectResponse;
- use Illuminate\Http\Request;
- use Illuminate\View\View;
- class BlockLayoutWizardController extends Controller
- {
- // Шаг 1: форма ввода HTML
- public function index(): View
- {
- return view('admin.blocks.wizard', ['parsed' => null]);
- }
- // Шаг 2: парсим HTML, показываем обнаруженные поля и черновик Blade
- public function analyze(Request $request): View
- {
- $request->validate([
- 'layout_name' => 'required|string|max:100|regex:/^[a-z0-9_]+$/',
- 'layout_title' => 'required|string|max:255',
- 'html' => 'required|string',
- ]);
- $parsed = (new BlockLayoutParser)->parse($request->input('html'));
- return view('admin.blocks.wizard', [
- 'parsed' => $parsed,
- 'layout_name' => $request->input('layout_name'),
- 'layout_title' => $request->input('layout_title'),
- 'original_html' => $request->input('html'),
- ]);
- }
- // Шаг 3: сохраняем файлы макета и Blade-шаблона
- public function generate(Request $request): RedirectResponse
- {
- $request->validate([
- 'layout_name' => 'required|string|max:100|regex:/^[a-z0-9_]+$/',
- 'layout_title' => 'required|string|max:255',
- 'blade' => 'required|string',
- ]);
- $name = $request->input('layout_name');
- $title = $request->input('layout_title');
- // Защита: не перезаписываем встроенные макеты
- if (isset(BlockLayoutRegistry::all()[$name]) && !file_exists(app_path("Support/layouts/{$name}.php"))) {
- return back()->withInput()->withErrors(['layout_name' => 'Такое имя уже занято встроенным макетом.']);
- }
- // Собираем определение полей из submitted-данных формы
- $fields = $this->buildFieldsFromRequest($request);
- // Сохраняем app/Support/layouts/{name}.php
- $layoutPhp = $this->renderLayoutPhp($title, $fields);
- file_put_contents(app_path("Support/layouts/{$name}.php"), $layoutPhp);
- // Сохраняем resources/views/blocks/{name}.blade.php
- $bladeContent = "{{-- Blade-шаблон блока «{$title}». Сгенерирован мастером макетов. --}}\n"
- . trim($request->input('blade')) . "\n";
- file_put_contents(resource_path("views/blocks/{$name}.blade.php"), $bladeContent);
- // Сбрасываем кеш скомпилированных вьюх
- array_map('unlink', glob(storage_path('framework/views/*.php')));
- return redirect()->route('admin.blocks.create')
- ->with('success', "Макет «{$title}» создан. Теперь создайте блок на его основе.");
- }
- // Собирает поля из multipart-данных формы шага 2
- private function buildFieldsFromRequest(Request $request): array
- {
- $isRepeater = (bool) $request->input('is_repeater', false);
- $rawFlatFields = $request->input('flat_fields', []);
- $flatFields = array_values(array_filter(
- array_map(fn($f) => [
- 'name' => trim($f['name'] ?? ''),
- 'label' => trim($f['label'] ?? ''),
- 'type' => $f['type'] ?? 'text',
- ], $rawFlatFields),
- fn($f) => $f['name'] !== ''
- ));
- if (!$isRepeater) {
- return $flatFields;
- }
- // Repeater: плоские поля перед/после + repeater-поле
- $subRaw = $request->input('sub_fields', []);
- $subFields = array_values(array_filter(
- array_map(fn($f) => [
- 'name' => trim($f['name'] ?? ''),
- 'label' => trim($f['label'] ?? ''),
- 'type' => $f['type'] ?? 'text',
- ], $subRaw),
- fn($f) => $f['name'] !== ''
- ));
- $repeaterField = [
- 'name' => trim($request->input('repeater_name', 'items')),
- 'label' => trim($request->input('repeater_label', 'Элементы')),
- 'type' => 'repeater',
- 'sub_fields' => $subFields,
- ];
- // Плоские поля (если есть) идут перед repeater
- return array_merge($flatFields, [$repeaterField]);
- }
- // Рендерит PHP-файл определения макета
- private function renderLayoutPhp(string $title, array $fields): string
- {
- $fieldsExport = $this->varExportPretty($fields, 1);
- return <<<PHP
- <?php
- // Сгенерирован мастером макетов tocha_app
- return [
- 'title' => {$this->exportStr($title)},
- 'fields' => {$fieldsExport},
- ];
- PHP;
- }
- // Аккуратный var_export с отступами вместо стандартного
- private function varExportPretty(mixed $value, int $depth = 0): string
- {
- $pad = str_repeat(' ', $depth);
- $pad1 = str_repeat(' ', $depth + 1);
- if (is_string($value)) {
- return $this->exportStr($value);
- }
- if (!is_array($value)) {
- return var_export($value, true);
- }
- if (empty($value)) {
- return '[]';
- }
- $isList = array_keys($value) === range(0, count($value) - 1);
- $lines = [];
- foreach ($value as $k => $v) {
- $key = $isList ? '' : $this->exportStr((string) $k) . ' => ';
- $lines[] = $pad1 . $key . $this->varExportPretty($v, $depth + 1);
- }
- return "[\n" . implode(",\n", $lines) . ",\n{$pad}]";
- }
- private function exportStr(string $s): string
- {
- return "'" . addcslashes($s, "'\\") . "'";
- }
- }
|