html_content); $fields = []; foreach ($placeholders as $placeholder) { // Handle special.* prefix - remove it for processing $normalizedPlaceholder = $placeholder; if (str_starts_with($placeholder, 'special.')) { $normalizedPlaceholder = substr($placeholder, 8); // Remove 'special.' prefix } $parts = explode('.', $normalizedPlaceholder); if (count($parts) < 2) { continue; // Skip invalid placeholders } $type = $parts[0]; // Skip special.* placeholders - they are special placeholders (page.*, menu, staticMenu), not form fields if (str_starts_with($placeholder, 'special.')) { continue; } // Skip custom.* placeholders - they are blade components, not form fields if ($type === 'custom') { continue; } // Skip page.* placeholders - they are special placeholders, not form fields if ($type === 'page') { continue; } // Skip menu and staticMenu placeholders - they are special placeholders, not form fields if ($normalizedPlaceholder === 'menu' || $normalizedPlaceholder === 'staticMenu') { continue; } // For form fields, we expect type.field format (2 parts) if (count($parts) !== 2) { continue; // Skip invalid placeholders } [$type, $name] = $parts; $label = str($name)->title()->replace('_', ' ')->toString(); // Get existing value from existingData if provided $existingValue = $existingData[$placeholder] ?? null; // Get default value from template's default_data $defaultValue = $defaultData[$placeholder] ?? null; // If existing value is empty (null, empty string, empty array), use default $isValueEmpty = $existingValue === null || $existingValue === '' || (is_array($existingValue) && empty($existingValue)); // Use default if existing value is empty $finalValue = $isValueEmpty && $defaultValue !== null ? $defaultValue : $existingValue; $field = match($type) { // Text Input Variants 'text' => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->maxLength(255), 'email' => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->email() ->maxLength(255), 'url' => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->url() ->maxLength(500), 'tel' => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->tel() ->maxLength(20), 'number' => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->numeric(), 'password' => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->password() ->dehydrated(fn ($state) => filled($state)) ->maxLength(255), // Text Area & Editors 'textarea' => Textarea::make("{$dataKey}.{$placeholder}") ->label($label) ->rows(4) ->maxLength(5000), 'richtext' => RichEditor::make("{$dataKey}.{$placeholder}") ->label($label) ->toolbarButtons([ 'bold', 'italic', 'underline', 'link', 'bulletList', 'orderedList', 'h2', 'h3', ]) ->maxLength(50000), 'markdown' => MarkdownEditor::make("{$dataKey}.{$placeholder}") ->label($label) ->toolbarButtons([ 'bold', 'italic', 'strike', 'link', 'heading', 'bulletList', 'orderedList', 'codeBlock', ]) ->maxLength(50000), 'code' => CodeEditor::make("{$dataKey}.{$placeholder}") ->label($label) //->lineNumbers() ->maxLength(50000), // Date & Time 'date' => DatePicker::make("{$dataKey}.{$placeholder}") ->label($label) ->native(false), 'datetime' => DateTimePicker::make("{$dataKey}.{$placeholder}") ->label($label) ->native(false) ->seconds(false), 'time' => TimePicker::make("{$dataKey}.{$placeholder}") ->label($label) ->native(false) ->seconds(false), // File Uploads 'image' => FileUpload::make("{$dataKey}.{$placeholder}") ->label($label) ->image() ->disk('public') ->directory('templates/images') ->imageEditor() ->imageEditorAspectRatios([null, '16:9', '4:3', '1:1']) ->maxSize(5120), 'images' => FileUpload::make("{$dataKey}.{$placeholder}") ->label($label) ->image() ->disk('public') ->directory('templates/images') ->multiple() ->imageEditor() ->maxSize(5120) ->maxFiles(10), 'file' => FileUpload::make("{$dataKey}.{$placeholder}") ->label($label) ->disk('public') ->directory('templates/files') ->maxSize(10240), 'files' => FileUpload::make("{$dataKey}.{$placeholder}") ->label($label) ->disk('public') ->directory('templates/files') ->multiple() ->maxSize(10240) ->maxFiles(10), // Selection & Options 'select' => Select::make("{$dataKey}.{$placeholder}") ->label($label) ->options(self::getSelectOptions($name)) ->searchable(), 'multiselect' => Select::make("{$dataKey}.{$placeholder}") ->label($label) ->multiple() ->options(self::getSelectOptions($name)) ->searchable(), 'checkbox' => Checkbox::make("{$dataKey}.{$placeholder}") ->label($label) ->inline(false), 'checkboxlist' => CheckboxList::make("{$dataKey}.{$placeholder}") ->label($label) ->options(self::getSelectOptions($name)) ->columns(2), 'radio' => Radio::make("{$dataKey}.{$placeholder}") ->label($label) ->options(self::getSelectOptions($name)) ->inline() ->inlineLabel(false), 'toggle' => Toggle::make("{$dataKey}.{$placeholder}") ->label($label) ->inline(false), 'togglebuttons' => ToggleButtons::make("{$dataKey}.{$placeholder}") ->label($label) ->options(self::getSelectOptions($name)) ->inline() ->grouped(), // Color & Visual 'color' => ColorPicker::make("{$dataKey}.{$placeholder}") ->label($label), // Structured Data 'tags' => TagsInput::make("{$dataKey}.{$placeholder}") ->label($label) ->separator(','), 'keyvalue' => KeyValue::make("{$dataKey}.{$placeholder}") ->label($label) ->keyLabel('Key') ->valueLabel('Value') ->reorderable(), // Advanced 'slider' => Slider::make("{$dataKey}.{$placeholder}") ->label($label) ->minValue(0) ->maxValue(100) ->step(1), 'hidden' => Hidden::make("{$dataKey}.{$placeholder}"), // Default default => TextInput::make("{$dataKey}.{$placeholder}") ->label($label) ->maxLength(255) ->helperText("Unknown type: {$type}"), }; // Set default value: use finalValue (which falls back to default if existing is empty) if ($finalValue !== null) { $field->default($finalValue); } $fields[] = $field; } return $fields; } /** * Parse placeholders from HTML content * Format: {type.field_name} or {type.field-name} or {special.type.field} */ public static function parsePlaceholders(string $html): array { // Pattern: {type.field} or {special.type.field} or {type.field.field2} etc. preg_match_all('/\{([a-z]+(?:\.[a-z][a-z_-]*)+)\}/i', $html, $matches); return array_unique($matches[1] ?? []); } /** * Get select options for a field * Can be extended to load from database or config */ protected static function getSelectOptions(string $fieldName): array { return config("template-options.{$fieldName}", [ 'option_1' => __('Option 1'), 'option_2' => __('Option 2'), 'option_3' => __('Option 3'), ]); } /** * Replace placeholders in HTML with actual data * Supports both {text.title} and {{text.title}} formats * Also supports {menu} and {staticMenu} placeholders for menu rendering * Also supports {custom.component_name} for custom blade components * Also supports {page.content}, {page.title}, {page.excerpt} for page data * * @param string $html HTML content with placeholders * @param array $data Data array for placeholders * @param Model|null $model Optional model instance (Page, Blog, etc.) for dynamic data */ public static function replacePlaceholders(string $html, array $data, ?Model $model = null): string { // Handle special {page.content} placeholder - Sayfa/model içeriğini gösterir if (str_contains($html, '{page.content}')) { $pageContent = ''; if ($model) { // Translation desteği varsa çeviriyi kullan if (method_exists($model, 'translate')) { $pageContent = $model->translate('content') ?: ($model->content ?? ''); } else { $pageContent = $model->content ?? ''; } } $html = str_replace('{page.content}', $pageContent ?? '', $html); } // Handle special {page.title} placeholder - Sayfa/model başlığını gösterir if (str_contains($html, '{page.title}')) { $pageTitle = ''; if ($model) { if (method_exists($model, 'translate')) { $pageTitle = $model->translate('title') ?: ($model->title ?? ''); } else { $pageTitle = $model->title ?? ''; } } $html = str_replace('{page.title}', $pageTitle ?? '', $html); } // Handle special {page.excerpt} placeholder - Sayfa/model özetini gösterir if (str_contains($html, '{page.excerpt}')) { $pageExcerpt = ''; if ($model) { if (method_exists($model, 'translate')) { $pageExcerpt = $model->translate('excerpt') ?: ($model->excerpt ?? ''); } else { $pageExcerpt = $model->excerpt ?? ''; } } $html = str_replace('{page.excerpt}', $pageExcerpt ?? '', $html); } // Handle special {menu} placeholder first if (str_contains($html, '{menu}')) { $renderedMenu = \App\Services\MenuService::render(); $html = str_replace('{menu}', $renderedMenu, $html); } // Handle special {staticMenu} placeholder if (str_contains($html, '{staticMenu}')) { $renderedStaticMenu = view('components.static-menu')->render(); $html = str_replace('{staticMenu}', $renderedStaticMenu, $html); } // Handle custom blade components: {custom.component_name} or {custom.component-name} preg_match_all('/\{custom\.([a-z][a-z_-]*)\}/i', $html, $customMatches); if (!empty($customMatches[0])) { foreach ($customMatches[0] as $index => $fullMatch) { $componentName = $customMatches[1][$index] ?? null; if ($componentName) { // Normalize component name to lowercase for consistent file system access $componentName = strtolower($componentName); $viewPath = "components.custom.{$componentName}"; if (view()->exists($viewPath)) { try { $renderedComponent = view($viewPath)->render(); // Ensure rendered component is properly formatted $renderedComponent = trim($renderedComponent); $html = str_replace($fullMatch, $renderedComponent, $html); } catch (\Exception $e) { // Log the error for debugging \Log::error("Custom component render error: {$viewPath}", [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // Show error message in preview (visible in development) $errorMessage = htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'); $errorHtml = "