diff --git a/app/Filament/Forms/Resources/ElementTemplateManagementResource.php b/app/Filament/Forms/Resources/ElementTemplateManagementResource.php index a9292757..38a68c65 100644 --- a/app/Filament/Forms/Resources/ElementTemplateManagementResource.php +++ b/app/Filament/Forms/Resources/ElementTemplateManagementResource.php @@ -71,21 +71,20 @@ public static function table(Table $table): Table Tables\Columns\TextColumn::make('data_path_preview')->label('Data path') ->state(fn(FormElement $r) => optional($r->dataBindings->first())->path) ->copyable()->toggleable(isToggledHiddenByDefault: true), - // hidden-by-default fields(some were above for displaying orders) - Tables\Columns\IconColumn::make('visible_web')->label('Visible (Web)')->boolean()->sortable()->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\IconColumn::make('visible_pdf')->label('Visible (PDF)')->boolean()->sortable()->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\IconColumn::make('is_required')->label('Required')->boolean()->sortable()->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\IconColumn::make('is_read_only')->label('Read‑only')->boolean()->sortable()->toggleable(isToggledHiddenByDefault: true), + // hidden-by-default fields (some were above for displaying orders) + self::makeStatusColumn('visible_web', 'Visible (Web)'), + self::makeStatusColumn('visible_pdf', 'Visible (PDF)'), + self::makeStatusColumn('is_required', 'Required'), + self::makeStatusColumn('is_read_only', 'Read‑only'), Tables\Columns\IconColumn::make('save_on_submit')->label('Save on Submit')->boolean()->sortable()->toggleable(isToggledHiddenByDefault: true), // Tables\Columns\IconColumn::make('is_template')->label('Is template')->boolean()->sortable()->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('source_element_id')->label('Source element ID')->sortable()->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\TextColumn::make('custom_read_only')->label('Custom Read Only')->wrap()->toggleable(isToggledHiddenByDefault: true)->searchable(), ]) ->filters([ SelectFilter::make('elementable_type')->label('Element type')->options(function () { // Canonical label map from the model $labels = FormElement::getAvailableElementTypes(); // [FQCN => 'Text Input', ...] - + // Only show types that are present among templates $typesInDb = FormElement::query() ->where('is_template', true) @@ -107,18 +106,18 @@ public static function table(Table $table): Table return $options; // [FQCN => 'Text Input', ...] }), SelectFilter::make('tags')->relationship('tags', 'name')->label('Tags')->multiple(), - TernaryFilter::make('visible_web')->label('Visible on Web')->boolean(), - TernaryFilter::make('visible_pdf')->label('Visible on PDF')->boolean(), - TernaryFilter::make('is_required')->label('Required')->boolean(), - TernaryFilter::make('is_read_only')->label('Read‑only')->boolean(), + self::makeStatusFilter('visible_web', 'Visible on Web'), + self::makeStatusFilter('visible_pdf', 'Visible on PDF'), + self::makeStatusFilter('is_required', 'Required'), + self::makeStatusFilter('is_read_only', 'Read‑only'), TernaryFilter::make('save_on_submit')->label('Save on Submit')->boolean(), - TernaryFilter::make('is_template')->label('Is Template')->boolean(), + // TernaryFilter::make('is_template')->label('Is Template')->boolean(), ]) // edit and untemplate action buttons ->actions([ Tables\Actions\Action::make('openBuilder') ->label('To Builder') - ->icon('heroicon-o-cube-transparent') + ->icon('heroicon-o-wrench-screwdriver') ->visible(fn(FormElement $r) => filled($r->form_version_id)) ->url(fn(FormElement $r) => url("/forms/form-versions/{$r->form_version_id}/build")) ->openUrlInNewTab(), @@ -207,4 +206,34 @@ private static function getElementTypeLabels(): array // Fallback: empty map; callers will fallback to class_basename return []; } -} + + /** + * Create a badge column for visibility/requirement/read-only fields. + */ + private static function makeStatusColumn(string $field, string $label): Tables\Columns\TextColumn + { + return Tables\Columns\TextColumn::make($field) + ->label($label) + ->formatStateUsing(fn($state) => FormElement::getToggleButtonStates()[$state] ?? $state) + ->badge() + ->color(fn($state) => match ($state) { + 'always' => 'success', + 'icm' => 'info', + 'portal' => 'info', + 'never' => 'gray', + }) + ->sortable() + ->toggleable(isToggledHiddenByDefault: true); + } + + /** + * Create a filter for visibility/requirement/read-only fields. + */ + private static function makeStatusFilter(string $field, string $label): SelectFilter + { + return SelectFilter::make($field) + ->label($label) + ->multiple() + ->options(FormElement::getToggleButtonStates()); + } +} \ No newline at end of file diff --git a/app/Filament/Forms/Resources/ElementTemplateManagementResource/Pages/ViewElementTemplateManagement.php b/app/Filament/Forms/Resources/ElementTemplateManagementResource/Pages/ViewElementTemplateManagement.php index 0acab374..08fe020e 100644 --- a/app/Filament/Forms/Resources/ElementTemplateManagementResource/Pages/ViewElementTemplateManagement.php +++ b/app/Filament/Forms/Resources/ElementTemplateManagementResource/Pages/ViewElementTemplateManagement.php @@ -3,6 +3,7 @@ namespace App\Filament\Forms\Resources\ElementTemplateManagementResource\Pages; use App\Filament\Forms\Resources\ElementTemplateManagementResource; +use App\Filament\Forms\Resources\FormVersionResource; use App\Helpers\GeneralTabHelper; use App\Models\FormBuilding\FormElement; use Filament\Actions; @@ -15,6 +16,7 @@ use Filament\Forms\Components\Toggle; use Filament\Resources\Pages\ViewRecord; use Filament\Forms\Components\Actions\Action as FieldAction; +use Illuminate\Support\Facades\Gate; class ViewElementTemplateManagement extends ViewRecord { @@ -44,6 +46,14 @@ protected function getHeaderActions(): array ->icon('heroicon-o-pencil-square') ->url(ElementTemplateManagementResource::getUrl('edit', ['record' => $record])) ->visible(!$isParented), + + Actions\Action::make('build') + ->label('To Builder') + ->icon('heroicon-o-wrench-screwdriver') + ->url(fn() => FormVersionResource::getUrl('build', ['record' => $this->record->form_version_id])) + ->color('primary') + ->outlined() + ->visible(fn() => Gate::allows('form-developer')), ]; } diff --git a/app/Filament/Forms/Resources/FormResource.php b/app/Filament/Forms/Resources/FormResource.php index e695501f..1dcde8fe 100644 --- a/app/Filament/Forms/Resources/FormResource.php +++ b/app/Filament/Forms/Resources/FormResource.php @@ -474,18 +474,25 @@ public static function table(Table $table): Table Tables\Columns\TextColumn::make('decommissioned') ->label('Status') ->badge() + ->sortable() + ->searchable() ->formatStateUsing(fn(bool $state): string => $state ? 'Inactive' : 'Active') ->color(fn(bool $state): string => $state ? 'danger' : 'success'), Tables\Columns\TextColumn::make('ministry.short_name') ->searchable() ->sortable() + ->toggleable() ->label('Ministry'), Tables\Columns\TextColumn::make('businessAreas.name') + ->label('Business Areas') ->badge() - ->label('Business Areas'), + ->toggleable() + ->sortable() + ->searchable(), Tables\Columns\TextColumn::make('form_purpose') ->searchable() ->limit(30) + ->toggleable(isToggledHiddenByDefault: true) ->tooltip(function ($record): ?string { $form_purpose = optional($record)->form_purpose ?? ''; return Str::length($form_purpose) > 30 @@ -494,8 +501,7 @@ public static function table(Table $table): Table }), Tables\Columns\TextColumn::make('notes') ->searchable() - ->toggleable() - ->toggledHiddenByDefault(true) + ->toggleable(isToggledHiddenByDefault: true) ->limit(30) ->tooltip(function ($record): ?string { $notes = optional($record)->notes ?? ''; @@ -504,31 +510,41 @@ public static function table(Table $table): Table : null; }), Tables\Columns\TextColumn::make('formFrequency.name') - ->label('Usage Frequency'), + ->label('Usage Frequency') + ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('formReach.name') - ->label('Audience Size'), + ->label('Audience Size') + ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('icm_generated') + ->label('ICM Generated') ->badge() ->formatStateUsing(fn($state) => $state ? 'Yes' : 'No') ->color(fn($state) => $state ? 'success' : 'danger') - ->label('ICM Generated'), + ->toggleable() + ->sortable() + ->searchable(), Tables\Columns\TextColumn::make('formSoftwareSources.name') + ->label('Software Sources') ->badge() - ->label('Software Sources'), + ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('formLocations.name') + ->label('Published Locations') ->badge() - ->label('Published Locations'), + ->toggleable() + ->sortable() + ->searchable(), Tables\Columns\TextColumn::make('formTags.name') + ->label('Tags') ->badge() - ->label('Tags'), + ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('dcv_material_number') + ->label('DCV Material Number') ->searchable() - ->toggleable() - ->toggledHiddenByDefault(true) - ->label('DCV Material Number'), + ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('migration2025_status') ->label('Migration 2025 Status') ->badge() + ->toggleable() ->getStateUsing(fn($record) => $record->migration2025_status) ->color(function ($state) { return match ($state) { diff --git a/app/Filament/Forms/Resources/FormResource/Pages/ViewForm.php b/app/Filament/Forms/Resources/FormResource/Pages/ViewForm.php index 29499d3a..afa48d63 100644 --- a/app/Filament/Forms/Resources/FormResource/Pages/ViewForm.php +++ b/app/Filament/Forms/Resources/FormResource/Pages/ViewForm.php @@ -63,14 +63,22 @@ protected function getHeaderActions(): array $actions = []; if (Gate::allows('admin') || Gate::allows('form-developer')) { - $actions[] = Actions\EditAction::make(); + $actions[] = Actions\EditAction::make() + ->outlined() + ->icon('heroicon-s-pencil-square'); if ($hasVersions) { $actions[] = Actions\Action::make('view_latest_version') ->label('View latest version') - ->icon('heroicon-o-document-text') + ->icon('heroicon-s-eye') ->url(fn() => FormVersionResource::getUrl('view', ['record' => $latestVersion])) + ->color('gray') ->outlined(); + + $actions[] = Actions\Action::make('build_latest_version') + ->label('Build latest version') + ->icon('heroicon-s-wrench-screwdriver') + ->url(fn() => FormVersionResource::getUrl('build', ['record' => $latestVersion])); } } else { // For regular users, show preview button if versions exist diff --git a/app/Filament/Forms/Resources/FormResource/RelationManagers/FormVersionRelationManager.php b/app/Filament/Forms/Resources/FormResource/RelationManagers/FormVersionRelationManager.php index 38f6c083..defc744c 100644 --- a/app/Filament/Forms/Resources/FormResource/RelationManagers/FormVersionRelationManager.php +++ b/app/Filament/Forms/Resources/FormResource/RelationManagers/FormVersionRelationManager.php @@ -7,15 +7,7 @@ use Filament\Tables; use Illuminate\Support\Facades\Gate; use App\Filament\Forms\Resources\FormVersionResource; -use App\Helpers\FormVersionHelper; use Filament\Forms\Components\DatePicker; -use App\Models\FormBuilding\FormScript; -use App\Models\FormBuilding\StyleSheet; -use App\Models\FormBuilding\FormVersionFormDataSource; -use App\Models\FormBuilding\FormElementDataBinding; -use Illuminate\Support\Facades\Auth; -use Filament\Tables\Actions\Action; -use Illuminate\Support\Str; class FormVersionRelationManager extends RelationManager { @@ -38,7 +30,10 @@ public function table(Tables\Table $table): Tables\Table ->sortable(), Tables\Columns\TextColumn::make('formatted_status') ->label('Status') - ->getStateUsing(fn($record) => $record->getFormattedStatusName()), + ->badge() + ->color(fn($state) => FormVersion::getStatusColour($state)) + ->getStateUsing(fn($record) => $record->getFormattedStatusName()) + ->sortable(), Tables\Columns\TextColumn::make('formDeveloper.name') ->label('Developer') ->sortable(), @@ -86,6 +81,10 @@ public function table(Tables\Table $table): Tables\Table Tables\Actions\EditAction::make() ->url(fn(FormVersion $record) => FormVersionResource::getUrl('edit', ['record' => $record])) ->visible(fn($record) => (in_array($record->status, ['draft', 'testing'])) && Gate::allows('form-developer')), + Tables\Actions\Action::make('Build') + ->url(fn(FormVersion $record) => FormVersionResource::getUrl('build', ['record' => $record])) + ->visible(fn($record) => (in_array($record->status, ['draft', 'testing'])) && Gate::allows('form-developer')) + ->icon('heroicon-s-wrench-screwdriver'), // Duplicate form version currently disabled due to ADO bugs 3302 and 3303 // Action::make('duplicate') // ->label('Duplicate') diff --git a/app/Filament/Forms/Resources/FormVersionResource.php b/app/Filament/Forms/Resources/FormVersionResource.php index 16af2349..8cbec8fb 100644 --- a/app/Filament/Forms/Resources/FormVersionResource.php +++ b/app/Filament/Forms/Resources/FormVersionResource.php @@ -40,7 +40,7 @@ class FormVersionResource extends Resource protected static ?string $navigationIcon = 'heroicon-o-inbox-stack'; - protected static bool $shouldRegisterNavigation = false; + protected static bool $shouldRegisterNavigation = true; public static function getEloquentQuery(): Builder { @@ -130,8 +130,8 @@ public static function form(Form $form): Form ->itemLabel( fn(array $state): ?string => isset($state['form_data_source_id']) - ? FormDataSource::find($state['form_data_source_id'])?->name ?? 'New Data Source' - : 'New Data Source' + ? FormDataSource::find($state['form_data_source_id'])?->name ?? 'New Data Source' + : 'New Data Source' ) ->addActionLabel('Add Data Source') ->collapsed() @@ -315,13 +315,13 @@ public static function table(Table $table): Table ]) ->filters([ SelectFilter::make('status') - ->options([ - 'draft' => 'Draft', - 'under_review' => 'Under Review', - 'approved' => 'Approved', - 'published' => 'Published', - 'archived' => 'Archived', - ]), + ->options([ + 'draft' => 'Draft', + 'under_review' => 'Under Review', + 'approved' => 'Approved', + 'published' => 'Published', + 'archived' => 'Archived', + ]), TrashedFilter::make() ->visible(fn() => Gate::allows('admin')), ]) @@ -329,102 +329,106 @@ public static function table(Table $table): Table ViewAction::make(), EditAction::make() ->visible(fn($record) => (in_array($record->status, ['draft', 'testing'])) && Gate::allows('form-developer')), - Action::make('duplicate') - ->label('Duplicate') - ->icon('heroicon-o-document-duplicate') - ->color('info') + Action::make('Build') + ->url(fn(FormVersion $record) => FormVersionResource::getUrl('build', ['record' => $record])) ->visible(fn($record) => (in_array($record->status, ['draft', 'testing'])) && Gate::allows('form-developer')) - ->action(function ($record) { - // Create a new version with incremented version number - $newVersion = $record->replicate(['version_number', 'status', 'created_at', 'updated_at']); - $newVersion->version_number = FormVersion::where('form_id', $record->form_id)->max('version_number') + 1; - $newVersion->status = 'draft'; - $newVersion->form_developer_id = Auth::id(); - $newVersion->comments = 'Duplicated from version ' . $record->version_number; - $newVersion->save(); - - // Duplicate all FormElements and map new to old - $oldToNewElementMap = []; - foreach ($record->formElements()->orderBy('order')->get() as $element) { - $newElement = $element->replicate(['id', 'form_version_id', 'parent_id', 'created_at', 'updated_at']); - $newElement->form_version_id = $newVersion->id; - $newElement->parent_id = null; - $newElement->save(); - - // Map old element ID to new element for parent relationship updates - $oldToNewElementMap[$element->id] = [ - 'new_element' => $newElement, - 'old_parent_id' => $element->parent_id - ]; - - // Attach tags - $newElement->tags()->attach($element->tags->pluck('id')); - - // Duplicate data bindings - foreach ($element->dataBindings as $dataBinding) { - \App\Models\FormBuilding\FormElementDataBinding::create([ - 'form_element_id' => $newElement->id, - 'form_data_source_id' => $dataBinding->form_data_source_id, - 'path' => $dataBinding->path, - 'condition' => $dataBinding->condition, - 'order' => $dataBinding->order, - ]); - } - - // Duplicate polymorphic elementable and link to new element - if ($element->elementable) { - $elementableData = $element->elementable->getData(); - - // Filter out null and empty string values to let model defaults apply - $filteredData = array_filter($elementableData, function ($value) { - return $value !== null && $value !== ''; - }); - - $newElementable = $element->elementable_type::create($filteredData); - $newElement->update(['elementable_id' => $newElementable->id]); - } - } - - // Update parent_id relationships for nested elements - foreach ($oldToNewElementMap as $data) { - if ($data['old_parent_id'] && isset($oldToNewElementMap[$data['old_parent_id']])) { - $data['new_element']->update([ - 'parent_id' => $oldToNewElementMap[$data['old_parent_id']]['new_element']->id - ]); - } - } - - // Duplicate related models using a helper method - FormVersionHelper::duplicateRelatedModels($record->id, $newVersion->id, StyleSheet::class); - FormVersionHelper::duplicateRelatedModels($record->id, $newVersion->id, FormScript::class); - - // Duplicate form data sources with their order - foreach ($record->formVersionFormDataSources as $formDataSource) { - \App\Models\FormBuilding\FormVersionFormDataSource::create([ - 'form_version_id' => $newVersion->id, - 'form_data_source_id' => $formDataSource->form_data_source_id, - 'order' => $formDataSource->order, - ]); - } - - // Duplicate form interfaces - foreach ($record->formVersionFormInterfaces as $formInterface) { - \App\Models\FormBuilding\FormVersionFormInterface::create([ - 'form_version_id' => $newVersion->id, - 'form_interface_id' => $formInterface->form_interface_id, - 'order' => $formInterface->order, - ]); - } - - // Redirect to build the new version - if (Gate::allows('form-developer')) { - return redirect()->to('/forms/form-versions/' . $newVersion->id . '/build'); - } else { - return redirect()->to(FormVersionResource::getUrl('view', ['record' => $newVersion])); - } - }) - ->requiresConfirmation() - ->modalDescription('This will create a new draft version based on this form version, including all form elements.'), + ->icon('heroicon-s-wrench-screwdriver'), + // Action::make('duplicate') + // ->label('Duplicate') + // ->icon('heroicon-o-document-duplicate') + // ->color('info') + // ->visible(fn($record) => (in_array($record->status, ['draft', 'testing'])) && Gate::allows('form-developer')) + // ->action(function ($record) { + // // Create a new version with incremented version number + // $newVersion = $record->replicate(['version_number', 'status', 'created_at', 'updated_at']); + // $newVersion->version_number = FormVersion::where('form_id', $record->form_id)->max('version_number') + 1; + // $newVersion->status = 'draft'; + // $newVersion->form_developer_id = Auth::id(); + // $newVersion->comments = 'Duplicated from version ' . $record->version_number; + // $newVersion->save(); + // + // // Duplicate all FormElements and map new to old + // $oldToNewElementMap = []; + // foreach ($record->formElements()->orderBy('order')->get() as $element) { + // $newElement = $element->replicate(['id', 'form_version_id', 'parent_id', 'created_at', 'updated_at']); + // $newElement->form_version_id = $newVersion->id; + // $newElement->parent_id = null; + // $newElement->save(); + // + // // Map old element ID to new element for parent relationship updates + // $oldToNewElementMap[$element->id] = [ + // 'new_element' => $newElement, + // 'old_parent_id' => $element->parent_id + // ]; + // + // // Attach tags + // $newElement->tags()->attach($element->tags->pluck('id')); + // + // // Duplicate data bindings + // foreach ($element->dataBindings as $dataBinding) { + // \App\Models\FormBuilding\FormElementDataBinding::create([ + // 'form_element_id' => $newElement->id, + // 'form_data_source_id' => $dataBinding->form_data_source_id, + // 'path' => $dataBinding->path, + // 'condition' => $dataBinding->condition, + // 'order' => $dataBinding->order, + // ]); + // } + // + // // Duplicate polymorphic elementable and link to new element + // if ($element->elementable) { + // $elementableData = $element->elementable->getData(); + // + // // Filter out null and empty string values to let model defaults apply + // $filteredData = array_filter($elementableData, function ($value) { + // return $value !== null && $value !== ''; + // }); + // + // $newElementable = $element->elementable_type::create($filteredData); + // $newElement->update(['elementable_id' => $newElementable->id]); + // } + // } + // + // // Update parent_id relationships for nested elements + // foreach ($oldToNewElementMap as $data) { + // if ($data['old_parent_id'] && isset($oldToNewElementMap[$data['old_parent_id']])) { + // $data['new_element']->update([ + // 'parent_id' => $oldToNewElementMap[$data['old_parent_id']]['new_element']->id + // ]); + // } + // } + // + // // Duplicate related models using a helper method + // FormVersionHelper::duplicateRelatedModels($record->id, $newVersion->id, StyleSheet::class); + // FormVersionHelper::duplicateRelatedModels($record->id, $newVersion->id, FormScript::class); + // + // // Duplicate form data sources with their order + // foreach ($record->formVersionFormDataSources as $formDataSource) { + // \App\Models\FormBuilding\FormVersionFormDataSource::create([ + // 'form_version_id' => $newVersion->id, + // 'form_data_source_id' => $formDataSource->form_data_source_id, + // 'order' => $formDataSource->order, + // ]); + // } + // + // // Duplicate form interfaces + // foreach ($record->formVersionFormInterfaces as $formInterface) { + // \App\Models\FormBuilding\FormVersionFormInterface::create([ + // 'form_version_id' => $newVersion->id, + // 'form_interface_id' => $formInterface->form_interface_id, + // 'order' => $formInterface->order, + // ]); + // } + // + // // Redirect to build the new version + // if (Gate::allows('form-developer')) { + // return redirect()->to('/forms/form-versions/' . $newVersion->id . '/build'); + // } else { + // return redirect()->to(FormVersionResource::getUrl('view', ['record' => $newVersion])); + // } + // }) + // ->requiresConfirmation() + // ->modalDescription('This will create a new draft version based on this form version, including all form elements.'), Action::make('archive') ->label('Archive') ->icon('heroicon-o-archive-box-arrow-down') @@ -449,7 +453,8 @@ public static function table(Table $table): Table 25, 50, 100, - ]);; + ]); + ; } public static function getRelations(): array diff --git a/app/Filament/Forms/Resources/FormVersionResource/Pages/BuildFormVersion.php b/app/Filament/Forms/Resources/FormVersionResource/Pages/BuildFormVersion.php index ed5a7635..a25b38de 100644 --- a/app/Filament/Forms/Resources/FormVersionResource/Pages/BuildFormVersion.php +++ b/app/Filament/Forms/Resources/FormVersionResource/Pages/BuildFormVersion.php @@ -310,14 +310,14 @@ protected function getHeaderActions(): array // Refresh the page to update the tree $this->redirect($this->getResource()::getUrl('build', ['record' => $this->record])); } catch (\InvalidArgumentException $e) { - \Filament\Notifications\Notification::make() + Notification::make() ->danger() ->title('Cannot Create Element') ->body($e->getMessage()) ->persistent() ->send(); } catch (\Exception $e) { - \Filament\Notifications\Notification::make() + Notification::make() ->danger() ->title('Error Creating Element') ->body('An unexpected error occurred: ' . $e->getMessage()) @@ -394,10 +394,8 @@ protected function getHeaderActions(): array }), ActionGroup::make([ - $this->makeDownloadJsonAction('download_old_json', 'Download v1', 1), - $this->makeDownloadJsonAction('download_json', 'Download v2', 2), - $this->makeCopyJsonAction('copy_json_v1', 'Copy v1 to Clipboard', 1), - $this->makeCopyJsonAction('copy_json_v2', 'Copy v2 to Clipboard', 2), + $this->makeDownloadJsonAction('download_json', 'Download', 2), + $this->makeCopyJsonAction('copy_json_v2', 'Copy to Clipboard', 2), ]) ->label('Download JSON') ->icon('heroicon-m-ellipsis-vertical') @@ -532,8 +530,8 @@ protected function handleValidationFailures(array $issues): void $bodyHtml = new HtmlString( '
' - . nl2br(e($payload)) . - '
' + . nl2br(e($payload)) . + '' ); Notification::make() @@ -594,7 +592,7 @@ public function save(): void { // Prevent saving if not editable if (!$this->isEditable()) { - \Filament\Notifications\Notification::make() + Notification::make() ->warning() ->title('Cannot Save Changes') ->body('Form versions can only be saved when in draft status.') @@ -629,9 +627,9 @@ public function save(): void $this->getSavedNotification()?->send(); } - protected function getSavedNotification(?string $message = null): ?\Filament\Notifications\Notification + protected function getSavedNotification(?string $message = null): ?Notification { - return \Filament\Notifications\Notification::make() + return Notification::make() ->success() ->title('Saved') ->body($message ?? 'The form builder changes have been saved successfully.'); @@ -770,7 +768,7 @@ public function triggerUpdateEvent(string $updateType = 'manual'): void 'js_content_pdf' => $data['js_content_pdf'] ?? '', ], $updateType, false); - \Filament\Notifications\Notification::make() + Notification::make() ->success() ->title('Update Broadcasted') ->body('Form version update has been broadcasted to all connected clients.') @@ -1064,8 +1062,8 @@ private function showFieldIssuesNotification(array $issues): void $bodyHtml = new HtmlString( '
' - . nl2br(e($payload)) - . '
' + . nl2br(e($payload)) + . '' ); Notification::make() @@ -1083,7 +1081,7 @@ public function importParsedSchemaElements() { $schemaContent = $this->importWizard['schema_content'] ?? null; if (empty($schemaContent)) { - \Filament\Notifications\Notification::make() + Notification::make() ->danger() ->title('No Parsed Schema') ->body('No schema content found to import.') @@ -1110,7 +1108,7 @@ public function importParsedSchemaElements() 'cacheKey' => $cacheKey, ]; - \Filament\Notifications\Notification::make() + Notification::make() ->info() ->title('Import Started') ->body('The import is being processed in the background. This page will refresh when it is complete.') @@ -1135,7 +1133,7 @@ public function pollImportStatus() session()->forget('import_job_cache_key'); $this->importJobStatus['done'] = true; $this->importJobStatus['status'] = 'complete'; - \Filament\Notifications\Notification::make() + Notification::make() ->success() ->title('Import Complete') ->body('Form elements have been successfully imported. The page will refresh to show the new elements.') @@ -1151,7 +1149,7 @@ public function pollImportStatus() $this->importJobStatus['done'] = true; $this->importJobStatus['status'] = 'error'; - \Filament\Notifications\Notification::make() + Notification::make() ->danger() ->title('Import Failed') ->body($error ?: 'An error occurred during import.') diff --git a/app/Filament/Forms/Resources/StyleSheetResource.php b/app/Filament/Forms/Resources/StyleSheetResource.php index 1d56a118..44329984 100644 --- a/app/Filament/Forms/Resources/StyleSheetResource.php +++ b/app/Filament/Forms/Resources/StyleSheetResource.php @@ -90,7 +90,7 @@ public static function form(Form $form): Form ), CustomMonacoEditor::make('content') - ->label('Script Content') + ->label('Style Sheet Content') ->language('css') ->theme('vs-dark') ->live() diff --git a/app/Filament/Forms/Resources/UserTypeResource.php b/app/Filament/Forms/Resources/UserTypeResource.php index d4b8afc8..53f6683c 100644 --- a/app/Filament/Forms/Resources/UserTypeResource.php +++ b/app/Filament/Forms/Resources/UserTypeResource.php @@ -3,15 +3,12 @@ namespace App\Filament\Forms\Resources; use App\Filament\Forms\Resources\UserTypeResource\Pages; -use App\Filament\Forms\Resources\UserTypeResource\RelationManagers; -use App\Models\UserType; +use App\Models\FormMetadata\UserType; use Filament\Forms; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\SoftDeletingScope; class UserTypeResource extends Resource { diff --git a/app/Helpers/GeneralTabHelper.php b/app/Helpers/GeneralTabHelper.php index d328b911..15c31d24 100644 --- a/app/Helpers/GeneralTabHelper.php +++ b/app/Helpers/GeneralTabHelper.php @@ -63,14 +63,51 @@ public static function getGeneralTabSchema( // Help text field $schema[] = self::makeHelpTextField($disabled, $disabledCallback, $shouldShowTooltipsCallback); - // Visibility grid - $schema[] = self::makeVisibilityGrid($disabled, $disabledCallback); - - // Required grid - $schema[] = self::makeRequiredGrid($disabled, $disabledCallback); + // Visibility button groups + $schema[] = Grid::make(2) + ->schema([ + self::makeToggleButton( + field: 'visible_web', + label: 'Visible on Web', + tooltip: 'Control when this element is visible on web forms', + default: 'always', + disabled: $disabled, + disabledCallback: $disabledCallback, + shouldShowTooltipsCallback: $shouldShowTooltipsCallback, + ), + self::makeToggleButton( + field: 'visible_pdf', + label: 'Visible on PDF', + tooltip: 'Control when this element is visible on PDF forms', + default: 'always', + disabled: $disabled, + disabledCallback: $disabledCallback, + shouldShowTooltipsCallback: $shouldShowTooltipsCallback, + ), + ]); - // Read Only grid - $schema[] = self::makeReadOnlyGrid($disabled, $disabledCallback); + // Required and read only button groups + $schema[] = Grid::make(2) + ->schema([ + self::makeToggleButton( + field: 'is_required', + label: 'Required', + tooltip: 'Control when this element is required', + default: 'never', + disabled: $disabled, + disabledCallback: $disabledCallback, + shouldShowTooltipsCallback: $shouldShowTooltipsCallback, + ), + self::makeToggleButton( + field: 'is_read_only', + label: 'Read-Only', + tooltip: 'Control when this element is read-only', + default: 'never', + disabled: $disabled, + disabledCallback: $disabledCallback, + shouldShowTooltipsCallback: $shouldShowTooltipsCallback, + ) + ]); // Template and Save On Submit toggles $schema[] = Grid::make(2) @@ -133,12 +170,10 @@ private static function makeTemplateField(?callable $shouldShowTooltipsCallback) $set('description', $template->description); $set('help_text', $template->help_text); $set('elementable_type', $template->elementable_type); - $set('is_required_toggle', $template->is_required !== null && $template->is_required !== ''); - $set('is_required', $template->is_required); - $set('is_read_only_toggle', $template->is_read_only !== null && $template->is_read_only !== ''); - $set('is_read_only', $template->is_read_only); $set('visible_web', $template->visible_web); $set('visible_pdf', $template->visible_pdf); + $set('is_required', $template->is_required); + $set('is_read_only', $template->is_read_only); $set('is_template', false); // New element should not be a template by default // Prefill tags @@ -420,104 +455,39 @@ private static function makeHelpTextField( ); } - private static function makeVisibilityGrid(bool $disabled, ?callable $disabledCallback): Component - { - return Grid::make(2) - ->schema([ - Toggle::make('visible_web') - ->label('Visible on Web') - ->default(true) - ->disabled($disabled || ($disabledCallback && $disabledCallback())), - Toggle::make('visible_pdf') - ->label('Visible on PDF') - ->default(true) - ->disabled($disabled || ($disabledCallback && $disabledCallback())), - ]); - } - - private static function makeRequiredGrid(bool $disabled, ?callable $disabledCallback): Component - { - $toggle = Toggle::make('is_required_toggle') - ->label('Is Required') - ->default(false) - ->live() - ->disabled($disabled || ($disabledCallback && $disabledCallback())) - ->afterStateHydrated(function (Toggle $component, callable $set, callable $get) { - $isRequired = $get('is_required'); - // Set toggle to true if is_required has any non-null value ('always' or 'portal') - if ($isRequired !== null && $isRequired !== '') { - $set('is_required_toggle', true); - } - }); - - $buttons = ToggleButtons::make('is_required') - ->label('Required When') - ->options([ - 'always' => 'Always', - 'portal' => 'On Portal Forms' - ]) - ->default('always') - ->inline() - ->disabled(fn($get) => !$get('is_required_toggle')) - ->afterStateHydrated(function (callable $set, callable $get) { - $value = $get('is_required'); - if ($value === null || $value === '') { - $set('is_required', 'always'); - } - }); - - return Grid::make(2) - ->schema([ - $toggle, - $buttons, - ]); - } - - private static function makeReadOnlyGrid(bool $disabled, ?callable $disabledCallback): Component - { - $toggle = Toggle::make('is_read_only_toggle') - ->label('Is Read Only') - ->default(false) - ->live() - ->disabled($disabled || ($disabledCallback && $disabledCallback())) - ->afterStateHydrated(function (Toggle $component, callable $set, callable $get) { - $isReadOnly = $get('is_read_only'); - // Set toggle to true if is_read_only has any non-null value ('always' or 'portal') - if ($isReadOnly !== null && $isReadOnly !== '') { - $set('is_read_only_toggle', true); - } - }); - - $buttons = ToggleButtons::make('is_read_only') - ->label('Read Only When') - ->options([ - 'always' => 'Always', - 'portal' => 'On Portal Forms' - ]) - ->default('always') + /** + * Make ToggleButton group for selecting visibility, required, and read-only states + * @param string $field + * @param string $label + * @param string $tooltip + * @param string $default + * @param bool $disabled + * @param mixed $disabledCallback + * @param mixed $shouldShowTooltipsCallback + * @return Component + */ + private static function makeToggleButton( + string $field, + string $label, + string $tooltip = '', + string $default = '', + bool $disabled = false, + ?callable $disabledCallback = null, + ?callable $shouldShowTooltipsCallback = null, + ): Component { + $buttonGroup = ToggleButtons::make($field) + ->label($label) + ->options(FormElement::getToggleButtonStates()) + ->default($default) ->inline() - ->disabled(fn($get) => !$get('is_read_only_toggle')) - ->afterStateHydrated(function (callable $set, callable $get) { - $value = $get('is_read_only'); - if ($value === null || $value === '') { - $set('is_read_only', 'always'); - } - }); - - $customScript = TextArea::make('custom_read_only') - ->label('Custom Read Only Script') - ->visible(fn($get) => $get('is_read_only')) - ->reactive() - ->disabled(fn($get) => !$get('is_read_only_toggle')) - ->hintIcon('heroicon-m-question-mark-circle', tooltip: 'Custom read only script to control when this element is read only. Use the format: "if (condition) { return true; } else { return false; }". This will be evaluated in the browser.') - ->columnSpanFull(); + ->grouped() + ->disabled($disabled || ($disabledCallback && $disabledCallback())); - return Grid::make(2) - ->schema([ - $toggle, - $buttons, - $customScript - ]); + return self::withOptionalTooltip( + $buttonGroup, + $shouldShowTooltipsCallback, + $tooltip, + ); } private static function makeTemplateToggle( diff --git a/app/Helpers/SchemaHelper.php b/app/Helpers/SchemaHelper.php index b71a76d8..0dbdc15d 100644 --- a/app/Helpers/SchemaHelper.php +++ b/app/Helpers/SchemaHelper.php @@ -5,6 +5,8 @@ use Filament\Forms\Components\Fieldset; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; +use Filament\Forms\Components\Repeater; +use Filament\Forms\Components\Select; use Filament\Forms\Components\Actions\Action; class SchemaHelper @@ -13,36 +15,18 @@ class SchemaHelper * Get the common Carbon Design System fields for form elements * * @param bool $disabled Whether the fields should be disabled + * @param bool $labelRequired Whether the label text field should be required * @return array Array of Filament form components */ - public static function getCommonCarbonFields(bool $disabled = false): array + public static function getCommonCarbonFields(bool $disabled = false, bool $labelRequired = false): Fieldset { - return [ - Fieldset::make('Field Label') - ->schema([ - TextInput::make('elementable_data.labelText') - ->label('Field Label') - ->disabled($disabled) - ->autocomplete(false) - ->suffixAction( - Action::make('generate_label_text') - ->icon('heroicon-o-arrow-path') - ->tooltip('Regenerate from Element Name') - ->action(function (callable $set, callable $get) { - $name = $get('name'); - if (!empty($name)) { - $set('elementable_data.labelText', $name); - } - }), - ), - SchemaHelper::getEnableVariableSubstitutionToggle($disabled), - Toggle::make('elementable_data.hideLabel') - ->label('Hide Label') - ->default(false) - ->disabled($disabled), - ]) - ->columns(1), - ]; + return Fieldset::make('Field Label') + ->schema([ + self::getLabelTextField($disabled, $labelRequired), + self::getEnableVariableSubstitutionToggle($disabled), + self::getHideLabelToggle($disabled), + ]) + ->columns(1); } /** @@ -53,13 +37,17 @@ public static function getPlaceholderTextField(bool $disabled = false) return TextInput::make('elementable_data.placeholder') ->label('Placeholder Text') ->autocomplete(false) + ->maxLength(255) ->disabled($disabled); } - public static function getLabelTextField(bool $disabled = false) + public static function getLabelTextField(bool $disabled = false, bool $required = false) { - return TextInput::make('elementable_data.labelText') + return TextInput::make('elementable_data.label_text') ->label('Field Label') + ->maxLength(255) + ->disabled($disabled) + ->required($required) ->suffixAction( Action::make('generate_label_text') ->icon('heroicon-o-arrow-path') @@ -67,36 +55,98 @@ public static function getLabelTextField(bool $disabled = false) ->action(function (callable $set, callable $get) { $name = $get('name'); if (!empty($name)) { - $set('elementable_data.labelText', $name); + $set('elementable_data.label_text', $name); } }), - ) - ->disabled($disabled); + ); } public static function getHideLabelToggle(bool $disabled = false) { - return Toggle::make('elementable_data.hideLabel') + return Toggle::make('elementable_data.hide_label') ->label('Hide Label') ->default(false) ->live() ->disabled($disabled); } - public static function getPlaceholderField(bool $disabled = false) - { - return TextInput::make('elementable_data.placeholder') - ->label('Placeholder Text') - ->disabled($disabled); - } - public static function getEnableVariableSubstitutionToggle(bool $disabled = false) { - return Toggle::make('elementable_data.enableVarSub') + return Toggle::make('elementable_data.enable_var_sub') ->label('Enable Variable Substitution') ->helperText('Use {{variableName}} syntax in the label to dynamically insert values from other form fields. Make sure you also insert the Moustache library in the Scripts tab and register the variable.') ->default(false) ->disabled($disabled); } + + public static function getOptionsRepeater(bool $disabled = false, string $label = 'Options'): Repeater + { + return Repeater::make('elementable_data.options') + ->label($label) + ->schema([ + TextInput::make('label') + ->label('Option Label') + ->required() + ->maxLength(255) + ->columnSpan(2) + ->autocomplete(false) + ->live(onBlur: true) + ->afterStateUpdated(function (callable $set, callable $get, $state) { + $value = $get('value'); + if (empty($value) && !empty($state)) { + $slug = \Illuminate\Support\Str::slug($state, '-'); + $set('value', $slug); + } + }), + TextInput::make('value') + ->label('Option Value') + ->required() + ->maxLength(255) + ->columnSpan(2) + ->suffixAction( + Action::make('regenerate_value') + ->icon('heroicon-o-arrow-path') + ->tooltip('Regenerate from Option Label') + ->action(function (callable $set, callable $get) { + $label = $get('label'); + if (!empty($label)) { + $slug = \Illuminate\Support\Str::slug($label, '-'); + $set('value', $slug); + } + }) + ), + ]) + ->columns(2) + ->defaultItems(1) + ->addActionLabel('Add Option') + ->reorderableWithButtons() + ->collapsible() + ->itemLabel(fn(array $state): ?string => $state['label'] ?? 'Option') + ->disabled($disabled) + ->minItems(1); + } + + public static function getOptionsDefaultSelectedSelect(bool $disabled = false, bool $multiple = false, string $label = 'Default Selected Value'): Select + { + $select = Select::make('elementable_data.default_selected') + ->label($label) + ->options(function (callable $get) { + $options = $get('elementable_data.options') ?? []; + $selectOptions = []; + foreach ($options as $option) { + if (!empty($option['value'])) { + $selectOptions[$option['value']] = $option['label'] ?? $option['value']; + } + } + return $selectOptions; + }) + ->disabled($disabled); + + if ($multiple) { + $select->multiple()->nullable()->live(); + } + + return $select; + } } diff --git a/app/Jobs/GenerateFormVersionJsonJob.php b/app/Jobs/GenerateFormVersionJsonJob.php index 5db79213..93b73849 100644 --- a/app/Jobs/GenerateFormVersionJsonJob.php +++ b/app/Jobs/GenerateFormVersionJsonJob.php @@ -2,7 +2,10 @@ namespace App\Jobs; +use App\Models\FormBuilding\CheckboxGroupFormElement; use App\Models\FormBuilding\FormVersion; +use App\Models\FormBuilding\RadioInputFormElement; +use App\Models\FormBuilding\SelectInputFormElement; use App\Services\FormVersionJsonService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -23,7 +26,8 @@ public function __construct( public FormVersion $formVersion, public int $userId, public int $version = 2 - ) {} + ) { + } public function handle(): void { @@ -34,9 +38,9 @@ public function handle(): void 'form', 'formElements.elementable' => function ($morphTo) { $morphTo->morphWith([ - \App\Models\FormBuilding\SelectInputFormElement::class => ['options'], - \App\Models\FormBuilding\RadioInputFormElement::class => ['options'], - \App\Models\FormBuilding\CheckboxGroupFormElement::class => ['options'], + SelectInputFormElement::class => ['options'], + RadioInputFormElement::class => ['options'], + CheckboxGroupFormElement::class => ['options'], ]); }, 'formElements.dataBindings.formDataSource', @@ -65,9 +69,11 @@ public function handle(): void } // Create filename with form title and version + $form_id = $this->formVersion->form->form_id; + $versionNumber = $this->formVersion->version_number; $formTitle = $this->formVersion->form->form_title ?? 'Unknown Form'; $sanitizedTitle = preg_replace('/[^a-zA-Z0-9\-_]/', '_', $formTitle); - $filename = "form_{$sanitizedTitle}_v{$this->formVersion->version_number}_{$this->formVersion->id}_formatversion_{$this->version}.json"; + $filename = "form_{$form_id}_v{$versionNumber}_{$sanitizedTitle}.json"; // Store the JSON file $filePath = "{$filename}"; @@ -80,7 +86,7 @@ public function handle(): void Notification::make() ->success() ->title('JSON Export Complete') - ->body("Your form JSON file has been generated successfully.") + ->body("Your export {$this->formVersion->form->form_id} \"{$formTitle}\" has been generated successfully.") ->actions([ \Filament\Notifications\Actions\Action::make('download') ->label('Download JSON') diff --git a/app/Jobs/ImportFormVersionElementsJob.php b/app/Jobs/ImportFormVersionElementsJob.php index 3af95ee6..6bd06c80 100644 --- a/app/Jobs/ImportFormVersionElementsJob.php +++ b/app/Jobs/ImportFormVersionElementsJob.php @@ -13,9 +13,23 @@ use Illuminate\Foundation\Bus\Dispatchable; use App\Models\FormBuilding\FormElement; use App\Events\FormVersionUpdateEvent; +use App\Models\FormBuilding\ButtonInputFormElement; +use App\Models\FormBuilding\CheckboxGroupFormElement; +use App\Models\FormBuilding\CheckboxInputFormElement; +use App\Models\FormBuilding\ContainerFormElement; +use App\Models\FormBuilding\CurrencyInputFormElement; +use App\Models\FormBuilding\DateSelectInputFormElement; use App\Models\FormBuilding\FormElementDataBinding; use App\Models\FormBuilding\FormScript; +use App\Models\FormBuilding\HTMLFormElement; +use App\Models\FormBuilding\NumberInputFormElement; +use App\Models\FormBuilding\RadioInputFormElement; +use App\Models\FormBuilding\SelectInputFormElement; +use App\Models\FormBuilding\SelectOptionFormElement; use App\Models\FormBuilding\StyleSheet; +use App\Models\FormBuilding\TextareaInputFormElement; +use App\Models\FormBuilding\TextInfoFormElement; +use App\Models\FormBuilding\TextInputFormElement; use App\Models\FormMetadata\FormDataSource; use Filament\Notifications\Notification; @@ -58,7 +72,7 @@ public function handle() // Normalize format $normalizedSchema = $this->normalizeSchema($parsed); - // Process data sources, javascript, and stylesheets + // Process data sources, javascript, and stylesheets $this->processDataSources($normalizedSchema, $formVersion); $this->processJavaScript($normalizedSchema, $formVersion); $this->processStyleSheets($normalizedSchema, $formVersion); @@ -80,6 +94,40 @@ public function handle() Cache::put($this->cacheKey . '_progress', "Completed: {$processedElements}/{$totalElements} elements", 3600); Cache::put($this->cacheKey . '_status', 'complete', 3600); + // Check for template conflicts and send database notification + $templateConflicts = Cache::get($this->cacheKey . '_template_conflicts', []); + if (!empty($templateConflicts)) { + $user = \App\Models\User::find($this->userId); + if ($user) { + $filenames = array_column($templateConflicts, 'filename'); + $conflictList = implode(', ', $filenames); + $count = count($templateConflicts); + // Construct action buttons for notification + $actions = []; + foreach ($templateConflicts as $conflict) { + // Choose the route based on the template type + $routeName = $conflict['type'] === 'stylesheet' + ? 'filament.forms.resources.style-sheets.view' + : 'filament.forms.resources.form-scripts.view'; + + $actions[] = \Filament\Notifications\Actions\Action::make('view_template_' . $conflict['id']) + ->button() + ->label("View '{$conflict['filename']}'") + ->url(route($routeName, ['record' => $conflict['id']])); + } + + Notification::make() + ->title('Template Conflicts Detected') + ->warning() + ->body( + "During import of {$formVersion->form->form_id} version {$formVersion->version_number}, {$count} template(s) already existed with different content. " + . "The existing versions were kept: {$conflictList}" + ) + ->actions($actions) + ->sendToDatabase($user); + } + } + FormVersionUpdateEvent::dispatch( $formVersion->id, $formVersion->form_id, @@ -95,182 +143,80 @@ public function handle() } /** - * Normalize different schema formats into a consistent structure + * Normalize the schema (Format 1 only - formversion structure) + * Format 2 and 3 are not supported */ private function normalizeSchema(array $parsed): array { - // Format 1: formversion structure - if (isset($parsed['formversion'])) { - return [ - 'elements' => $parsed['formversion']['elements'] ?? [], - 'dataSources' => $parsed['formversion']['dataSources'] ?? [], - 'javascript' => $this->extractJavaScriptFromFormversion($parsed['formversion']), - 'stylesheets' => $this->extractStyleSheetsFromFormversion($parsed['formversion']), - ]; + if (!isset($parsed['formversion'])) { + throw new \Exception("Only 'formversion' format is supported"); } - // Format 2: data structure - if (isset($parsed['data'])) { - $data = $parsed['data']; - - // Accept both "elements" and "items" - $elements = $data['elements'] ?? $data['items'] ?? []; - - // Prefer "javascript" but gracefully convert "scripts" -> sections - $javascript = $data['javascript'] ?? []; - if ((!$javascript || !is_array($javascript)) && !empty($data['scripts']) && is_array($data['scripts'])) { - $javascript = []; - foreach ($data['scripts'] as $script) { - $type = $script['type'] ?? 'web'; - $content = $script['content'] ?? ''; - if ($content !== '') { - $javascript[$type] = ($javascript[$type] ?? ''); - $javascript[$type] .= ($javascript[$type] ? "\n" : "") . $content; - } - } - } - // Ensure styles array exists - $data['styles'] ?? $data['styles'] = []; - - return [ - 'elements' => is_array($elements) ? $elements : [], - 'dataSources' => $data['dataSources'] ?? ($parsed['dataSources'] ?? []), - 'javascript' => is_array($javascript) ? $javascript : [], - 'stylesheets' => is_array($data['styles']) ? $data['styles'] : [], - ]; - } - - // Format 3: direct structure (legacy) - if (isset($parsed['elements']) || isset($parsed['items'])) { - $elements = $parsed['elements'] ?? $parsed['items'] ?? []; - $javascript = $parsed['javascript'] ?? []; - if ((!$javascript || !is_array($javascript)) && !empty($parsed['scripts']) && is_array($parsed['scripts'])) { - $javascript = []; - foreach ($parsed['scripts'] as $script) { - $type = $script['type'] ?? 'web'; - $content = $script['content'] ?? ''; - if ($content !== '') { - $javascript[$type] = ($javascript[$type] ?? ''); - $javascript[$type] .= ($javascript[$type] ? "\n" : "") . $content; - } - } - } - - return [ - 'elements' => is_array($elements) ? $elements : [], - 'dataSources' => $parsed['dataSources'] ?? [], - 'javascript' => is_array($javascript) ? $javascript : [], - 'stylesheets' => $parsed['styles'] ?? [], - ]; - } + $formVersion = $parsed['formversion']; - Log::warning('Unknown schema format, returning empty structure'); - return ['elements' => [], 'dataSources' => [], 'javascript' => [], 'stylesheets' => []]; + return [ + 'elements' => $formVersion['elements'] ?? [], + 'dataSources' => $formVersion['dataSources'] ?? [], + 'javascript' => $this->normalizeCodeAssets($formVersion['scripts'] ?? []), + 'stylesheets' => $this->normalizeCodeAssets($formVersion['styles'] ?? []), + ]; } - /** - * Extract JavaScript from formversion format + * Normalize legacy boolean states to the new string enum states for visibility, required, and read-only fields. + * - true / 1 / '1' -> 'always' + * - false / 0 / '0' / null -> 'never' + * - existing strings ('always', 'icm', 'portal', 'never') are returned as-is */ - private function extractJavaScriptFromFormversion(array $formversion): array + private function normalizeLegacyState($value): string { - $javascript = []; - - // Check for scripts array in formversion format - if (!empty($formversion['scripts']) && is_array($formversion['scripts'])) { - foreach ($formversion['scripts'] as $script) { - $type = $script['type'] ?? 'web'; - $content = $script['content'] ?? ''; - if ($type === 'template') { - // Pass the template's filename as content so it can be attached - $content = $script['filename']; - } - if ($content !== '') { - if ($type === 'template') { - if (!array_key_exists($type, $javascript)) { - $javascript[$type] = []; - } - array_push($javascript[$type], $content); - } else { - // concatenate if multiple blocks of the same type exist - $javascript[$type] = ($javascript[$type] ?? ''); - $javascript[$type] .= ($javascript[$type] ? "\n" : "") . $content; - } - } - } + if ($value === true || $value === 1 || $value === '1') { + return 'always'; + } + + if ($value === false || $value === 0 || $value === '0' || $value === null) { + return 'never'; } - return $javascript; + return (string) $value; } /** - * Extract stylesheets from formversion format + * Normalize code assets (scripts/stylesheets) into a standard structure. + * Transforms the JSON array format into a structure that matches our storage model: + * - Web/PDF assets are concatenated into single strings + * - Script templates remain as individual objects with filename and content */ - private function extractStyleSheetsFromFormversion(array $formversion): array + private function normalizeCodeAssets(array $assets): array { - $stylesheets = []; - - // Check for styles array in formversion format - if (!empty($formversion['styles']) && is_array($formversion['styles'])) { - foreach ($formversion['styles'] as $stylesheet) { - $type = $stylesheet['type'] ?? 'web'; - $content = $stylesheet['content'] ?? ''; - if ($type === 'template') { - // Pass the template's filename as content so it can be attached - $content = $stylesheet['filename']; - } - if ($content !== '') { - if ($type === 'template') { - if (!array_key_exists($type, $stylesheets)) { - $stylesheets[$type] = []; - } - array_push($stylesheets[$type], $content); - } else { - // concatenate if multiple blocks of the same type exist - $stylesheets[$type] = ($stylesheets[$type] ?? ''); - $stylesheets[$type] .= ($stylesheets[$type] ? "\n" : "") . $content; + $normalized = []; + + foreach ($assets as $asset) { + $type = $asset['type'] ?? 'web'; + $content = $asset['content'] ?? ''; + $filename = $asset['filename'] ?? null; + + if ($type === 'template') { + // Templates need to remain as individual objects for separate file creation + if ($filename && $filename !== '') { + if (!array_key_exists($type, $normalized)) { + $normalized[$type] = []; } + $normalized[$type][] = [ + 'filename' => $filename, + 'content' => $content, + ]; } - } - } - return $stylesheets; - } - - /** - * Parse JavaScript content to extract individual sections - */ - private function parseJavaScriptSections(string $content): array - { - $sections = []; - - // Split by section comments (// Section: sectionName) - $lines = explode("\n", $content); - $currentSection = null; - $currentCode = []; - - foreach ($lines as $line) { - // Check if this is a section header - if (preg_match('/\/\/ Section: (.+)/', trim($line), $matches)) { - // Save previous section if exists - if ($currentSection && !empty($currentCode)) { - $sections[$currentSection] = implode("\n", $currentCode); + } else { + // Web/PDF assets get concatenated into a single string + if ($content !== '') { + $normalized[$type] = ($normalized[$type] ?? ''); + $normalized[$type] .= ($normalized[$type] ? "\n" : "") . $content; } - - // Start new section - $currentSection = trim($matches[1]); - $currentCode = []; - } elseif ($currentSection && trim($line) !== '') { - // Add line to current section (skip empty lines at start) - $currentCode[] = $line; } } - // Save the last section - if ($currentSection && !empty($currentCode)) { - $sections[$currentSection] = implode("\n", $currentCode); - } - - return $sections; + return $normalized; } /** @@ -303,7 +249,7 @@ private function processDataSources(array $normalizedSchema, $formVersion): void $host = $dataSourceData['host'] ?? null; // Create or find the data source - $dataSource = \App\Models\FormMetadata\FormDataSource::firstOrCreate([ + $dataSource = FormDataSource::firstOrCreate([ 'name' => $name, 'type' => $type, ], [ @@ -327,203 +273,201 @@ private function processDataSources(array $normalizedSchema, $formVersion): void } /** - * Process JavaScript from the normalized schema + * Process JavaScript from the normalized schema. Handles form scripts and templates */ private function processJavaScript(array $normalizedSchema, $formVersion): void { $javascript = $normalizedSchema['javascript'] ?? []; - if (!$javascript || !is_array($javascript)) { + if (empty($javascript)) return; - } - - // If keys look like types, emit one FormScript per type. - $knownTypes = ['web', 'pdf', 'portal', 'template']; - $typeKeys = array_intersect(array_keys($javascript), $knownTypes); - try { - if (!class_exists(FormScript::class)) { - throw new \Exception('FormScript class not found'); - } + foreach (['web', 'pdf', 'template'] as $type) { + if (!isset($javascript[$type])) + continue; - if (!empty($typeKeys)) { - foreach ($typeKeys as $t) { - if ($t === 'template') { - // Filenames are saved as content - $filenames = $javascript[$t]; - // Find template by filename and attach to formVersion - foreach ($filenames as $filename) { - $id = FormScript::where('filename', $filename)->value('id'); - $formVersion->formScripts()->syncWithoutDetaching($id); - } - } else { - $content = trim((string) ($javascript[$t] ?? '')); - FormScript::createFormScript($formVersion, $content, $t); - } + if ($type === 'template') { + foreach ($javascript[$type] as $templateData) { + $this->processTemplateScript($formVersion, $templateData); } } else { - // Fallback: treat as “sections” and combine into a single web script - $combined = "// Imported JavaScript from template\n\n"; - foreach ($javascript as $sectionName => $jsContent) { - if (!empty($jsContent)) { - $combined .= "// Section: {$sectionName}\n{$jsContent}\n\n"; - } - } - FormScript::createFormScript($formVersion, trim($combined), 'web'); + FormScript::createFormScript($formVersion, trim($javascript[$type]), $type); } - } catch (\Exception $e) { - Log::error('Failed to create JavaScript form script(s)', [ - 'form_version_id' => $formVersion->id, - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); } } /** - * Process stylesheets from the normalized schema + * Process stylesheets from the normalized schema. Handles form styles and templates */ private function processStyleSheets(array $normalizedSchema, $formVersion): void { $stylesheets = $normalizedSchema['stylesheets'] ?? []; - if (!$stylesheets || !is_array($stylesheets)) { + if (empty($stylesheets)) return; - } - // If keys look like types, emit one StyleSheet per type. - $knownTypes = ['web', 'pdf', 'portal', 'template']; - $typeKeys = array_intersect(array_keys($stylesheets), $knownTypes); + foreach (['web', 'pdf', 'template'] as $type) { + if (!isset($stylesheets[$type])) + continue; - try { - if (!class_exists(StyleSheet::class)) { - throw new \Exception('StyleSheet class not found'); - } - - if (!empty($typeKeys)) { - foreach ($typeKeys as $t) { - if ($t === 'template') { - // Filenames are saved as content - $filenames = $stylesheets[$t]; - // Find template by filename and attach to formVersion - foreach ($filenames as $filename) { - $id = StyleSheet::where('filename', $filename)->value('id'); - $formVersion->styleSheets()->syncWithoutDetaching($id); - } - } else { - $content = trim((string) ($stylesheets[$t] ?? '')); - StyleSheet::createStyleSheet($formVersion, $content, $t); - } + if ($type === 'template') { + foreach ($stylesheets[$type] as $templateData) { + $this->processTemplateStyleSheet($formVersion, $templateData); } } else { - // Fallback: treat as “sections” and combine into a single web stylesheet - $combined = "// Imported StyleSheet from template\n\n"; - foreach ($stylesheets as $sectionName => $jsContent) { - if (!empty($jsContent)) { - $combined .= "// Section: {$sectionName}\n{$jsContent}\n\n"; - } - } - StyleSheet::createStyleSheet($formVersion, trim($combined), 'web'); + StyleSheet::createStyleSheet($formVersion, trim($stylesheets[$type]), $type); } - } catch (\Exception $e) { - Log::error('Failed to create StyleSheet form stylesheet(s)', [ - 'form_version_id' => $formVersion->id, - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); } } - /** - * Return child elements for any container/group regardless of key naming. + * Process a template script as a transaction. + * Includes conflict resolution for when records exist with the same filename but different content. */ - private function getChildElements(array $element): array + private function processTemplateScript($formVersion, array $templateData): void { - $kids = $element['elements'] - ?? $element['children'] - ?? $element['containerItems'] - ?? $element['fields'] - ?? []; + $filename = $templateData['filename']; + $incomingContent = $templateData['content'] ?? ''; + + DB::transaction(function () use ($formVersion, $filename, $incomingContent) { + $existing = FormScript::where('filename', $filename)->where('type', 'template')->first(); + + if ($existing) { + // Read content from disk to compare + if ($existing->getJsContent() === $incomingContent) { + // Content matches - use existing template + $this->syncTemplate('script', $formVersion, $existing->id); + return; + } - return is_array($kids) ? $kids : []; - } + // Content differs - CONFLICT! Use existing but record the conflict + $this->syncTemplate('script', $formVersion, $existing->id); + $this->recordTemplateConflict($filename, $existing->id, 'script'); - // Add method to count total elements for progress tracking - protected function countElementsRecursive(array $elements): int - { - $count = 0; - foreach ($elements as $element) { - $count++; + Log::warning('Template script conflict detected during import', [ + 'filename' => $filename, + 'existing_template_id' => $existing->id, + 'form_version_id' => $formVersion->id, + 'message' => 'Template exists with different content. Using existing template.' + ]); + } else { + // Template doesn't exist - create it + $new = FormScript::create(['filename' => $filename, 'type' => 'template']); - $kids = $this->getChildElements($element); - if (!empty($kids)) { - $count += $this->countElementsRecursive($kids); + // Save content to disk + if (!$new->saveJsContent($incomingContent)) { + throw new \Exception('Failed to save template JS content to file'); + } + + $this->syncTemplate('script', $formVersion, $new->id); } - } - return $count; + }); } /** - * Check if element is a button type + * Process a template stylesheet as a transaction. + * Includes conflict resolution for when records exist with the same filename but different content. */ - private function isButtonElement($elementType): bool + private function processTemplateStyleSheet($formVersion, array $templateData): void { - return $elementType === \App\Models\FormBuilding\ButtonInputFormElement::class || - $elementType === 'ButtonInputFormElements' || - $elementType === 'button'; + $filename = $templateData['filename']; + $incomingContent = $templateData['content'] ?? ''; + + DB::transaction(function () use ($formVersion, $filename, $incomingContent) { + $existing = StyleSheet::where('filename', $filename)->where('type', 'template')->first(); + + if ($existing) { + // Read content from disk to compare + if ($existing->getCssContent() === $incomingContent) { + // Content matches - use existing template + $this->syncTemplate('stylesheet', $formVersion, $existing->id); + return; + } + + // Content differs - CONFLICT! Use existing but record the conflict + $this->syncTemplate('stylesheet', $formVersion, $existing->id); + $this->recordTemplateConflict($filename, $existing->id, 'stylesheet'); + + Log::warning('Template stylesheet conflict detected during import', [ + 'filename' => $filename, + 'existing_template_id' => $existing->id, + 'form_version_id' => $formVersion->id, + 'message' => 'Template exists with different content. Using existing template.' + ]); + } else { + // Template doesn't exist - create it + $new = StyleSheet::create(['filename' => $filename, 'type' => 'template']); + + // Save content to disk + if (!$new->saveCssContent($incomingContent)) { + throw new \Exception('Failed to save template CSS content to file'); + } + + $this->syncTemplate('stylesheet', $formVersion, $new->id); + } + }); } /** - * Check if button element has '+' or '-' label + * Sync a template to the form version relationship + * Encapsulates the mapping between template type and relationship method */ - private function isPlusMinusButton(array $element): bool + private function syncTemplate(string $type, $formVersion, int $templateId): void { - $label = trim($element['label'] ?? $element['name'] ?? ''); - return $label === '+' || $label === '-'; + if ($type === 'script') { + $formVersion->formScripts()->syncWithoutDetaching($templateId); + } else { + $formVersion->styleSheets()->syncWithoutDetaching($templateId); + } } /** - * Check if container element has '+' or '-' label + * Record script and stylesheet template conflicts for later notification */ - private function isPlusMinusContainer(array $element): bool + private function recordTemplateConflict(string $filename, int $existingTemplateId, string $type = 'script'): void { - $label = trim($element['label'] ?? $element['name'] ?? ''); - return $label === '+' || $label === '-'; + $conflicts = Cache::get($this->cacheKey . '_template_conflicts', []); + $ids = array_column($conflicts, 'id'); + + if (!in_array($existingTemplateId, $ids)) { + $conflicts[] = [ + 'filename' => $filename, + 'id' => $existingTemplateId, + 'type' => $type, + ]; + Cache::put($this->cacheKey . '_template_conflicts', $conflicts, 3600); + } } /** - * Check if element is a container (should not create form field) + * Return child elements for any container/group regardless of key naming. */ - private function isContainerElement(string $elementType, array $element): bool + private function getChildElements(array $element): array { - $containerTypes = [ - 'ContainerFormElements', - \App\Models\FormBuilding\ContainerFormElement::class, - 'container', - 'section', - 'group', - 'fieldset' - ]; + $kids = $element['elements'] + ?? $element['children'] + ?? $element['containerItems'] + ?? $element['fields'] + ?? []; - // Check element type directly - if (in_array($elementType, $containerTypes)) { - return true; - } + return is_array($kids) ? $kids : []; + } - // Check the actual resolved type - $resolvedType = $this->resolveElementableType($elementType); - if ($resolvedType === \App\Models\FormBuilding\ContainerFormElement::class) { - return true; - } + /** + * Count total elements recursively for progress tracking + */ + protected function countElementsRecursive(array $elements): int + { + $count = 0; + foreach ($elements as $element) { + $count++; - // Check container type property - if (isset($element['containerType'])) { - return true; + $kids = $this->getChildElements($element); + if (!empty($kids)) { + $count += $this->countElementsRecursive($kids); + } } - - return false; + return $count; } - /** * Extract options from different element formats */ @@ -531,20 +475,21 @@ private function extractOptions(array $element): array { $options = []; - // Format 1: formversion format with options array + // Handle formversion format with options array if (!empty($element['options']) && is_array($element['options'])) { - foreach ($element['options'] as $index => $option) { if (is_array($option)) { $optionData = [ 'label' => $option['label'] ?? '', + 'value' => $option['value'] ?? null, 'order' => $option['order'] ?? ($index + 1), 'description' => $option['description'] ?? null, ]; $options[] = $optionData; } else { $optionData = [ - 'label' => (string)$option, + 'label' => (string) $option, + 'value' => (string) $option, 'order' => $index + 1, 'description' => null, ]; @@ -552,36 +497,6 @@ private function extractOptions(array $element): array } } } - // Format 2: listItems array - elseif (!empty($element['listItems']) && is_array($element['listItems'])) { - foreach ($element['listItems'] as $idx => $item) { - if (is_array($item)) { - $options[] = [ - 'label' => $item['label'] ?? $item['text'] ?? $item['name'] ?? $item['value'] ?? '', - 'order' => $item['order'] ?? ($idx + 1), - 'description' => $item['description'] ?? null, - ]; - } else { - $options[] = [ - 'label' => isset($item['value']) ? $item['value'] : (string)$item, - 'order' => $idx + 1, - 'description' => null, - ]; - } - } - } - // Format 3: attributes.options - elseif (!empty($element['attributes']['options']) && is_array($element['attributes']['options'])) { - foreach ($element['attributes']['options'] as $idx => $option) { - if (is_array($option)) { - $options[] = [ - 'label' => $option['label'] ?? '', - 'order' => $option['order'] ?? ($idx + 1), - 'description' => $option['description'] ?? null, - ]; - } - } - } // Filter out options with empty labels $options = array_filter($options, function ($option) { @@ -600,50 +515,42 @@ private function extractOptions(array $element): array } /** - * Create select options for SelectInputFormElement + * Create options for Select, Radio, and CheckboxGroup elements */ - private function createSelectOptions($selectModel, array $options): void + private function createOptionsForElement($model, string $type, array $options): void { - if (empty($options)) return; - - foreach ($options as $index => $optionData) { - if (empty($optionData['label'])) continue; // Skip options without labels + if (empty($options)) + return; - try { - \App\Models\FormBuilding\SelectOptionFormElement::createForSelect($selectModel, $optionData); - } catch (\Exception $e) { - Log::error('Failed to create select option', [ - 'option_data' => $optionData, - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); - } - } - } + $methodMap = [ + SelectInputFormElement::class => 'createForSelect', + RadioInputFormElement::class => 'createForRadio', + CheckboxGroupFormElement::class => 'createForCheckboxGroup', + ]; - /** - * Create radio options for RadioInputFormElement - */ - private function createRadioOptions($radioModel, array $options): void - { + $method = $methodMap[$type] ?? null; + if (!$method) + return; - if (empty($options)) return; + foreach ($options as $optionData) { + if (empty($optionData['label'])) + continue; - foreach ($options as $index => $optionData) { - if (empty($optionData['label'])) continue; // Skip options without labels try { - \App\Models\FormBuilding\SelectOptionFormElement::createForRadio($radioModel, $optionData); + SelectOptionFormElement::$method($model, $optionData); } catch (\Exception $e) { - Log::error('Failed to create radio option', [ + Log::error('Failed to create option', [ + 'type' => $type, 'option_data' => $optionData, 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() ]); } } } - // Updated to include progress tracking + /** + * Import elements recursively with progress tracking + */ protected function importElementsRecursive(array $elements, $parentId, $formVersion, $processedElements = 0, $totalElements = 0, $inRepeatableContainer = false, $inPlusContainer = false) { foreach ($elements as $element) { @@ -678,7 +585,7 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers $formVersion, $processedElements, $totalElements, - $inRepeatableContainer /* or $childInRepeatable when present */, + $inRepeatableContainer /* or $childInRepeatable when present */ , $inPlusContainer /* or $childInPlusContainer when present */ ); } @@ -691,41 +598,42 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers // Fallback for lowercase/short types if (!$type) { $typeMap = [ - 'container' => \App\Models\FormBuilding\ContainerFormElement::class, - 'group' => \App\Models\FormBuilding\ContainerFormElement::class, - 'text-input' => \App\Models\FormBuilding\TextInputFormElement::class, - 'textarea' => \App\Models\FormBuilding\TextareaInputFormElement::class, - 'textarea-input' => \App\Models\FormBuilding\TextareaInputFormElement::class, - 'radio' => \App\Models\FormBuilding\RadioInputFormElement::class, - 'radio-input' => \App\Models\FormBuilding\RadioInputFormElement::class, - 'dropdown' => \App\Models\FormBuilding\SelectInputFormElement::class, - 'dropdown-input' => \App\Models\FormBuilding\SelectInputFormElement::class, - 'select' => \App\Models\FormBuilding\SelectInputFormElement::class, - 'select-input' => \App\Models\FormBuilding\SelectInputFormElement::class, - 'checkbox' => \App\Models\FormBuilding\CheckboxInputFormElement::class, - 'checkbox-input' => \App\Models\FormBuilding\CheckboxInputFormElement::class, - 'checkbox-group' => \App\Models\FormBuilding\CheckboxGroupFormElement::class, - 'checkbox-group-input' => \App\Models\FormBuilding\CheckboxGroupFormElement::class, - 'date' => \App\Models\FormBuilding\DateSelectInputFormElement::class, - 'date-select-input' => \App\Models\FormBuilding\DateSelectInputFormElement::class, - 'number' => \App\Models\FormBuilding\NumberInputFormElement::class, - 'number-input' => \App\Models\FormBuilding\NumberInputFormElement::class, - 'currency' => \App\Models\FormBuilding\CurrencyInputFormElement::class, - 'currency-input' => \App\Models\FormBuilding\CurrencyInputFormElement::class, - 'html' => \App\Models\FormBuilding\HTMLFormElement::class, - 'text-info' => \App\Models\FormBuilding\TextInfoFormElement::class, - 'button' => \App\Models\FormBuilding\ButtonInputFormElement::class, - 'button-input' => \App\Models\FormBuilding\ButtonInputFormElement::class, + 'container' => ContainerFormElement::class, + 'group' => ContainerFormElement::class, + 'text-input' => TextInputFormElement::class, + 'textarea' => TextareaInputFormElement::class, + 'textarea-input' => TextareaInputFormElement::class, + 'radio' => RadioInputFormElement::class, + 'radio-input' => RadioInputFormElement::class, + 'dropdown' => SelectInputFormElement::class, + 'dropdown-input' => SelectInputFormElement::class, + 'select' => SelectInputFormElement::class, + 'select-input' => SelectInputFormElement::class, + 'checkbox' => CheckboxInputFormElement::class, + 'checkbox-input' => CheckboxInputFormElement::class, + 'checkbox-group' => CheckboxGroupFormElement::class, + 'checkbox-group-input' => CheckboxGroupFormElement::class, + 'date' => DateSelectInputFormElement::class, + 'date-select-input' => DateSelectInputFormElement::class, + 'number' => NumberInputFormElement::class, + 'number-input' => NumberInputFormElement::class, + 'currency' => CurrencyInputFormElement::class, + 'currency-input' => CurrencyInputFormElement::class, + 'html' => HTMLFormElement::class, + 'text-info' => TextInfoFormElement::class, + 'button' => ButtonInputFormElement::class, + 'button-input' => ButtonInputFormElement::class, ]; if (isset($typeMap[$elementType])) { $type = $typeMap[$elementType]; } } - if (!$type) continue; + if (!$type) + continue; $isRepeatableContainer = false; - if ($type === \App\Models\FormBuilding\ContainerFormElement::class) { + if ($type === ContainerFormElement::class) { $isRepeatableContainer = $this->isRepeatableContainer($element); } @@ -738,7 +646,7 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers $formVersion, $processedElements, $totalElements, - $inRepeatableContainer /* or $childInRepeatable when present */, + $inRepeatableContainer /* or $childInRepeatable when present */ , $inPlusContainer /* or $childInPlusContainer when present */ ); } @@ -757,7 +665,7 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers $humanReadableLabel = null; // Special handling for TextInfo elements - use content if it's short - if ($type === \App\Models\FormBuilding\TextInfoFormElement::class && isset($element['content'])) { + if ($type === TextInfoFormElement::class && isset($element['content'])) { $content = trim($element['content']); if (!empty($content) && strlen($content) <= 30) { $humanReadableLabel = $content; @@ -806,11 +714,10 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers 'reference_id' => $referenceId, 'description' => $attributes['description'] ?? '', 'help_text' => $attributes['help_text'] ?? '', - 'is_read_only' => $attributes['is_read_only'] ? true : false, - 'custom_read_only' => $attributes['is_read_only'] ? true : false, - 'visible_web' => $attributes['visible_web'] ?? true, - 'visible_pdf' => $attributes['visible_pdf'] ?? true, - 'is_required' => $attributes['is_required'] ?? false, + 'visible_web' => $this->normalizeLegacyState($attributes['visible_web'] ?? null), + 'visible_pdf' => $this->normalizeLegacyState($attributes['visible_pdf'] ?? null), + 'is_required' => $this->normalizeLegacyState($attributes['is_required'] ?? null), + 'is_read_only' => $this->normalizeLegacyState($attributes['is_read_only'] ?? null), 'save_on_submit' => $attributes['save_on_submit'] ?? true, ]; @@ -822,69 +729,24 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers $formElement = null; - if ($type === \App\Models\FormBuilding\ContainerFormElement::class) { - $containerModel = \App\Models\FormBuilding\ContainerFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $containerModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\TextInputFormElement::class) { - $textInputModel = \App\Models\FormBuilding\TextInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $textInputModel->id; + // Check if the class exists and is an Eloquent model + if (class_exists($type) && is_subclass_of($type, \Illuminate\Database\Eloquent\Model::class)) { + $elementableModel = $type::create($attributes['attributes']); + $elementData['elementable_id'] = $elementableModel->id; $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\TextareaInputFormElement::class) { - $textareModel = \App\Models\FormBuilding\TextareaInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $textareModel->id; - $formElement = FormElement::create($elementData); - } elseif ($type === \App\Models\FormBuilding\TextInfoFormElement::class) { - $textInfoModel = \App\Models\FormBuilding\TextInfoFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $textInfoModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\DateSelectInputFormElement::class) { - $dateSelectModel = \App\Models\FormBuilding\DateSelectInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $dateSelectModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\CheckboxInputFormElement::class) { - $checkboxInputModel = \App\Models\FormBuilding\CheckboxInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $checkboxInputModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\CheckboxGroupFormElement::class) { - $checkboxGroupModel = \App\Models\FormBuilding\CheckboxGroupFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $checkboxGroupModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\SelectInputFormElement::class) { - $selectModel = \App\Models\FormBuilding\SelectInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $selectModel->id; - $formElement = FormElement::create($elementData); - $this->createSelectOptions($selectModel, $options); - } elseif ($type === \App\Models\FormBuilding\RadioInputFormElement::class) { - $radioModel = \App\Models\FormBuilding\RadioInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $radioModel->id; - $formElement = FormElement::create($elementData); - $this->createRadioOptions($radioModel, $options); - } else if ($type === \App\Models\FormBuilding\NumberInputFormElement::class) { - $numberInputModel = \App\Models\FormBuilding\NumberInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $numberInputModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\CurrencyInputFormElement::class) { - $currencyInputModel = \App\Models\FormBuilding\CurrencyInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $currencyInputModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\ButtonInputFormElement::class) { - $buttonModel = \App\Models\FormBuilding\ButtonInputFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $buttonModel->id; - $formElement = FormElement::create($elementData); - } else if ($type === \App\Models\FormBuilding\HTMLFormElement::class) { - $htmlModel = \App\Models\FormBuilding\HTMLFormElement::updateOrCreate($attributes['attributes']); - $elementData['elementable_id'] = $htmlModel->id; - $formElement = FormElement::create($elementData); - } else { - if (method_exists($type, 'create')) { - $elementableModel = $type::create($attributes['attributes']); - $elementData['elementable_id'] = $elementableModel->id; + + // Handle options for Select, Radio, and CheckboxGroup elements + if ( + in_array($type, [ + SelectInputFormElement::class, + RadioInputFormElement::class, + CheckboxGroupFormElement::class + ]) + ) { + $this->createOptionsForElement($elementableModel, $type, $options); } - $formElement = FormElement::create($elementData); } - if ($formElement) { // Create data binding if ($dataBindingInfo) { @@ -919,7 +781,7 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers } } catch (\Exception $e) { Log::error('Failed to import individual element', [ - 'element' => $element['name'], + 'element' => $element['name'] ?? 'unknown', 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); @@ -927,11 +789,12 @@ protected function importElementsRecursive(array $elements, $parentId, $formVers } if ($this->defaultDataSourceError) { - Notification::make() - ->title("Failed to create the default data source 'Imported Data Source'") - ->body("Please reseed the form_data_sources table using the following command: sail artisan db:seed --class=FormDataSourceSeeder") - ->warning() - ->send(); + Cache::put( + $this->cacheKey . '_warning', + "Failed to create the default data source. + Please reseed the form_data_sources table using the following command: sail artisan db:seed --class=FormDataSourceSeeder", + 3600 + ); } return $processedElements; @@ -1060,22 +923,25 @@ private function createDataBinding($formElement, array $dataBindingInfo, $formVe } } + /** + * Resolve element type string to fully qualified class name + */ private function resolveElementableType(string $elementType): ?string { $map = [ - 'TextInputFormElements' => \App\Models\FormBuilding\TextInputFormElement::class, - 'TextareaInputFormElements' => \App\Models\FormBuilding\TextareaInputFormElement::class, - 'TextInfoFormElements' => \App\Models\FormBuilding\TextInfoFormElement::class, - 'DateSelectInputFormElements' => \App\Models\FormBuilding\DateSelectInputFormElement::class, - 'CheckboxInputFormElements' => \App\Models\FormBuilding\CheckboxInputFormElement::class, - 'CheckboxGroupFormElements' => \App\Models\FormBuilding\CheckboxGroupFormElement::class, - 'SelectInputFormElements' => \App\Models\FormBuilding\SelectInputFormElement::class, - 'RadioInputFormElements' => \App\Models\FormBuilding\RadioInputFormElement::class, - 'NumberInputFormElements' => \App\Models\FormBuilding\NumberInputFormElement::class, - 'CurrencyInputFormElements' => \App\Models\FormBuilding\CurrencyInputFormElement::class, - 'ButtonInputFormElements' => \App\Models\FormBuilding\ButtonInputFormElement::class, - 'HTMLFormElements' => \App\Models\FormBuilding\HTMLFormElement::class, - 'ContainerFormElements' => \App\Models\FormBuilding\ContainerFormElement::class, + 'TextInputFormElements' => TextInputFormElement::class, + 'TextareaInputFormElements' => TextareaInputFormElement::class, + 'TextInfoFormElements' => TextInfoFormElement::class, + 'DateSelectInputFormElements' => DateSelectInputFormElement::class, + 'CheckboxInputFormElements' => CheckboxInputFormElement::class, + 'CheckboxGroupFormElements' => CheckboxGroupFormElement::class, + 'SelectInputFormElements' => SelectInputFormElement::class, + 'RadioInputFormElements' => RadioInputFormElement::class, + 'NumberInputFormElements' => NumberInputFormElement::class, + 'CurrencyInputFormElements' => CurrencyInputFormElement::class, + 'ButtonInputFormElements' => ButtonInputFormElement::class, + 'HTMLFormElements' => HTMLFormElement::class, + 'ContainerFormElements' => ContainerFormElement::class, ]; if (isset($map[$elementType])) { @@ -1091,6 +957,9 @@ private function resolveElementableType(string $elementType): ?string return null; } + /** + * Extract element attributes from the element array + */ private function extractElementAttributes(array $element): array { $exclude = [ @@ -1114,6 +983,9 @@ private function extractElementAttributes(array $element): array ]; $attributes = []; + // Initialize the nested array immediately so we don't get null errors + $attributes['attributes'] = []; + foreach ($element as $key => $value) { if (!in_array($key, $exclude, true)) { $attributes[$key] = $value; @@ -1122,51 +994,50 @@ private function extractElementAttributes(array $element): array // Handle both formats for repeatable containers if (isset($element['repeats'])) { - $attributes['is_repeatable'] = (bool)$element['repeats']; - $attributes['attributes']['is_repeatable'] = (bool)$element['repeats']; + $attributes['attributes']['is_repeatable'] = (bool) $element['repeats']; if (isset($element['attributes']['repeaterItemLabel'])) { $attributes['attributes']['repeater_item_label'] = $element['attributes']['repeaterItemLabel']; } } elseif (isset($element['attributes']['isRepeatable'])) { - $attributes['is_repeatable'] = (bool)$element['attributes']['isRepeatable']; - $attributes['attributes']['is_repeatable'] = (bool)$element['attributes']['isRepeatable']; + $attributes['attributes']['is_repeatable'] = (bool) $element['attributes']['isRepeatable']; if (isset($element['attributes']['repeaterItemLabel'])) { $attributes['attributes']['repeater_item_label'] = $element['attributes']['repeaterItemLabel']; } } + // Handle min/max repeats if (isset($element['minRepeats'])) { - $attributes['min_repeats'] = (int)$element['minRepeats']; + $attributes['attributes']['min_repeats'] = (int) $element['minRepeats']; } elseif (isset($element['min_repeats'])) { - $attributes['min_repeats'] = (int)$element['min_repeats']; + $attributes['attributes']['min_repeats'] = (int) $element['min_repeats']; } if (isset($element['maxRepeats'])) { - $attributes['max_repeats'] = (int)$element['maxRepeats']; + $attributes['attributes']['max_repeats'] = (int) $element['maxRepeats']; } elseif (isset($element['max_repeats'])) { - $attributes['max_repeats'] = (int)$element['max_repeats']; + $attributes['attributes']['max_repeats'] = (int) $element['max_repeats']; } // Handle container type mapping if (isset($element['containerType'])) { - $attributes['container_type'] = $element['containerType']; + $attributes['attributes']['container_type'] = $element['containerType']; } elseif (isset($element['attributes']['containerType'])) { $attributes['attributes']['container_type'] = $element['attributes']['containerType']; } // Handle collapsible properties if (isset($element['collapsible'])) { - $attributes['collapsible'] = (bool)$element['collapsible']; + $attributes['attributes']['collapsible'] = (bool) $element['collapsible']; } if (isset($element['collapsedByDefault'])) { - $attributes['collapsed_by_default'] = (bool)$element['collapsedByDefault']; + $attributes['attributes']['collapsed_by_default'] = (bool) $element['collapsedByDefault']; } $elementType = $element['elementType'] ?? $element['type'] ?? ''; // For TextInfo elements, ensure content is properly mapped if ($elementType === 'TextInfoFormElements' && isset($element['content'])) { - $attributes['content'] = $element['content']; + $attributes['attributes']['content'] = $element['content']; } // For Button elements, ensure label is properly mapped @@ -1174,43 +1045,41 @@ private function extractElementAttributes(array $element): array $attributes['attributes']['text'] = $element['label']; } - // Handle options/list items (both formats) - // if (isset($element['listItems'])) { - // $attributes['listItems'] = $element['listItems']; - // } elseif (isset($element['options'])) { - // $attributes['options'] = $element['options']; - // } - // Handle default values if (isset($element['attributes']['value'])) { - $attributes['attributes']['defaultValue'] = $element['attributes']['value']; + $attributes['attributes']['default_value'] = $element['attributes']['value']; + } elseif (isset($element['attributes']['defaultValue'])) { + $attributes['attributes']['default_value'] = $element['attributes']['defaultValue']; } // Handle date format if (isset($element['dateFormat'])) { - $attributes['dateFormat'] = \App\Models\FormBuilding\DateSelectInputFormElement::convertFromFlatpickrFormat($element['dateFormat']); + $attributes['attributes']['date_format'] = DateSelectInputFormElement::convertFromFlatpickrFormat($element['dateFormat']); } else if (isset($element['attributes']['dateFormat'])) { - $attributes['attributes']['dateFormat'] = \App\Models\FormBuilding\DateSelectInputFormElement::convertFromFlatpickrFormat($element['attributes']['dateFormat']); + $attributes['attributes']['date_format'] = DateSelectInputFormElement::convertFromFlatpickrFormat($element['attributes']['dateFormat']); } // Handle HTML content if (isset($element['htmlContent'])) { - $attributes['html_content'] = $element['htmlContent']; + $attributes['attributes']['html_content'] = $element['htmlContent']; } else if (isset($element['attributes']['htmlContent'])) { $attributes['attributes']['html_content'] = $element['attributes']['htmlContent']; } - - // Ensure $attributes['attributes] exists - if (!isset($attributes['attributes'])) { - $attributes['attributes'] = []; + // Convert all keys in $attributes['attributes'] to snake_case + if (is_array($attributes['attributes'])) { + $snakeAttributes = []; + foreach ($attributes['attributes'] as $key => $value) { + $snakeAttributes[\Illuminate\Support\Str::snake($key)] = $value; + } + $attributes['attributes'] = $snakeAttributes; } return $attributes; } /** - * Determine if the given element is a repeatable container. + * Determine if the given element is a repeatable container */ private function isRepeatableContainer(array $element): bool { @@ -1224,12 +1093,12 @@ private function isRepeatableContainer(array $element): bool } /** - * Determine if the given type is a text field element. + * Determine if the given type is a text field element */ private function isTextField($type): bool { $textFieldTypes = [ - \App\Models\FormBuilding\TextInfoFormElement::class, + TextInfoFormElement::class, ]; return in_array($type, $textFieldTypes, true); } diff --git a/app/Livewire/FormElementTreeBuilder.php b/app/Livewire/FormElementTreeBuilder.php index 6a077e76..67cf9b53 100644 --- a/app/Livewire/FormElementTreeBuilder.php +++ b/app/Livewire/FormElementTreeBuilder.php @@ -388,9 +388,9 @@ protected function mutateFormDataBeforeSave(array $data): array // Filter out null values from elementable data to let model defaults apply // But convert null values to empty strings for text fields that the user might want to clear - $textFields = ['labelText', 'placeholder', 'helperText', 'mask', 'maskErrorMessage', 'content', 'legend', 'repeater_item_label']; - $numericFields = ['min', 'max', 'step', 'defaultValue', 'maxCount', 'rows', 'cols', 'order', 'min_repeats', 'max_repeats']; - $nullableFields = ['level', 'defaultSelected', 'minDate', 'maxDate']; + $textFields = ['label_text', 'placeholder', 'helperText', 'mask', 'mask_error_message', 'content', 'legend', 'repeater_item_label']; + $numericFields = ['min', 'max', 'step', 'default_value', 'max_count', 'rows', 'cols', 'order', 'min_repeats', 'max_repeats']; + $nullableFields = ['level', 'default_selected', 'min_date', 'max_date']; $filteredElementableData = []; foreach ($elementableData as $key => $value) { @@ -557,9 +557,9 @@ protected function mutateFormDataBeforeCreate(array $data): array // Filter out null values from elementable data to let model defaults apply // But convert null values to empty strings for text fields that the user might want to clear - $textFields = ['labelText', 'placeholder', 'helperText', 'mask', 'maskErrorMessage', 'content', 'legend', 'repeater_item_label']; - $numericFields = ['min', 'max', 'step', 'defaultValue', 'maxCount', 'rows', 'cols', 'order', 'min_repeats', 'max_repeats']; - $nullableFields = ['level', 'defaultSelected', 'minDate', 'maxDate']; + $textFields = ['label_text', 'placeholder', 'helperText', 'mask', 'mask_error_message', 'content', 'legend', 'repeater_item_label']; + $numericFields = ['min', 'max', 'step', 'default_value', 'max_count', 'rows', 'cols', 'order', 'min_repeats', 'max_repeats']; + $nullableFields = ['level', 'default_selected', 'min_date', 'max_date']; $filteredElementableData = []; foreach ($elementableData as $key => $value) { diff --git a/app/Models/FormBuilding/ButtonInputFormElement.php b/app/Models/FormBuilding/ButtonInputFormElement.php index 15d58d76..687bf020 100644 --- a/app/Models/FormBuilding/ButtonInputFormElement.php +++ b/app/Models/FormBuilding/ButtonInputFormElement.php @@ -17,7 +17,7 @@ class ButtonInputFormElement extends Model protected $fillable = [ 'text', 'kind', - 'enableVarSub', + 'enable_var_sub', ]; protected $casts = [ @@ -30,12 +30,11 @@ class ButtonInputFormElement extends Model public static function getFilamentSchema(bool $disabled = false): array { return [ - TextInput::make('elementable_data.text') + SchemaHelper::getLabelTextField($disabled) ->label('Button Text') ->default('Submit') - ->required(true) ->autocomplete(false) - ->disabled($disabled), + ->required(true), SchemaHelper::getEnableVariableSubstitutionToggle($disabled), Select::make('elementable_data.kind') ->label('Button Kind') @@ -61,7 +60,7 @@ public function getData(): array return [ 'text' => $this->text, 'kind' => $this->kind, - 'enableVarSub' => $this->enableVarSub, + 'enable_var_sub' => $this->enable_var_sub, ]; } @@ -89,7 +88,7 @@ public static function getDefaultData(): array return [ 'text' => 'Submit', 'kind' => 'primary', - 'enableVarSub' => false, + 'enable_var_sub' => false, ]; } } diff --git a/app/Models/FormBuilding/CheckboxGroupFormElement.php b/app/Models/FormBuilding/CheckboxGroupFormElement.php index 937cbc82..c325b2cc 100644 --- a/app/Models/FormBuilding/CheckboxGroupFormElement.php +++ b/app/Models/FormBuilding/CheckboxGroupFormElement.php @@ -18,21 +18,21 @@ class CheckboxGroupFormElement extends Model use HasFactory, SoftDeletes; protected $fillable = [ - 'labelText', - 'hideLabel', - 'enableVarSub', - 'defaultSelected', + 'label_text', + 'hide_label', + 'enable_var_sub', + 'default_selected', ]; protected $casts = [ - 'hideLabel' => 'boolean', - 'defaultSelected' => 'array', + 'hide_label' => 'boolean', + 'default_selected' => 'array', ]; protected $attributes = [ - 'hideLabel' => false, - 'labelText' => '', - 'defaultSelected' => null, + 'hide_label' => false, + 'label_text' => '', + 'default_selected' => null, ]; /** @@ -41,72 +41,11 @@ class CheckboxGroupFormElement extends Model public static function getFilamentSchema(bool $disabled = false): array { return [ - Fieldset::make('Field Label') - ->schema([ - SchemaHelper::getLabelTextField($disabled)->required(), - SchemaHelper::getEnableVariableSubstitutionToggle($disabled), - SchemaHelper::getHideLabelToggle($disabled), - ]) - ->columns(1), + SchemaHelper::getCommonCarbonFields($disabled, true), Fieldset::make('Values') ->schema([ - Select::make('elementable_data.defaultSelected') - ->label('Default Selected Value') - ->multiple() - ->nullable() - ->live() - ->options(function (callable $get) { - $options = $get('elementable_data.options') ?? []; - $selectOptions = []; - foreach ($options as $option) { - if (!empty($option['value'])) { - $selectOptions[$option['value']] = $option['label'] ?? $option['value']; - } - } - return $selectOptions; - }) - ->disabled($disabled), - Repeater::make('elementable_data.options') - ->label('Options') - ->schema([ - TextInput::make('label') - ->label('Option Label') - ->required() - ->columnSpan(2) - ->autocomplete(false) - ->live(onBlur: true) - ->afterStateUpdated(function (callable $set, callable $get, $state) { - $value = $get('value'); - if (empty($value) && !empty($state)) { - $slug = \Illuminate\Support\Str::slug($state, '-'); - $set('value', $slug); - } - }), - TextInput::make('value') - ->label('Option Value') - ->required() - ->columnSpan(2) - ->suffixAction( - \Filament\Forms\Components\Actions\Action::make('regenerate_value') - ->icon('heroicon-o-arrow-path') - ->tooltip('Regenerate from Option Label') - ->action(function (callable $set, callable $get) { - $label = $get('label'); - if (!empty($label)) { - $slug = \Illuminate\Support\Str::slug($label, '-'); - $set('value', $slug); - } - }) - ), - ]) - ->columns(2) - ->defaultItems(1) - ->addActionLabel('Add Option') - ->reorderableWithButtons() - ->collapsible() - ->itemLabel(fn(array $state): ?string => $state['label'] ?? 'Option') - ->disabled($disabled) - ->minItems(1), + SchemaHelper::getOptionsDefaultSelectedSelect($disabled, true), + SchemaHelper::getOptionsRepeater($disabled), ]) ->columns(1), ]; @@ -126,10 +65,10 @@ public function formElement(): MorphOne public function getData(): array { return [ - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, - 'defaultSelected' => $this->defaultSelected ?? [], + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, + 'default_selected' => $this->default_selected ?? [], ]; } @@ -147,10 +86,10 @@ public function options(): MorphMany public static function getDefaultData(): array { return [ - 'hideLabel' => false, - 'labelText' => '', - 'enableVarSub' => false, - 'defaultSelected' => [], + 'hide_label' => false, + 'label_text' => '', + 'enable_var_sub' => false, + 'default_selected' => [], 'options' => [ ['label' => 'Option 1', 'value' => 'option_1'], ['label' => 'Option 2', 'value' => 'option_2'], diff --git a/app/Models/FormBuilding/CheckboxInputFormElement.php b/app/Models/FormBuilding/CheckboxInputFormElement.php index 562c09f2..cdfedd16 100644 --- a/app/Models/FormBuilding/CheckboxInputFormElement.php +++ b/app/Models/FormBuilding/CheckboxInputFormElement.php @@ -15,20 +15,20 @@ class CheckboxInputFormElement extends Model use HasFactory, SoftDeletes; protected $fillable = [ - 'labelText', - 'hideLabel', - 'defaultChecked', - 'enableVarSub', + 'label_text', + 'hide_label', + 'default_checked', + 'enable_var_sub', ]; protected $casts = [ - 'hideLabel' => 'boolean', - 'defaultChecked' => 'boolean', + 'hide_label' => 'boolean', + 'default_checked' => 'boolean', ]; protected $attributes = [ - 'hideLabel' => false, - 'defaultChecked' => false, + 'hide_label' => false, + 'default_checked' => false, ]; /** @@ -37,20 +37,11 @@ class CheckboxInputFormElement extends Model public static function getFilamentSchema(bool $disabled = false): array { return [ - Fieldset::make('Field Label') - ->schema([ - SchemaHelper::getLabelTextField($disabled) - ->label('Checkbox Label') - ->autocomplete(false) - ->required(), - SchemaHelper::getEnableVariableSubstitutionToggle($disabled), - SchemaHelper::getHideLabelToggle($disabled), - ]) - ->columns(1), - Toggle::make('elementable_data.defaultChecked') - ->label('Default Checked') - ->default(false) - ->disabled($disabled), + SchemaHelper::getCommonCarbonFields($disabled, true), + Toggle::make('elementable_data.default_checked') + ->label('Default Checked') + ->default(false) + ->disabled($disabled), ]; } @@ -68,10 +59,10 @@ public function formElement(): MorphOne public function getData(): array { return [ - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'defaultChecked' => $this->defaultChecked, - 'enableVarSub' => $this->enableVarSub, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'default_checked' => $this->default_checked, + 'enable_var_sub' => $this->enable_var_sub, ]; } @@ -81,10 +72,10 @@ public function getData(): array public static function getDefaultData(): array { return [ - 'hideLabel' => false, - 'defaultChecked' => false, - 'labelText' => '', - 'enableVarSub' => false, + 'hide_label' => false, + 'default_checked' => false, + 'label_text' => '', + 'enable_var_sub' => false, ]; } } diff --git a/app/Models/FormBuilding/ContainerFormElement.php b/app/Models/FormBuilding/ContainerFormElement.php index b7423c71..d9f24e02 100644 --- a/app/Models/FormBuilding/ContainerFormElement.php +++ b/app/Models/FormBuilding/ContainerFormElement.php @@ -28,7 +28,7 @@ class ContainerFormElement extends Model 'min_repeats', 'max_repeats', 'legend', - 'enableVarSub', + 'enable_var_sub', 'level' ]; @@ -46,7 +46,8 @@ class ContainerFormElement extends Model protected static function formatInteger(string $target): Closure { return function ($state, callable $set, Get $get) use ($target) { - if ($state === null) return; + if ($state === null) + return; $raw = trim((string) $state); @@ -101,6 +102,7 @@ public static function getFilamentSchema(bool $disabled = false): array ->columnSpan(2) ->helperText('Label for individual repeater items (e.g., "Item", "Entry")') ->disabled($disabled) + ->maxLength(255) ->visible(fn(callable $get) => $get('elementable_data.is_repeatable')), TextInput::make('elementable_data.min_repeats') ->label('Minimum Repeats') @@ -155,19 +157,10 @@ public static function getFilamentSchema(bool $disabled = false): array ->nullable() ->helperText('Optional level override for the label (e.g., h2, h3, etc.)') ->disabled($disabled), - TextInput::make('elementable_data.legend') + SchemaHelper::getLabelTextField($disabled) ->label('Legend/Title') ->helperText('Optional title for the container') - ->suffixAction(Action::make('generate_label_text') - ->icon('heroicon-o-arrow-path') - ->tooltip('Regenerate from Element Name') - ->action(function (callable $set, callable $get) { - $name = $get('name'); - if (!empty($name)) { - $set('elementable_data.legend', $name); - } - })) - ->disabled($disabled), + ->maxLength(255), SchemaHelper::getEnableVariableSubstitutionToggle($disabled), ]) ->columns(1), @@ -194,7 +187,7 @@ public function getData(): array 'min_repeats' => $this->min_repeats, 'max_repeats' => $this->max_repeats, 'legend' => $this->legend, - 'enableVarSub' => $this->enableVarSub, + 'enable_var_sub' => $this->enable_var_sub, 'level' => $this->level, ]; } @@ -222,7 +215,7 @@ public static function getDefaultData(): array 'container_type' => 'section', 'is_repeatable' => false, 'legend' => '', - 'enableVarSub' => false, + 'enable_var_sub' => false, 'repeater_item_label' => '', 'min_repeats' => null, 'max_repeats' => null, diff --git a/app/Models/FormBuilding/CurrencyInputFormElement.php b/app/Models/FormBuilding/CurrencyInputFormElement.php index 82dacbe4..eeb42f59 100644 --- a/app/Models/FormBuilding/CurrencyInputFormElement.php +++ b/app/Models/FormBuilding/CurrencyInputFormElement.php @@ -19,32 +19,33 @@ class CurrencyInputFormElement extends Model protected $fillable = [ 'placeholder', - 'labelText', - 'hideLabel', - 'enableVarSub', + 'label_text', + 'hide_label', + 'enable_var_sub', 'min', 'max', - 'defaultValue', + 'default_value', ]; protected $casts = [ - 'hideLabel' => 'boolean', + 'hide_label' => 'boolean', 'min' => 'integer', 'max' => 'integer', - 'defaultValue' => 'float', + 'default_value' => 'float', ]; protected $attributes = [ - 'hideLabel' => false, + 'hide_label' => false, ]; /* * Format the value as currency - */ + */ protected static function formatCurrency(string $target): Closure { return function ($state, callable $set) use ($target): void { - if ($state === null) return; + if ($state === null) + return; $raw = trim((string) $state); @@ -56,17 +57,20 @@ protected static function formatCurrency(string $target): Closure // Collapse lone sign/dot or all-zero forms to "0.00" $isZeroish = static function (string $s): bool { $s = trim($s); - if ($s === '') return false; + if ($s === '') + return false; // remove leading sign $s = ltrim($s, "+-"); // empty after sign or just a dot -> zero-ish - if ($s === '' || $s === '.') return true; + if ($s === '' || $s === '.') + return true; // keep only digits and a single dot $clean = preg_replace('/[^\d.]/', '', $s) ?? ''; - if ($clean === '' || substr_count($clean, '.') > 1) return false; + if ($clean === '' || substr_count($clean, '.') > 1) + return false; // if all remaining digits are zeros, it's zero-ish $digitsOnly = str_replace('.', '', $clean); @@ -86,11 +90,11 @@ protected static function formatCurrency(string $target): Closure } // Extract sign once; operate on a signless body - $neg = ($raw !== '' && $raw[0] === '-'); + $neg = ($raw !== '' && $raw[0] === '-'); $body = ltrim($raw, '+-'); // Split into integer + fractional segments - $int = $body; + $int = $body; $frac = ''; if (strpos($body, '.') !== false) { [$int, $frac] = explode('.', $body, 2); @@ -98,7 +102,8 @@ protected static function formatCurrency(string $target): Closure // Normalize integer: strip extra leading zeros, keep at least one '0' $int = ltrim($int, '0'); - if ($int === '') $int = '0'; + if ($int === '') + $int = '0'; // Fraction handling: // - If <= 2 digits: pad to exactly 2 @@ -142,68 +147,66 @@ public static function getFilamentSchema(bool $disabled = false): array $noSci = 'not_regex:/[eE]/'; // forbid scientific notation $currencyRegex = 'regex:/^-?\d+(\.\d{1,2})?$/'; - return array_merge( + return [ SchemaHelper::getCommonCarbonFields($disabled), - [ - Fieldset::make('Value') - ->schema([ - SchemaHelper::getPlaceholderTextField($disabled) - ->columnSpan(3), - TextInput::make('elementable_data.defaultValue') - ->label('Default Value') - ->numeric() - ->nullable() - ->step(.01) - ->live(onBlur: true) - ->afterStateUpdated(self::formatCurrency('elementable_data.defaultValue')) - ->rules([$currencyRegex, $noSci]) - ->rule(NumericRules::compareWith( - minPath: 'elementable_data.min', - maxPath: 'elementable_data.max', - options: [ - 'format' => fn(float $n) => number_format($n, 2, '.', ''), - ] - )) - ->columnSpan(1) - ->disabled($disabled), - TextInput::make('elementable_data.min') - ->label('Minimum Value') - ->numeric() - ->nullable() - ->step(.01) - ->live(onBlur: true) - ->afterStateUpdated(self::formatCurrency('elementable_data.min')) - ->rules([$currencyRegex, $noSci]) - ->rule(NumericRules::compareWith( - minPath: null, - maxPath: 'elementable_data.max', - options: [ - 'format' => fn(float $n) => number_format($n, 2, '.', ''), - ] - )) - ->columnSpan(1) - ->disabled($disabled), - TextInput::make('elementable_data.max') - ->label('Maximum Value') - ->numeric() - ->nullable() - ->step(.01) - ->live(onBlur: true) - ->afterStateUpdated(self::formatCurrency('elementable_data.max')) - ->rules([$currencyRegex, $noSci]) - ->rule(NumericRules::compareWith( - minPath: 'elementable_data.min', - maxPath: null, - options: [ - 'format' => fn(float $n) => number_format($n, 2, '.', ''), - ] - )) - ->columnSpan(1) - ->disabled($disabled), - ]) - ->columns(3), - ] - ); + Fieldset::make('Value') + ->schema([ + SchemaHelper::getPlaceholderTextField($disabled) + ->columnSpan(3), + TextInput::make('elementable_data.default_value') + ->label('Default Value') + ->numeric() + ->nullable() + ->step(.01) + ->live(onBlur: true) + ->afterStateUpdated(self::formatCurrency('elementable_data.default_value')) + ->rules([$currencyRegex, $noSci]) + ->rule(NumericRules::compareWith( + minPath: 'elementable_data.min', + maxPath: 'elementable_data.max', + options: [ + 'format' => fn(float $n) => number_format($n, 2, '.', ''), + ] + )) + ->columnSpan(1) + ->disabled($disabled), + TextInput::make('elementable_data.min') + ->label('Minimum Value') + ->numeric() + ->nullable() + ->step(.01) + ->live(onBlur: true) + ->afterStateUpdated(self::formatCurrency('elementable_data.min')) + ->rules([$currencyRegex, $noSci]) + ->rule(NumericRules::compareWith( + minPath: null, + maxPath: 'elementable_data.max', + options: [ + 'format' => fn(float $n) => number_format($n, 2, '.', ''), + ] + )) + ->columnSpan(1) + ->disabled($disabled), + TextInput::make('elementable_data.max') + ->label('Maximum Value') + ->numeric() + ->nullable() + ->step(.01) + ->live(onBlur: true) + ->afterStateUpdated(self::formatCurrency('elementable_data.max')) + ->rules([$currencyRegex, $noSci]) + ->rule(NumericRules::compareWith( + minPath: 'elementable_data.min', + maxPath: null, + options: [ + 'format' => fn(float $n) => number_format($n, 2, '.', ''), + ] + )) + ->columnSpan(1) + ->disabled($disabled), + ]) + ->columns(3), + ]; } /** @@ -221,12 +224,12 @@ public function getData(): array { return [ 'placeholder' => $this->placeholder, - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, 'min' => $this->min, 'max' => $this->max, - 'defaultValue' => $this->defaultValue, + 'default_value' => $this->default_value, ]; } @@ -237,12 +240,12 @@ public static function getDefaultData(): array { return [ 'placeholder' => '', - 'labelText' => '', - 'hideLabel' => false, - 'enableVarSub' => false, + 'label_text' => '', + 'hide_label' => false, + 'enable_var_sub' => false, 'min' => null, 'max' => null, - 'defaultValue' => null, + 'default_value' => null, ]; } } diff --git a/app/Models/FormBuilding/DateSelectInputFormElement.php b/app/Models/FormBuilding/DateSelectInputFormElement.php index f7073646..22d8bbe0 100644 --- a/app/Models/FormBuilding/DateSelectInputFormElement.php +++ b/app/Models/FormBuilding/DateSelectInputFormElement.php @@ -17,23 +17,23 @@ class DateSelectInputFormElement extends Model protected $fillable = [ 'placeholder', - 'labelText', - 'hideLabel', - 'enableVarSub', - 'minDate', - 'maxDate', - 'dateFormat', + 'label_text', + 'hide_label', + 'enable_var_sub', + 'min_date', + 'max_date', + 'date_format', ]; protected $casts = [ - 'hideLabel' => 'boolean', - 'minDate' => 'date', - 'maxDate' => 'date', + 'hide_label' => 'boolean', + 'min_date' => 'date', + 'max_date' => 'date', ]; protected $attributes = [ - 'hideLabel' => false, - 'dateFormat' => 'YYYY-MMM-DD', + 'hide_label' => false, + 'date_format' => 'YYYY-MMM-DD', ]; /** @@ -41,30 +41,28 @@ class DateSelectInputFormElement extends Model */ public static function getFilamentSchema(bool $disabled = false): array { - return array_merge( + return [ SchemaHelper::getCommonCarbonFields($disabled), - [ - Fieldset::make('Value') - ->schema([ - SchemaHelper::getPlaceholderTextField($disabled), - Select::make('elementable_data.dateFormat') - ->label('Date Format') - ->options(static::getDateFormats()) - ->default('YYYY-MMM-DD') - ->required() - ->disabled($disabled), - DatePicker::make('elementable_data.minDate') - ->label('Minimum Date') - ->helperText('Earliest date users can select') - ->disabled($disabled), - DatePicker::make('elementable_data.maxDate') - ->label('Maximum Date') - ->helperText('Latest date users can select') - ->disabled($disabled), - ]) - ->columns(1), - ] - ); + Fieldset::make('Value') + ->schema([ + SchemaHelper::getPlaceholderTextField($disabled), + Select::make('elementable_data.date_format') + ->label('Date Format') + ->options(static::getDateFormats()) + ->default('YYYY-MMM-DD') + ->required() + ->disabled($disabled), + DatePicker::make('elementable_data.min_date') + ->label('Minimum Date') + ->helperText('Earliest date users can select') + ->disabled($disabled), + DatePicker::make('elementable_data.max_date') + ->label('Maximum Date') + ->helperText('Latest date users can select') + ->disabled($disabled), + ]) + ->columns(1), + ]; } /** @@ -82,12 +80,12 @@ public function getData(): array { return [ 'placeholder' => $this->placeholder, - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, - 'minDate' => $this->minDate, - 'maxDate' => $this->maxDate, - 'dateFormat' => $this->dateFormat, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, + 'min_date' => $this->min_date, + 'max_date' => $this->max_date, + 'date_format' => $this->date_format, ]; } @@ -197,12 +195,12 @@ public static function getDefaultData(): array { return [ 'placeholder' => '', - 'labelText' => 'Date Select Input', - 'hideLabel' => false, - 'enableVarSub' => false, - 'minDate' => null, - 'maxDate' => null, - 'dateFormat' => 'YYYY-MMM-DD', + 'label_text' => 'Date Select Input', + 'hide_label' => false, + 'enable_var_sub' => false, + 'min_date' => null, + 'max_date' => null, + 'date_format' => 'YYYY-MMM-DD', ]; } } diff --git a/app/Models/FormBuilding/FormElement.php b/app/Models/FormBuilding/FormElement.php index e1218510..ab84fcd2 100644 --- a/app/Models/FormBuilding/FormElement.php +++ b/app/Models/FormBuilding/FormElement.php @@ -30,12 +30,11 @@ class FormElement extends Model 'elementable_id', 'help_text', 'calculated_value', + 'visible_web', + 'visible_pdf', 'is_read_only', - 'custom_read_only', 'is_required', 'save_on_submit', - 'visible_web', - 'visible_pdf', 'is_template', 'source_element_id', ]; @@ -44,9 +43,11 @@ class FormElement extends Model 'order' => 'integer', 'parent_id' => 'integer', 'save_on_submit' => 'boolean', - 'visible_web' => 'boolean', - 'visible_pdf' => 'boolean', 'is_template' => 'boolean', + 'visible_web' => 'string', + 'visible_pdf' => 'string', + 'is_required' => 'string', + 'is_read_only' => 'string', ]; protected static $logAttributes = [ @@ -57,6 +58,10 @@ class FormElement extends Model 'form_version_id', 'elementable_type', 'help_text', + 'visible_web', + 'visible_pdf', + 'is_required', + 'is_read_only', ]; public static function boot() @@ -240,7 +245,9 @@ public function scopeOrdered($query) */ public function scopeVisible($query) { - return $query->where('visible_web', true); + return $query + ->whereIn('visible_web', self::getActiveToggleButtonStates()) + ->orWhereIn('visible_pdf', self::getActiveToggleButtonStates()); } /** @@ -248,7 +255,7 @@ public function scopeVisible($query) */ public function scopeVisibleWeb($query) { - return $query->where('visible_web', true); + return $query->whereIn('visible_web', self::getActiveToggleButtonStates()); } /** @@ -256,7 +263,7 @@ public function scopeVisibleWeb($query) */ public function scopeVisiblePdf($query) { - return $query->where('visible_pdf', true); + return $query->whereIn('visible_pdf', self::getActiveToggleButtonStates()); } /** @@ -264,12 +271,12 @@ public function scopeVisiblePdf($query) */ public function scopeReadOnly($query) { - return $query->where('is_read_only', true); + return $query->whereIn('is_read_only', self::getActiveToggleButtonStates()); } public function scopeIsRequired($query) { - return $query->where('is_required', true); + return $query->where('is_required', self::getActiveToggleButtonStates()); } /** @@ -277,7 +284,7 @@ public function scopeIsRequired($query) */ public function scopeEditable($query) { - return $query->where('is_read_only', false); + return $query->whereIn('is_read_only', self::getInactiveToggleButtonStates()); } /** @@ -659,16 +666,47 @@ public function getOptions() return collect(); } + /** + * Get all state options for ToggleButtons (required, read-only, visibility) + */ + public static function getToggleButtonStates(): array + { + return [ + 'always' => 'Always', + 'icm' => 'On ICM Forms', + 'portal' => 'On Portal Forms', + 'never' => 'Never', + ]; + } + + /** + * Get the ToggleButton keys that represent an "active" or "enabled" state + */ + public static function getActiveToggleButtonStates(): array + { + return array_diff(array_keys(self::getToggleButtonStates()), ['never']); + } + + /** + * Get the ToggleButton values that represent an "inactive" or "disabled" state + */ + public static function getInactiveToggleButtonStates(): array + { + return ['never', '']; + } + /** * Check if element is visible for a specific platform */ public function isVisibleFor(string $platform): bool { - return match ($platform) { + $value = match ($platform) { 'web' => $this->visible_web, 'pdf' => $this->visible_pdf, - default => false, + default => null, }; + + return in_array($value, self::getActiveToggleButtonStates(), true); } /** @@ -676,17 +714,43 @@ public function isVisibleFor(string $platform): bool */ public function shouldSaveOnSubmit(): bool { - return $this->save_on_submit && ($this->isVisibleFor('web') || $this->isVisibleFor('pdf')) && !$this->is_read_only; + $isReadOnly = in_array($this->is_read_only, self::getActiveToggleButtonStates(), true); + return $this->save_on_submit && ($this->isVisibleFor('web') || $this->isVisibleFor('pdf')) && !$isReadOnly; } /** - * Set visibility for specific platforms + * Set visibility for web */ - public function setVisibilityFor(array $platforms): self + public function setVisibilityWeb(string $value): self { - $this->visible_web = in_array('web', $platforms); - $this->visible_pdf = in_array('pdf', $platforms); + $this->visible_web = $value; + return $this; + } + /** + * Set visibility for pdf + */ + public function setVisibilityPdf(string $value): self + { + $this->visible_pdf = $value; + return $this; + } + + /** + * Set required + */ + public function setRequired(string $value): self + { + $this->is_required = $value; + return $this; + } + + /** + * Set read-only for pdf + */ + public function setReadOnly(string $value): self + { + $this->is_read_only = $value; return $this; } diff --git a/app/Models/FormBuilding/NumberInputFormElement.php b/app/Models/FormBuilding/NumberInputFormElement.php index 918ed675..e8b61ee7 100644 --- a/app/Models/FormBuilding/NumberInputFormElement.php +++ b/app/Models/FormBuilding/NumberInputFormElement.php @@ -20,28 +20,28 @@ class NumberInputFormElement extends Model protected $fillable = [ 'placeholder', - 'labelText', - 'hideLabel', - 'enableVarSub', + 'label_text', + 'hide_label', + 'enable_var_sub', 'min', 'max', 'step', - 'defaultValue', - 'maskType', + 'default_value', + 'mask_type', ]; protected $casts = [ - 'hideLabel' => 'boolean', + 'hide_label' => 'boolean', 'min' => 'integer', 'max' => 'integer', 'step' => 'float', - 'defaultValue' => 'float', + 'default_value' => 'float', ]; protected $attributes = [ - 'hideLabel' => false, + 'hide_label' => false, 'step' => 1, - 'maskType' => 'integer', + 'mask_type' => 'integer', ]; /** @@ -50,10 +50,11 @@ class NumberInputFormElement extends Model protected static function formatNumberByMask(string $target): Closure { return function ($state, callable $set, Get $get) use ($target) { - if ($state === null) return; + if ($state === null) + return; - $raw = trim((string) $state); - $mask = strtolower((string) ($get('elementable_data.maskType') ?? 'integer')); // 'integer' | 'decimal' + $raw = trim((string) $state); + $mask = strtolower((string) ($get('elementable_data.mask_type') ?? 'integer')); // 'integer' | 'decimal' // Do not "fix" scientific notation or thousands separators; let validation reject them. if (preg_match('/[eE, ]/', $state)) { @@ -64,13 +65,16 @@ protected static function formatNumberByMask(string $target): Closure $isZeroish = static function (string $s): bool { $s = trim($s); - if ($s === '') return false; + if ($s === '') + return false; $s = ltrim($s, "+-"); - if ($s === '' || $s === '.') return true; + if ($s === '' || $s === '.') + return true; $clean = preg_replace('/[^\d.]/', '', $s) ?? ''; - if ($clean === '' || substr_count($clean, '.') > 1) return false; + if ($clean === '' || substr_count($clean, '.') > 1) + return false; $digitsOnly = str_replace('.', '', $clean); return $digitsOnly !== '' && preg_match('/^0+$/', $digitsOnly) === 1; @@ -117,7 +121,8 @@ protected static function formatNumberByMask(string $target): Closure // normalize leading zeros in integer part $int = ltrim($int, '0'); - if ($int === '') $int = '0'; + if ($int === '') + $int = '0'; // trim trailing zeros in fractional part; drop the dot if empty $frac = rtrim($frac, '0'); @@ -127,7 +132,8 @@ protected static function formatNumberByMask(string $target): Closure // integer-like in decimal context $int = preg_replace('/\D/', '', $body) ?? ''; $int = ltrim($int, '0'); - if ($int === '') $int = '0'; + if ($int === '') + $int = '0'; $out = $int; } @@ -147,128 +153,126 @@ protected static function formatNumberByMask(string $target): Closure public static function getFilamentSchema(bool $disabled = false): array { - $isDecimal = fn(Get $get) => strtolower($get('elementable_data.maskType') ?? 'integer') === 'decimal'; + $isDecimal = fn(Get $get) => strtolower($get('elementable_data.mask_type') ?? 'integer') === 'decimal'; $noSci = 'not_regex:/[eE]/'; // forbid scientific notation $plainDecimal = 'regex:/^-?\d+(\.\d+)?$/'; // allow optional leading '-', digits, and one dot - return array_merge( + return [ SchemaHelper::getCommonCarbonFields($disabled), - [ - Fieldset::make('Value') - ->schema([ - SchemaHelper::getPlaceholderTextField($disabled) - ->columnSpan(6), - TextInput::make('elementable_data.defaultValue') - ->label('Default Value') - ->numeric() - ->nullable() - ->step(fn(Get $get) => $get('elementable_data.step') ?? 1) - ->live(onBlur: true) - ->afterStateUpdated(self::formatNumberByMask('elementable_data.defaultValue')) - ->rules(function (Get $get) use ($isDecimal, $noSci, $plainDecimal) { - $rules = $isDecimal($get) - ? ['numeric', $noSci, $plainDecimal] - : ['integer']; - return $rules; - }) - ->rule(NumericRules::compareWith( - minPath: 'elementable_data.min', - maxPath: 'elementable_data.max', - )) - ->columnSpan(2) - ->disabled($disabled), - TextInput::make('elementable_data.min') - ->label('Minimum Value') - ->numeric() - ->nullable() - ->step(fn(Get $get) => $get('elementable_data.step') ?? 1) - ->live(onBlur: true) - ->afterStateUpdated(self::formatNumberByMask('elementable_data.min')) - ->rules(function (Get $get) use ($isDecimal, $noSci, $plainDecimal) { - $rules = $isDecimal($get) - ? ['numeric', $noSci, $plainDecimal] - : ['integer']; - return $rules; - }) - ->rule(NumericRules::compareWith( - minPath: null, - maxPath: 'elementable_data.max', - )) - ->columnSpan(2) - ->disabled($disabled), - TextInput::make('elementable_data.max') - ->label('Maximum Value') - ->numeric() - ->nullable() - ->step(fn(Get $get) => $get('elementable_data.step') ?? 1) - ->live(onBlur: true) - ->afterStateUpdated(self::formatNumberByMask('elementable_data.max')) - ->rules(function (Get $get) use ($isDecimal, $noSci, $plainDecimal) { - $rules = $isDecimal($get) - ? ['numeric', $noSci, $plainDecimal] - : ['integer']; - return $rules; - }) - ->rule(NumericRules::compareWith( - minPath: 'elementable_data.min', - maxPath: null, - )) - ->columnSpan(2) - ->disabled($disabled), - ToggleButtons::make('elementable_data.maskType') - ->label('Input Mask Type') - ->options([ - 'integer' => 'Integer', - 'decimal' => 'Decimal', - ]) - ->inline() - ->default('integer') - ->live() - ->columnSpan(3) - ->afterStateUpdated(function (string $state, callable $set, Get $get) { - if ($state === 'integer') { - // Force step = 1 for integer mode - $set('elementable_data.step', 1); - - // Coerce existing values to integers (if set) - foreach (['defaultValue', 'min', 'max'] as $key) { - $path = "elementable_data.$key"; - $val = $get($path); - if (filled($val)) { - $set($path, (int) round((float) $val)); - } + Fieldset::make('Value') + ->schema([ + SchemaHelper::getPlaceholderTextField($disabled) + ->columnSpan(6), + TextInput::make('elementable_data.default_value') + ->label('Default Value') + ->numeric() + ->nullable() + ->step(fn(Get $get) => $get('elementable_data.step') ?? 1) + ->live(onBlur: true) + ->afterStateUpdated(self::formatNumberByMask('elementable_data.default_value')) + ->rules(function (Get $get) use ($isDecimal, $noSci, $plainDecimal) { + $rules = $isDecimal($get) + ? ['numeric', $noSci, $plainDecimal] + : ['integer']; + return $rules; + }) + ->rule(NumericRules::compareWith( + minPath: 'elementable_data.min', + maxPath: 'elementable_data.max', + )) + ->columnSpan(2) + ->disabled($disabled), + TextInput::make('elementable_data.min') + ->label('Minimum Value') + ->numeric() + ->nullable() + ->step(fn(Get $get) => $get('elementable_data.step') ?? 1) + ->live(onBlur: true) + ->afterStateUpdated(self::formatNumberByMask('elementable_data.min')) + ->rules(function (Get $get) use ($isDecimal, $noSci, $plainDecimal) { + $rules = $isDecimal($get) + ? ['numeric', $noSci, $plainDecimal] + : ['integer']; + return $rules; + }) + ->rule(NumericRules::compareWith( + minPath: null, + maxPath: 'elementable_data.max', + )) + ->columnSpan(2) + ->disabled($disabled), + TextInput::make('elementable_data.max') + ->label('Maximum Value') + ->numeric() + ->nullable() + ->step(fn(Get $get) => $get('elementable_data.step') ?? 1) + ->live(onBlur: true) + ->afterStateUpdated(self::formatNumberByMask('elementable_data.max')) + ->rules(function (Get $get) use ($isDecimal, $noSci, $plainDecimal) { + $rules = $isDecimal($get) + ? ['numeric', $noSci, $plainDecimal] + : ['integer']; + return $rules; + }) + ->rule(NumericRules::compareWith( + minPath: 'elementable_data.min', + maxPath: null, + )) + ->columnSpan(2) + ->disabled($disabled), + ToggleButtons::make('elementable_data.mask_type') + ->label('Input Mask Type') + ->options([ + 'integer' => 'Integer', + 'decimal' => 'Decimal', + ]) + ->inline() + ->default('integer') + ->live() + ->columnSpan(3) + ->afterStateUpdated(function (string $state, callable $set, Get $get) { + if ($state === 'integer') { + // Force step = 1 for integer mode + $set('elementable_data.step', 1); + + // Coerce existing values to integers (if set) + foreach (['default_value', 'min', 'max'] as $key) { + $path = "elementable_data.$key"; + $val = $get($path); + if (filled($val)) { + $set($path, (int) round((float) $val)); } - } else { - // Decimal mode: keep or set a reasonable decimal step - $set('elementable_data.step', 0.01); } - }), - TextInput::make('elementable_data.step') - ->required() - ->label('Step Size') - ->numeric() - ->default(1) - ->live(onBlur: true) - // Ensure UI "step" attribute makes sense (1 for integer; any step otherwise) - ->step(fn(Get $get) => $isDecimal($get) ? "any" : 1) - ->afterStateUpdated(self::formatNumberByMask('elementable_data.step')) - // Validation: integer & >=1 in integer mode; numeric & >0 in decimal mode - ->rule(fn(Get $get) => $isDecimal($get) - ? [ - 'numeric', - 'gt:0', - $noSci, - 'regex:/^\d+(\.\d+)?$/', // only digits and one dot - ] - : ['integer', 'min:1']) - ->columnSpan(3) - ->disabled($disabled), - ]) - ->columns(6), - ] - ); + } else { + // Decimal mode: keep or set a reasonable decimal step + $set('elementable_data.step', 0.01); + } + }), + TextInput::make('elementable_data.step') + ->required() + ->label('Step Size') + ->numeric() + ->default(1) + ->live(onBlur: true) + // Ensure UI "step" attribute makes sense (1 for integer; any step otherwise) + ->step(fn(Get $get) => $isDecimal($get) ? "any" : 1) + ->afterStateUpdated(self::formatNumberByMask('elementable_data.step')) + // Validation: integer & >=1 in integer mode; numeric & >0 in decimal mode + ->rule(fn(Get $get) => $isDecimal($get) + ? [ + 'numeric', + 'gt:0', + $noSci, + 'regex:/^\d+(\.\d+)?$/', // only digits and one dot + ] + : ['integer', 'min:1']) + ->columnSpan(3) + ->disabled($disabled), + ]) + ->columns(6), + ]; } /** @@ -286,14 +290,14 @@ public function getData(): array { return [ 'placeholder' => $this->placeholder, - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, 'min' => $this->min, 'max' => $this->max, 'step' => $this->step, - 'defaultValue' => $this->defaultValue, - 'maskType' => $this->maskType, + 'default_value' => $this->default_value, + 'mask_type' => $this->mask_type, ]; } @@ -304,14 +308,14 @@ public static function getDefaultData(): array { return [ 'placeholder' => '', - 'labelText' => '', - 'hideLabel' => false, - 'enableVarSub' => false, + 'label_text' => '', + 'hide_label' => false, + 'enable_var_sub' => false, 'min' => null, 'max' => null, 'step' => 1, - 'defaultValue' => null, - 'maskType' => 'integer', + 'default_value' => null, + 'mask_type' => 'integer', ]; } } diff --git a/app/Models/FormBuilding/RadioInputFormElement.php b/app/Models/FormBuilding/RadioInputFormElement.php index ccc8ac06..79579e47 100644 --- a/app/Models/FormBuilding/RadioInputFormElement.php +++ b/app/Models/FormBuilding/RadioInputFormElement.php @@ -18,22 +18,22 @@ class RadioInputFormElement extends Model use HasFactory, SoftDeletes; protected $fillable = [ - 'labelText', - 'hideLabel', - 'enableVarSub', - 'defaultSelected', - 'labelPosition', + 'label_text', + 'hide_label', + 'enable_var_sub', + 'default_selected', + 'label_position', 'orientation', ]; protected $casts = [ - 'hideLabel' => 'boolean', + 'hide_label' => 'boolean', ]; protected $attributes = [ - 'hideLabel' => false, - 'labelText' => '', - 'labelPosition' => 'right', + 'hide_label' => false, + 'label_text' => '', + 'label_position' => 'right', 'orientation' => 'vertical', ]; @@ -48,16 +48,15 @@ public static function getFilamentSchema(bool $disabled = false): array SchemaHelper::getLabelTextField($disabled)->required(), SchemaHelper::getEnableVariableSubstitutionToggle($disabled), SchemaHelper::getHideLabelToggle($disabled), - Select::make('elementable_data.labelPosition') + Select::make('elementable_data.label_position') ->label('Label Position') ->options([ 'left' => 'Left', 'right' => 'Right', ]) ->default('right') - ->visible(fn(callable $get): bool => !$get('elementable_data.hideLabel')) + ->visible(fn(callable $get): bool => !$get('elementable_data.hide_label')) ->disabled($disabled), - ]) ->columns(1), Fieldset::make('Values') @@ -70,60 +69,8 @@ public static function getFilamentSchema(bool $disabled = false): array ]) ->default('vertical') ->disabled($disabled), - Select::make('elementable_data.defaultSelected') - ->label('Default Selected Value') - ->options(function (callable $get) { - $options = $get('elementable_data.options') ?? []; - $selectOptions = []; - foreach ($options as $option) { - if (!empty($option['value'])) { - $selectOptions[$option['value']] = $option['label'] ?? $option['value']; - } - } - return $selectOptions; - }) - ->disabled($disabled), - Repeater::make('elementable_data.options') - ->label('Options') - ->schema([ - TextInput::make('label') - ->label('Option Label') - ->required() - ->columnSpan(2) - ->autocomplete(false) - ->live(onBlur: true) - ->afterStateUpdated(function (callable $set, callable $get, $state) { - $value = $get('value'); - if (empty($value) && !empty($state)) { - $slug = \Illuminate\Support\Str::slug($state, '-'); - $set('value', $slug); - } - }), - TextInput::make('value') - ->label('Option Value') - ->required() - ->columnSpan(2) - ->suffixAction( - \Filament\Forms\Components\Actions\Action::make('regenerate_value') - ->icon('heroicon-o-arrow-path') - ->tooltip('Regenerate from Option Label') - ->action(function (callable $set, callable $get) { - $label = $get('label'); - if (!empty($label)) { - $slug = \Illuminate\Support\Str::slug($label, '-'); - $set('value', $slug); - } - }) - ), - ]) - ->columns(2) - ->defaultItems(1) - ->addActionLabel('Add Option') - ->reorderableWithButtons() - ->collapsible() - ->itemLabel(fn(array $state): ?string => $state['label'] ?? 'Option') - ->disabled($disabled) - ->minItems(1), + SchemaHelper::getOptionsDefaultSelectedSelect($disabled), + SchemaHelper::getOptionsRepeater($disabled), ]) ->columns(1), ]; @@ -143,11 +90,11 @@ public function formElement(): MorphOne public function getData(): array { return [ - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, - 'defaultSelected' => $this->defaultSelected, - 'labelPosition' => $this->labelPosition, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, + 'default_selected' => $this->default_selected, + 'label_position' => $this->label_position, 'orientation' => $this->orientation, ]; } @@ -166,10 +113,10 @@ public function options(): MorphMany public static function getDefaultData(): array { return [ - 'hideLabel' => false, - 'labelText' => '', - 'enableVarSub' => false, - 'labelPosition' => 'right', + 'hide_label' => false, + 'label_text' => '', + 'enable_var_sub' => false, + 'label_position' => 'right', 'orientation' => 'vertical', 'options' => [ ['label' => 'True', 'value' => 'true'], diff --git a/app/Models/FormBuilding/SelectInputFormElement.php b/app/Models/FormBuilding/SelectInputFormElement.php index 3d703242..3ba063a2 100644 --- a/app/Models/FormBuilding/SelectInputFormElement.php +++ b/app/Models/FormBuilding/SelectInputFormElement.php @@ -18,20 +18,20 @@ class SelectInputFormElement extends Model use HasFactory, SoftDeletes; protected $fillable = [ - 'labelText', - 'hideLabel', - 'enableVarSub', - 'defaultSelected', + 'label_text', + 'hide_label', + 'enable_var_sub', + 'default_selected', ]; protected $casts = [ - 'hideLabel' => 'boolean', + 'hide_label' => 'boolean', ]; protected $attributes = [ - 'hideLabel' => false, - 'labelText' => '', - 'defaultSelected' => null, + 'hide_label' => false, + 'label_text' => '', + 'default_selected' => null, ]; /** @@ -40,69 +40,11 @@ class SelectInputFormElement extends Model public static function getFilamentSchema(bool $disabled = false): array { return [ - Fieldset::make('Field Label') - ->schema([ - SchemaHelper::getLabelTextField($disabled)->required(), - SchemaHelper::getEnableVariableSubstitutionToggle($disabled), - SchemaHelper::getHideLabelToggle($disabled), - ]) - ->columns(1), + SchemaHelper::getCommonCarbonFields($disabled, true), Fieldset::make('Values') ->schema([ - Select::make('elementable_data.defaultSelected') - ->label('Default Selected Value') - ->options(function (callable $get) { - $options = $get('elementable_data.options') ?? []; - $selectOptions = []; - foreach ($options as $option) { - if (!empty($option['value'])) { - $selectOptions[$option['value']] = $option['label'] ?? $option['value']; - } - } - return $selectOptions; - }) - ->disabled($disabled), - Repeater::make('elementable_data.options') - ->label('Options') - ->schema([ - TextInput::make('label') - ->label('Option Label') - ->required() - ->columnSpan(2) - ->autocomplete(false) - ->live(onBlur: true) - ->afterStateUpdated(function (callable $set, callable $get, $state) { - $value = $get('value'); - if (empty($value) && !empty($state)) { - $slug = \Illuminate\Support\Str::slug($state, '-'); - $set('value', $slug); - } - }), - TextInput::make('value') - ->label('Option Value') - ->required() - ->columnSpan(2) - ->suffixAction( - \Filament\Forms\Components\Actions\Action::make('regenerate_value') - ->icon('heroicon-o-arrow-path') - ->tooltip('Regenerate from Option Label') - ->action(function (callable $set, callable $get) { - $label = $get('label'); - if (!empty($label)) { - $slug = \Illuminate\Support\Str::slug($label, '-'); - $set('value', $slug); - } - }) - ), - ]) - ->columns(2) - ->defaultItems(1) - ->addActionLabel('Add Option') - ->reorderableWithButtons() - ->collapsible() - ->itemLabel(fn(array $state): ?string => $state['label'] ?? 'Option') - ->disabled($disabled) - ->minItems(1), + SchemaHelper::getOptionsDefaultSelectedSelect($disabled), + SchemaHelper::getOptionsRepeater($disabled), ]) ->columns(1), ]; @@ -122,10 +64,10 @@ public function formElement(): MorphOne public function getData(): array { return [ - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, - 'defaultSelected' => $this->defaultSelected, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, + 'default_selected' => $this->default_selected, ]; } @@ -143,10 +85,10 @@ public function options(): MorphMany public static function getDefaultData(): array { return [ - 'hideLabel' => false, - 'labelText' => '', - 'enableVarSub' => false, - 'defaultSelected' => null, + 'hide_label' => false, + 'label_text' => '', + 'enable_var_sub' => false, + 'default_selected' => null, 'options' => [ ['label' => 'True', 'value' => 'true'], ['label' => 'False', 'value' => 'false'], diff --git a/app/Models/FormBuilding/TextInputFormElement.php b/app/Models/FormBuilding/TextInputFormElement.php index 8c0d17a0..511a98eb 100644 --- a/app/Models/FormBuilding/TextInputFormElement.php +++ b/app/Models/FormBuilding/TextInputFormElement.php @@ -17,24 +17,24 @@ class TextInputFormElement extends Model protected $fillable = [ 'placeholder', - 'labelText', - 'hideLabel', - 'enableVarSub', - 'maskType', + 'label_text', + 'hide_label', + 'enable_var_sub', + 'mask_type', 'mask', - 'maskErrorMessage', - 'maxCount', - 'defaultValue', + 'mask_error_message', + 'max_count', + 'default_value', ]; protected $casts = [ - 'hideLabel' => 'boolean', - 'maxCount' => 'integer', + 'hide_label' => 'boolean', + 'max_count' => 'integer', ]; protected $attributes = [ - 'hideLabel' => false, - 'maskType' => 'custom', + 'hide_label' => false, + 'mask_type' => 'custom', ]; /** @@ -42,53 +42,53 @@ class TextInputFormElement extends Model */ public static function getFilamentSchema(bool $disabled = false): array { - return array_merge( + return [ SchemaHelper::getCommonCarbonFields($disabled), - [ - Fieldset::make('Value') - ->schema([ - SchemaHelper::getPlaceholderTextField($disabled), - TextInput::make('elementable_data.defaultValue') - ->label('Default Value') - ->disabled($disabled), - TextInput::make('elementable_data.maxCount') - ->label('Maximum Character Count') - ->numeric() - ->disabled($disabled), - ToggleButtons::make('elementable_data.maskType') - ->label('Input Mask Type') - ->options([ - 'email' => 'Email', - 'phone' => 'Phone', - 'postal' => 'Postal Code', - 'custom' => 'Custom', - ]) - ->inline() - ->live() - ->afterStateUpdated(function ($state, callable $set) { - $maskPatterns = [ - 'email' => '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$', - 'phone' => '### ###-####', - 'postal' => '@#@ #@#', - 'custom' => '', - ]; - $set('elementable_data.mask', $maskPatterns[$state] ?? ''); - }), - TextInput::make('elementable_data.mask') - ->label('Input Mask') - ->autocomplete(false) - ->hint('Supports Maska syntax, regular expressions, or character classes like "a-zA-Z0-9"') - ->disabled($disabled), - TextInput::make('elementable_data.maskErrorMessage') - ->label('Validation Message') - ->placeholder('e.g. Only letters and spaces are allowed') - ->hint('Displayed when input doesn\'t match the mask') - ->visible(fn($get) => $get('elementable_data.maskType') === 'custom') - ->disabled($disabled), - ]) - ->columns(1), - ] - ); + Fieldset::make('Value') + ->schema([ + SchemaHelper::getPlaceholderTextField($disabled), + TextInput::make('elementable_data.default_value') + ->label('Default Value') + ->maxLength(255) + ->disabled($disabled), + TextInput::make('elementable_data.max_count') + ->label('Maximum Character Count') + ->numeric() + ->disabled($disabled), + ToggleButtons::make('elementable_data.mask_type') + ->label('Input Mask Type') + ->options([ + 'email' => 'Email', + 'phone' => 'Phone', + 'postal' => 'Postal Code', + 'custom' => 'Custom', + ]) + ->inline() + ->live() + ->afterStateUpdated(function ($state, callable $set) { + $maskPatterns = [ + 'email' => '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$', + 'phone' => '### ###-####', + 'postal' => '@#@ #@#', + 'custom' => '', + ]; + $set('elementable_data.mask', $maskPatterns[$state] ?? ''); + }), + TextInput::make('elementable_data.mask') + ->label('Input Mask') + ->autocomplete(false) + ->maxLength(255) + ->hint('Supports Maska syntax, regular expressions, or character classes like "a-zA-Z0-9"') + ->disabled($disabled), + TextInput::make('elementable_data.mask_error_message') + ->label('Validation Message') + ->placeholder('e.g. Only letters and spaces are allowed') + ->hint('Displayed when input doesn\'t match the mask') + ->visible(fn($get) => $get('elementable_data.mask_type') === 'custom') + ->disabled($disabled), + ]) + ->columns(1), + ]; } /** @@ -106,13 +106,13 @@ public function getData(): array { return [ 'placeholder' => $this->placeholder, - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, 'mask' => $this->mask, - 'maskErrorMessage' => $this->maskErrorMessage, - 'maxCount' => $this->maxCount, - 'defaultValue' => $this->defaultValue, + 'mask_error_message' => $this->mask_error_message, + 'max_count' => $this->max_count, + 'default_value' => $this->default_value, ]; } @@ -123,13 +123,13 @@ public static function getDefaultData(): array { return [ 'placeholder' => '', - 'labelText' => '', - 'hideLabel' => false, - 'enableVarSub' => false, + 'label_text' => '', + 'hide_label' => false, + 'enable_var_sub' => false, 'mask' => '', - 'maskErrorMessage' => '', - 'maxCount' => null, - 'defaultValue' => '', + 'mask_error_message' => '', + 'max_count' => null, + 'default_value' => '', ]; } } diff --git a/app/Models/FormBuilding/TextareaInputFormElement.php b/app/Models/FormBuilding/TextareaInputFormElement.php index 708dc9d7..940fb90e 100644 --- a/app/Models/FormBuilding/TextareaInputFormElement.php +++ b/app/Models/FormBuilding/TextareaInputFormElement.php @@ -16,24 +16,24 @@ class TextareaInputFormElement extends Model protected $fillable = [ 'placeholder', - 'labelText', - 'hideLabel', - 'enableVarSub', + 'label_text', + 'hide_label', + 'enable_var_sub', 'rows', 'cols', - 'maxCount', - 'defaultValue', + 'max_count', + 'default_value', ]; protected $casts = [ - 'hideLabel' => 'boolean', + 'hide_label' => 'boolean', 'rows' => 'integer', 'cols' => 'integer', - 'maxCount' => 'integer', + 'max_count' => 'integer', ]; protected $attributes = [ - 'hideLabel' => false, + 'hide_label' => false, 'rows' => 3, ]; @@ -42,12 +42,11 @@ class TextareaInputFormElement extends Model */ public static function getFilamentSchema(bool $disabled = false): array { - return array_merge( + return [ SchemaHelper::getCommonCarbonFields($disabled), - [ - Fieldset::make('Value') - ->schema([ - SchemaHelper::getPlaceholderTextField($disabled), + Fieldset::make('Value') + ->schema([ + SchemaHelper::getPlaceholderTextField($disabled), TextInput::make('elementable_data.rows') ->label('Number of Rows') ->numeric() @@ -57,17 +56,17 @@ public static function getFilamentSchema(bool $disabled = false): array ->label('Number of Columns') ->numeric() ->disabled($disabled), - TextInput::make('elementable_data.maxCount') + TextInput::make('elementable_data.max_count') ->label('Maximum Character Count') ->numeric() ->disabled($disabled), - TextInput::make('elementable_data.defaultValue') + TextInput::make('elementable_data.default_value') ->label('Default Value') + ->maxLength(255) ->disabled($disabled), - ]) - ->columns(1), - ] - ); + ]) + ->columns(1), + ]; } /** @@ -85,13 +84,13 @@ public function getData(): array { return [ 'placeholder' => $this->placeholder, - 'labelText' => $this->labelText, - 'hideLabel' => $this->hideLabel, - 'enableVarSub' => $this->enableVarSub, + 'label_text' => $this->label_text, + 'hide_label' => $this->hide_label, + 'enable_var_sub' => $this->enable_var_sub, 'rows' => $this->rows, 'cols' => $this->cols, - 'maxCount' => $this->maxCount, - 'defaultValue' => $this->defaultValue, + 'max_count' => $this->max_count, + 'default_value' => $this->default_value, ]; } @@ -102,13 +101,13 @@ public static function getDefaultData(): array { return [ 'placeholder' => '', - 'labelText' => '', - 'hideLabel' => false, - 'enableVarSub' => false, + 'label_text' => '', + 'hide_label' => false, + 'enable_var_sub' => false, 'rows' => 3, 'cols' => null, - 'maxCount' => null, - 'defaultValue' => '', + 'max_count' => null, + 'default_value' => '', ]; } } diff --git a/app/Policies/UserTypePolicy.php b/app/Policies/UserTypePolicy.php index 84bed4c7..06e7ed1d 100644 --- a/app/Policies/UserTypePolicy.php +++ b/app/Policies/UserTypePolicy.php @@ -3,8 +3,7 @@ namespace App\Policies; use App\Models\User; -use App\Models\UserType; -use Illuminate\Auth\Access\Response; +use App\Models\FormMetadata\UserType; class UserTypePolicy { diff --git a/app/Services/FormVersionJsonService.php b/app/Services/FormVersionJsonService.php index 8cafddc6..fb9c831b 100644 --- a/app/Services/FormVersionJsonService.php +++ b/app/Services/FormVersionJsonService.php @@ -148,31 +148,19 @@ protected function getStylesPreMigration(FormVersion $formVersion): array $key = $sheet->id ? ('style:' . $sheet->id) : null; if ($key && isset($added[$key])) continue; - $css = $sheet->getCssContent() ?? ''; if ($css !== '') { - $webCss .= ($webCss !== '' ? "\n\n" : '') - . "/* Attached stylesheet */\n" - . $css; + $webCss .= ($webCss !== '' ? "\n\n" : '') . "/* Attached stylesheet */\n" . $css; } if ($key) $added[$key] = true; } - if ($webCss !== '') { - $styles[] = [ - 'type' => 'web', - 'content' => $webCss - ]; + $styles[] = ['type' => 'web', 'content' => $webCss]; } - if ($formVersion->pdfStyleSheet) { - $styles[] = [ - 'type' => 'pdf', - 'content' => $formVersion->pdfStyleSheet->getCssContent() - ]; + $styles[] = ['type' => 'pdf', 'content' => $formVersion->pdfStyleSheet->getCssContent()]; } - return $styles; } @@ -200,26 +188,18 @@ protected function getScriptsPreMigration(FormVersion $formVersion): array $js = $script->getJsContent() ?? ''; if ($js !== '') { - $webJs .= ($webJs !== '' ? "\n\n" : '') - . "/* Attached form script */\n" - . $js; + $webJs .= ($webJs !== '' ? "\n\n" : '') . "/* Attached form script */\n" . $js; } if ($key) $added[$key] = true; } if ($webJs !== '') { - $scripts[] = [ - 'type' => 'web', - 'content' => $webJs - ]; + $scripts[] = ['type' => 'web', 'content' => $webJs]; } if ($formVersion->pdfFormScript) { - $scripts[] = [ - 'type' => 'pdf', - 'content' => $formVersion->pdfFormScript->getJsContent() - ]; + $scripts[] = ['type' => 'pdf', 'content' => $formVersion->pdfFormScript->getJsContent()]; } return $scripts; @@ -298,6 +278,7 @@ protected function getScripts(FormVersion $formVersion): array 'filename' => $formVersion->pdfFormScript->filename, 'content' => $formVersion->pdfFormScript->getJsContent() ?? '' ]; + if ($formVersion->pdfFormScript->id) { $added['script:' . $formVersion->pdfFormScript->id] = true; } @@ -316,8 +297,9 @@ protected function getScripts(FormVersion $formVersion): array 'filename' => $script->filename, 'content' => $script->getJsContent() ?? '' ]; - if ($key) + if ($key) { $added[$key] = true; + } } return $scripts; @@ -337,8 +319,7 @@ protected function getElements(FormVersion $formVersion): array // Get root elements (elements without a parent - parent_id is -1 for root elements) $rootElements = $formVersion->formElements() ->where(function ($query) { - $query->whereNull('parent_id') - ->orWhere('parent_id', -1); + $query->whereNull('parent_id')->orWhere('parent_id', -1); }) ->orderBy('order') ->get(); @@ -350,8 +331,7 @@ protected function getElements(FormVersion $formVersion): array protected function getTags(FormElement $element): array { - $tags = $element->tags->pluck('filename', 'id')->toArray(); - return $tags; + return $element->tags->pluck('filename', 'id')->toArray(); } protected function transformElement(FormElement $element): array @@ -365,10 +345,10 @@ protected function transformElement(FormElement $element): array 'name' => $element->name, 'description' => $element->description, 'help_text' => $element->help_text, - 'is_required' => $element->is_required, 'visible_web' => $element->visible_web, 'visible_pdf' => $element->visible_pdf, - 'is_read_only' => $element->is_read_only && $element->custom_read_only ? $element->custom_read_only : $element->is_read_only, + 'is_required' => $element->is_required, + 'is_read_only' => $element->is_read_only, 'save_on_submit' => $element->save_on_submit, 'order' => $element->order, 'tags' => $this->getTags($element), @@ -433,8 +413,7 @@ protected function transformElementsToPreMigrationFormat(FormVersion $formVersio // Get root elements (elements without a parent - parent_id is -1 for root elements) $rootElements = $formVersion->formElements() ->where(function ($query) { - $query->whereNull('parent_id') - ->orWhere('parent_id', -1); + $query->whereNull('parent_id')->orWhere('parent_id', -1); }) ->with(['elementable', 'dataBindings.formDataSource']) ->orderBy('order') @@ -484,21 +463,15 @@ protected function transformContainerElement(FormElement $element, array $elemen $elementData['containerId'] = (string) ($element->id ?? ''); $elementData['clear_button'] = false; - $elementData['codeContext'] = [ - 'name' => $this->generateCodeContextName($element->name ?? 'container') - ]; + $elementData['codeContext'] = ['name' => $this->generateCodeContextName($element->name ?? 'container')]; $elementData['attributes'] = $this->remapAttributes($this->getElementAttributes($element)); $elementData['label'] = $elementData['attributes']['legend'] ?? null; $elementData['repeater'] = false; $elementData['repeaterLabel'] = null; - $elementData['pdfStyles'] = [ - 'display' => $element->visible_pdf ? null : 'none', - ]; - $elementData['webStyles'] = [ - 'display' => $element->visible_web ? null : 'none', - ]; + $elementData['pdfStyles'] = ['display' => $this->isActive($element->visible_pdf) ? null : 'none']; + $elementData['webStyles'] = ['display' => $this->isActive($element->visible_web) ? null : 'none']; // Add validation rules $validation = $this->transformValidationRules($element); @@ -540,17 +513,11 @@ protected function transformRepeatableContainerAsGroup(FormElement $element, arr $elementData['minRepeats'] = $element->elementable?->min_repeats ?? null; $elementData['maxRepeats'] = $element->elementable?->max_repeats ?? null; $elementData['clear_button'] = $element->elementable?->clear_button ?? false; - $elementData['codeContext'] = [ - 'name' => $this->generateCodeContextName($element->name ?? 'group') - ]; + $elementData['codeContext'] = ['name' => $this->generateCodeContextName($element->name ?? 'group')]; // Add styles - $elementData['pdfStyles'] = [ - 'display' => $element->visible_pdf ? null : 'none', - ]; - $elementData['webStyles'] = [ - 'display' => $element->visible_web ? null : 'none', - ]; + $elementData['pdfStyles'] = ['display' => $this->isActive($element->visible_pdf) ? null : 'none']; + $elementData['webStyles'] = ['display' => $this->isActive($element->visible_web) ? null : 'none']; // Add validation rules $validation = $this->transformValidationRules($element); @@ -578,15 +545,11 @@ protected function transformRepeatableContainerAsGroup(FormElement $element, arr $fields = []; if ($children->count() > 0) { - $fields = $children->map(function (FormElement $child) { - return $this->transformElementToPreMigrationFormat($child); - })->toArray(); + $fields = $children->map(fn(FormElement $child) => $this->transformElementToPreMigrationFormat($child))->toArray(); } // Create groupItems structure expected by the renderer - $elementData['groupItems'] = [ - ['fields' => $fields] - ]; + $elementData['groupItems'] = [['fields' => $fields]]; // Add container-specific attributes $attributes = $this->getElementAttributes($element); @@ -609,17 +572,11 @@ protected function transformGroupElement(FormElement $element, array $elementDat $elementData['minRepeats'] = $element->elementable?->min_repeats ?? null; $elementData['maxRepeats'] = $element->elementable?->max_repeats ?? null; $elementData['clear_button'] = $element->elementable?->clear_button ?? false; - $elementData['codeContext'] = [ - 'name' => $this->generateCodeContextName($element->name ?? 'group') - ]; + $elementData['codeContext'] = ['name' => $this->generateCodeContextName($element->name ?? 'group')]; // Add styles - $elementData['pdfStyles'] = [ - 'display' => $element->visible_pdf ? null : 'none', - ]; - $elementData['webStyles'] = [ - 'display' => $element->visible_web ? null : 'none', - ]; + $elementData['pdfStyles'] = ['display' => $this->isActive($element->visible_pdf) ? null : 'none']; + $elementData['webStyles'] = ['display' => $this->isActive($element->visible_web) ? null : 'none']; // Add validation rules $validation = $this->transformValidationRules($element); @@ -641,14 +598,9 @@ protected function transformGroupElement(FormElement $element, array $elementDat $fields = []; if ($children->count() > 0) { - $fields = $children->map(function (FormElement $child) { - return $this->transformElementToPreMigrationFormat($child); - })->toArray(); + $fields = $children->map(fn(FormElement $child) => $this->transformElementToPreMigrationFormat($child))->toArray(); } - - $elementData['groupItems'] = [ - ['fields' => $fields] - ]; + $elementData['groupItems'] = [['fields' => $fields]]; // Add group-specific attributes $attributes = $this->getElementAttributes($element); @@ -665,23 +617,14 @@ protected function transformStandardElement(FormElement $element, array $element $elementData['attributes'] = $this->remapAttributes($this->getElementAttributes($element)); // Basic properties for all standard elements $attributes = $this->getElementAttributes($element); - if (isset($attributes['hideLabel']) && $attributes['hideLabel']) { - $elementData['label'] = ''; - } else { - $elementData['label'] = $attributes['labelText'] ?? ''; - } + + $elementData['label'] = (isset($attributes['hide_label']) && $attributes['hide_label']) ? '' : ($attributes['label_text'] ?? ''); $elementData['helperText'] = $element->help_text; $elementData['mask'] = null; - $elementData['codeContext'] = [ - 'name' => $this->generateCodeContextName($element->name ?? 'field') - ]; + $elementData['codeContext'] = ['name' => $this->generateCodeContextName($element->name ?? 'field')]; - $elementData['pdfStyles'] = [ - 'display' => $element->visible_pdf ? null : 'none', - ]; - $elementData['webStyles'] = [ - 'display' => $element->visible_web ? null : 'none', - ]; + $elementData['pdfStyles'] = ['display' => $this->isActive($element->visible_pdf) ? null : 'none']; + $elementData['webStyles'] = ['display' => $this->isActive($element->visible_web) ? null : 'none']; // Add input type for specific elements if (in_array($originalType, ['text-input', 'text-area', 'textarea-input', 'number-input', 'date-input', 'date-select-input', 'file-input'])) { @@ -710,6 +653,7 @@ protected function transformStandardElement(FormElement $element, array $element $this->addElementSpecificProperties($elementData, $element, $originalType); $this->addElementStyles($elementData, $element); + return $elementData; } @@ -791,12 +735,10 @@ protected function addElementSpecificProperties(array &$elementData, FormElement $radioOptions = []; if ($element->elementable && method_exists($element->elementable, 'options')) { $optionsCollection = $element->elementable->options()->ordered()->get(); - $radioOptions = $optionsCollection->map(function ($option) { - return [ - 'value' => $option->value ?? '', - 'text' => $option->label ?? '', - ]; - })->toArray(); + $radioOptions = $optionsCollection->map(fn($option) => [ + 'value' => $option->value ?? '', + 'text' => $option->label ?? '', + ])->toArray(); } if (!empty($radioOptions)) { $elementData['listItems'] = $radioOptions; @@ -808,17 +750,14 @@ protected function addElementSpecificProperties(array &$elementData, FormElement $options = []; if ($element->elementable && method_exists($element->elementable, 'options')) { $optionsCollection = $element->elementable->options()->ordered()->get(); - $options = $optionsCollection->map(function ($option) { - return [ - 'name' => $option->label ?? '', - 'text' => $option->label ?? '', - 'value' => $option->value ?? '', - ]; - })->toArray(); + $options = $optionsCollection->map(fn($option) => [ + 'name' => $option->label ?? '', + 'text' => $option->label ?? '', + 'value' => $option->value ?? '', + ])->toArray(); } - if (!empty($options)) { + if (!empty($options)) $elementData['listItems'] = $options; - } break; case 'file': // Add file-specific properties @@ -946,18 +885,11 @@ protected function transformConditions(FormElement $element): array 'value' => $element->save_on_submit ? '{return true}' : '{return false}' ]; - if (!empty($element->custom_read_only) && $element->is_read_only) { - $conditions[] = [ - 'type' => 'readOnly', - 'value' => $element->custom_read_only - ]; - } else { - // If no custom read-only condition, use the default read-only state - $conditions[] = [ - 'type' => 'readOnly', - 'value' => $element->is_read_only ? '{return true}' : '{return false}' - ]; - } + $isReadOnly = $this->isActive($element->is_read_only); + $conditions[] = [ + 'type' => 'readOnly', + 'value' => $isReadOnly ? '{return true}' : '{return false}' + ]; return $conditions; } @@ -1120,9 +1052,7 @@ protected function convertKeyValueArrayToObject($keyValueArray): object */ protected function toCamelCase(string $str): string { - return preg_replace_callback('/_([a-z])/', function ($matches) { - return strtoupper($matches[1]); - }, $str); + return preg_replace_callback('/_([a-z])/', fn($matches) => strtoupper($matches[1]), $str); } /** @@ -1166,10 +1096,7 @@ protected function customAttributeMapping(string $key, $value): ?array case 'max': case 'min': case 'step': - if (is_numeric($value)) { - return [$key, (float) $value]; - } - return [$key, $value]; + return is_numeric($value) ? [$key, (float) $value] : [$key, $value]; case 'dateFormat': if ($value) { return ['dateFormat', DateSelectInputFormElement::convertToFlatpickrFormat($value)]; @@ -1241,4 +1168,12 @@ protected function remapAttributes(array $attributes): array { return $this->normalizeAttributes($attributes); } -} + + /** + * Check if a state string represents an "active" state (i.e., not 'never', '', null, or false). + */ + protected function isActive($state): bool + { + return !in_array($state, FormElement::getActiveToggleButtonStates(), true); + } +} \ No newline at end of file diff --git a/database/migrations/2026_06_18_214659_add_visible_when_toggles.php b/database/migrations/2026_06_18_214659_add_visible_when_toggles.php new file mode 100644 index 00000000..f93d3b11 --- /dev/null +++ b/database/migrations/2026_06_18_214659_add_visible_when_toggles.php @@ -0,0 +1,96 @@ +columns as $column) { + $table->string("{$column}_new")->nullable()->after($column); + } + }); + + // Step 2: Migrate data: true → 'always', false/null → null + foreach ($this->columns as $column) { + DB::table('form_elements')->chunkById(1000, function ($rows) use ($column) { + foreach ($rows as $row) { + $newValue = match ($row->{$column}) { + true => 'always', + default => null, + }; + + DB::table('form_elements') + ->where('id', $row->id) + ->update(["{$column}_new" => $newValue]); + } + }); + } + + // Step 3: Drop old boolean columns + Schema::table('form_elements', function (Blueprint $table) { + foreach ($this->columns as $column) { + $table->dropColumn($column); + } + }); + + // Step 4: Rename new columns to original names + Schema::table('form_elements', function (Blueprint $table) { + foreach ($this->columns as $column) { + $table->renameColumn("{$column}_new", $column); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Step 1: Add temporary boolean columns + Schema::table('form_elements', function (Blueprint $table) { + foreach ($this->columns as $column) { + $table->boolean("{$column}_new")->default(false)->after($column); + } + }); + + // Step 2: Reverse migrate: 'always' → true, null → false + foreach ($this->columns as $column) { + DB::table('form_elements')->chunkById(1000, function ($rows) use ($column) { + foreach ($rows as $row) { + $newValue = $row->{$column} === 'always'; + + DB::table('form_elements') + ->where('id', $row->id) + ->update(["{$column}_new" => $newValue]); + } + }); + } + + // Step 3: Drop enum columns + Schema::table('form_elements', function (Blueprint $table) { + foreach ($this->columns as $column) { + $table->dropColumn($column); + } + }); + + // Step 4: Rename boolean columns back + Schema::table('form_elements', function (Blueprint $table) { + foreach ($this->columns as $column) { + $table->renameColumn("{$column}_new", $column); + } + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_06_23_191830_remove_custom_read_only_from_form_elements.php b/database/migrations/2026_06_23_191830_remove_custom_read_only_from_form_elements.php new file mode 100644 index 00000000..c2b2199b --- /dev/null +++ b/database/migrations/2026_06_23_191830_remove_custom_read_only_from_form_elements.php @@ -0,0 +1,28 @@ +dropColumn('custom_read_only'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('form_elements', function (Blueprint $table) { + // Restoring as a nullable text column since it previously held JS scripts + $table->text('custom_read_only')->nullable()->after('is_read_only'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_07_02_150324_fix_toggle_defaults.php b/database/migrations/2026_07_02_150324_fix_toggle_defaults.php new file mode 100644 index 00000000..9bf45db7 --- /dev/null +++ b/database/migrations/2026_07_02_150324_fix_toggle_defaults.php @@ -0,0 +1,51 @@ +where(function ($query) use ($col) { + $query->whereNull($col) + ->orWhere($col, false) + ->orWhere($col, 'false'); + }) + ->update([$col => 'never']); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $columns = [ + 'visible_web', + 'visible_pdf', + 'is_required', + 'is_read_only', + ]; + + // Revert 'never' values back to null for the specified columns + foreach ($columns as $col) { + DB::table('form_elements') + ->where($col, 'never') + ->update([$col => null]); + } + } +}; \ No newline at end of file diff --git a/database/migrations/2026_07_07_211531_refactor_camel_case_columns.php b/database/migrations/2026_07_07_211531_refactor_camel_case_columns.php new file mode 100644 index 00000000..59bae275 --- /dev/null +++ b/database/migrations/2026_07_07_211531_refactor_camel_case_columns.php @@ -0,0 +1,153 @@ +renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('checkbox_group_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('defaultSelected', 'default_selected'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('checkbox_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('defaultChecked', 'default_checked'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('container_form_elements', function (Blueprint $table) { + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('currency_input_form_elements', function (Blueprint $table) { + $table->renameColumn('enableVarSub', 'enable_var_sub'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('defaultValue', 'default_value'); + $table->renameColumn('labelText', 'label_text'); + }); + Schema::table('date_select_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('minDate', 'min_date'); + $table->renameColumn('maxDate', 'max_date'); + $table->renameColumn('dateFormat', 'date_format'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('number_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('defaultValue', 'default_value'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + $table->renameColumn('maskType', 'mask_type'); + }); + Schema::table('radio_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('defaultSelected', 'default_selected'); + $table->renameColumn('labelPosition', 'label_position'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('select_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('defaultSelected', 'default_selected'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('textarea_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('maxCount', 'max_count'); + $table->renameColumn('defaultValue', 'default_value'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + }); + Schema::table('text_input_form_elements', function (Blueprint $table) { + $table->renameColumn('labelText', 'label_text'); + $table->renameColumn('hideLabel', 'hide_label'); + $table->renameColumn('maxCount', 'max_count'); + $table->renameColumn('defaultValue', 'default_value'); + $table->renameColumn('enableVarSub', 'enable_var_sub'); + $table->renameColumn('maskType', 'mask_type'); + $table->renameColumn('maskErrorMessage', 'mask_error_message'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('button_input_form_elements', function (Blueprint $table) { + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('checkbox_group_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('default_selected', 'defaultSelected'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('checkbox_input_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('default_checked', 'defaultChecked'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('container_form_elements', function (Blueprint $table) { + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('currency_input_form_elements', function (Blueprint $table) { + $table->renameColumn('enable_var_sub', 'enableVarSub'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('default_value', 'defaultValue'); + $table->renameColumn('label_text', 'labelText'); + }); + Schema::table('date_select_input_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('min_date', 'minDate'); + $table->renameColumn('max_date', 'maxDate'); + $table->renameColumn('date_format', 'dateFormat'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('number_input_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('default_value', 'defaultValue'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + $table->renameColumn('mask_type', 'maskType'); + }); + Schema::table('radio_input_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('default_selected', 'defaultSelected'); + $table->renameColumn('label_position', 'labelPosition'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('select_input_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('default_selected', 'defaultSelected'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('textarea_input_form_elements', function (Blueprint $table) { + $table->renameColumn('enable_var_sub', 'enableVarSub'); + }); + Schema::table('text_input_form_elements', function (Blueprint $table) { + $table->renameColumn('label_text', 'labelText'); + $table->renameColumn('hide_label', 'hideLabel'); + $table->renameColumn('max_count', 'maxCount'); + $table->renameColumn('default_value', 'defaultValue'); + $table->renameColumn('enable_var_sub', 'enableVarSub'); + $table->renameColumn('mask_type', 'maskType'); + $table->renameColumn('mask_error_message', 'maskErrorMessage'); + }); + } +}; diff --git a/database/seeders/BulkFormMetadataUpdateSeeder.php b/database/seeders/BulkFormMetadataUpdateSeeder.php new file mode 100644 index 00000000..8941293c --- /dev/null +++ b/database/seeders/BulkFormMetadataUpdateSeeder.php @@ -0,0 +1,910 @@ + 'HR3740E', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3740F', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3741', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3681', 'name' => 'Unknown'], + ['form_id' => 'HR3691F', 'name' => 'Unknown'], + ['form_id' => 'HR3704F', 'name' => 'Unknown'], + ['form_id' => 'HR4008', 'name' => 'Unknown'], + ['form_id' => 'HR3689E', 'name' => 'Unknown'], + ['form_id' => 'HR3687E-AT', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATa', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATb', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATd', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATf', 'name' => 'Unknown'], + ['form_id' => 'HR3260', 'name' => 'Adobe'], + ['form_id' => 'HR3599', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3219', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3262', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3534', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3233', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3236', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3249', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3334', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3335', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3555', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3633', 'name' => 'Unknown'], + ['form_id' => 'HR2247', 'name' => 'Unknown'], + ['form_id' => 'HR2766', 'name' => 'Unknown'], + ['form_id' => 'HR3352F', 'name' => 'Unknown'], + ['form_id' => 'HR3353F', 'name' => 'Unknown'], + ['form_id' => 'HR3354F', 'name' => 'Unknown'], + ['form_id' => 'HR3355F', 'name' => 'Unknown'], + ['form_id' => 'HR3356F', 'name' => 'Unknown'], + ['form_id' => 'HR3357F', 'name' => 'Unknown'], + ['form_id' => 'HR3358F', 'name' => 'Unknown'], + ['form_id' => 'HR3360F', 'name' => 'Unknown'], + ['form_id' => 'HR3361F', 'name' => 'Unknown'], + ['form_id' => 'HR3362F', 'name' => 'Unknown'], + ['form_id' => 'HR3363F', 'name' => 'Unknown'], + ['form_id' => 'HR3364F', 'name' => 'Unknown'], + ['form_id' => 'HR3365F', 'name' => 'Unknown'], + ['form_id' => 'HR3366F', 'name' => 'Unknown'], + ['form_id' => 'HR3367F', 'name' => 'Unknown'], + ['form_id' => 'HR3368F', 'name' => 'Unknown'], + ['form_id' => 'HR3369F', 'name' => 'Unknown'], + ['form_id' => 'HR3370F', 'name' => 'Unknown'], + ['form_id' => 'HR3372F', 'name' => 'Unknown'], + ['form_id' => 'HR3376F', 'name' => 'Unknown'], + ['form_id' => 'HR3379F', 'name' => 'Unknown'], + ['form_id' => 'HR3380F', 'name' => 'Unknown'], + ['form_id' => 'HR3381F', 'name' => 'Unknown'], + ['form_id' => 'HR3382F', 'name' => 'Unknown'], + ['form_id' => 'HR3383F', 'name' => 'Unknown'], + ['form_id' => 'HR3384F', 'name' => 'Unknown'], + ['form_id' => 'HR3385F', 'name' => 'Unknown'], + ['form_id' => 'HR3386F', 'name' => 'Unknown'], + ['form_id' => 'HR3387F', 'name' => 'Unknown'], + ['form_id' => 'HR3388F', 'name' => 'Unknown'], + ['form_id' => 'HR3389F', 'name' => 'Unknown'], + ['form_id' => 'HR3390F', 'name' => 'Unknown'], + ['form_id' => 'HR3391F', 'name' => 'Unknown'], + ['form_id' => 'HR3392F', 'name' => 'Unknown'], + ['form_id' => 'HR3393F', 'name' => 'Unknown'], + ['form_id' => 'HR3394F', 'name' => 'Unknown'], + ['form_id' => 'HR3396F', 'name' => 'Unknown'], + ['form_id' => 'HR3397F', 'name' => 'Unknown'], + ['form_id' => 'HR3398F', 'name' => 'Unknown'], + ['form_id' => 'HR3399F', 'name' => 'Unknown'], + ['form_id' => 'HR3400F', 'name' => 'Unknown'], + ['form_id' => 'HR3450F', 'name' => 'Unknown'], + ['form_id' => 'HR3451F', 'name' => 'Unknown'], + ['form_id' => 'HR3452F', 'name' => 'Unknown'], + ['form_id' => 'HR3453F', 'name' => 'Unknown'], + ['form_id' => 'HR3454F', 'name' => 'Unknown'], + ['form_id' => 'HR3455F', 'name' => 'Unknown'], + ['form_id' => 'HR3456F', 'name' => 'Unknown'], + ['form_id' => 'HR3458F', 'name' => 'Unknown'], + ['form_id' => 'HR3459F', 'name' => 'Unknown'], + ['form_id' => 'HR3484F', 'name' => 'Unknown'], + ['form_id' => 'HR3688F', 'name' => 'Unknown'], + ['form_id' => 'HR3692F', 'name' => 'Unknown'], + ['form_id' => 'HR3693F', 'name' => 'Unknown'], + ['form_id' => 'HR3694F', 'name' => 'Unknown'], + ['form_id' => 'HR3695F', 'name' => 'Unknown'], + ['form_id' => 'HR3696F', 'name' => 'Unknown'], + ['form_id' => 'HR3698E-AT', 'name' => 'Unknown'], + ['form_id' => 'HR3698F', 'name' => 'Unknown'], + ['form_id' => 'HR3699F', 'name' => 'Unknown'], + ['form_id' => 'HR3700F', 'name' => 'Unknown'], + ['form_id' => 'HR3701F', 'name' => 'Unknown'], + ['form_id' => 'HR3702F', 'name' => 'Unknown'], + ['form_id' => 'HR3703F', 'name' => 'Unknown'], + ['form_id' => 'HR3710F', 'name' => 'Unknown'], + ['form_id' => 'HR3655E', 'name' => 'Unknown'], + ['form_id' => 'CF0605_portal', 'name' => 'Unknown'], + ['form_id' => 'HR3492E', 'name' => 'Unknown'], + ['form_id' => 'HR3600', 'name' => 'Unknown'], + ['form_id' => 'HR3601E', 'name' => 'Unknown'], + ['form_id' => 'HR3602', 'name' => 'Unknown'], + ['form_id' => 'HR3603', 'name' => 'Unknown'], + ['form_id' => 'HR3604', 'name' => 'Unknown'], + ['form_id' => 'HR3605', 'name' => 'Unknown'], + ['form_id' => 'HR3606', 'name' => 'Unknown'], + ['form_id' => 'HR3607', 'name' => 'Unknown'], + ['form_id' => 'HR3608', 'name' => 'Unknown'], + ['form_id' => 'HR3609', 'name' => 'Unknown'], + ['form_id' => 'HR3610', 'name' => 'Unknown'], + ['form_id' => 'HR3611', 'name' => 'Unknown'], + ['form_id' => 'HR3612', 'name' => 'Unknown'], + ['form_id' => 'HR3613', 'name' => 'Unknown'], + ['form_id' => 'HR3654E', 'name' => 'Unknown'], + ['form_id' => 'HR3671', 'name' => 'Unknown'], + ['form_id' => 'HR3711', 'name' => 'Unknown'], + ['form_id' => 'HR3714E', 'name' => 'Unknown'], + ['form_id' => 'MW-001', 'name' => 'Unknown'], + ['form_id' => 'HR3488E', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3499E', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3527E', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3589A', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3589B', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3589C', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3653E', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3678B', 'name' => 'Adobe Livecycle'], + ['form_id' => 'HR3259', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3322', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3347', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3348', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3349', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3476', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3478', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3479', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3480', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3481', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3482', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3491', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3537', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3538', 'name' => 'Microsoft Word'], + ['form_id' => 'HR3539', 'name' => 'Microsoft Word'], + ['form_id' => 'HR2152', 'name' => 'Unknown'], + ['form_id' => 'HR2181A', 'name' => 'Unknown'], + ['form_id' => 'HR2181B', 'name' => 'Unknown'], + ['form_id' => 'HR2181C', 'name' => 'Unknown'], + ['form_id' => 'HR2192', 'name' => 'Unknown'], + ['form_id' => 'HR2193', 'name' => 'Unknown'], + ['form_id' => 'HR2274', 'name' => 'Unknown'], + ['form_id' => 'HR2340', 'name' => 'Unknown'], + ['form_id' => 'HR2365', 'name' => 'Unknown'], + ['form_id' => 'HR2366', 'name' => 'Unknown'], + ['form_id' => 'HR2368', 'name' => 'Unknown'], + ['form_id' => 'HR2527', 'name' => 'Unknown'], + ['form_id' => 'HR2545', 'name' => 'Unknown'], + ['form_id' => 'HR2609', 'name' => 'Unknown'], + ['form_id' => 'HR2663B', 'name' => 'Unknown'], + ['form_id' => 'HR2674', 'name' => 'Unknown'], + ['form_id' => 'HR2754', 'name' => 'Unknown'], + ['form_id' => 'HR2767', 'name' => 'Unknown'], + ['form_id' => 'HR2778', 'name' => 'Unknown'], + ['form_id' => 'HR2781', 'name' => 'Unknown'], + ['form_id' => 'HR2791', 'name' => 'Unknown'], + ['form_id' => 'HR2808', 'name' => 'Unknown'], + ['form_id' => 'HR2846', 'name' => 'Unknown'], + ['form_id' => 'HR2857', 'name' => 'Unknown'], + ['form_id' => 'HR2859', 'name' => 'Unknown'], + ['form_id' => 'HR2860', 'name' => 'Unknown'], + ['form_id' => 'HR2863B', 'name' => 'Unknown'], + ['form_id' => 'HR2948', 'name' => 'Unknown'], + ['form_id' => 'HR2992', 'name' => 'Unknown'], + ['form_id' => 'HR3014', 'name' => 'Unknown'], + ['form_id' => 'HR3030', 'name' => 'Unknown'], + ['form_id' => 'HR3031', 'name' => 'Unknown'], + ['form_id' => 'HR3045', 'name' => 'Unknown'], + ['form_id' => 'HR3051', 'name' => 'Unknown'], + ['form_id' => 'HR3073', 'name' => 'Unknown'], + ['form_id' => 'HR3132', 'name' => 'Unknown'], + ['form_id' => 'HR3136', 'name' => 'Unknown'], + ['form_id' => 'HR3137', 'name' => 'Unknown'], + ['form_id' => 'HR3161', 'name' => 'Unknown'], + ['form_id' => 'HR3186', 'name' => 'Unknown'], + ['form_id' => 'HR3201', 'name' => 'Unknown'], + ['form_id' => 'HR3207A', 'name' => 'Unknown'], + ['form_id' => 'HR3215', 'name' => 'Unknown'], + ['form_id' => 'HR3221', 'name' => 'Unknown'], + ['form_id' => 'HR3222', 'name' => 'Unknown'], + ['form_id' => 'HR3223', 'name' => 'Unknown'], + ['form_id' => 'HR3242', 'name' => 'Unknown'], + ['form_id' => 'HR3243', 'name' => 'Unknown'], + ['form_id' => 'HR3246', 'name' => 'Unknown'], + ['form_id' => 'HR3247', 'name' => 'Unknown'], + ['form_id' => 'HR3270', 'name' => 'Unknown'], + ['form_id' => 'HR3272', 'name' => 'Unknown'], + ['form_id' => 'HR3313', 'name' => 'Unknown'], + ['form_id' => 'HR3333', 'name' => 'Unknown'], + ['form_id' => 'HR3338', 'name' => 'Unknown'], + ['form_id' => 'HR3339', 'name' => 'Unknown'], + ['form_id' => 'HR3346', 'name' => 'Unknown'], + ['form_id' => 'HR3467', 'name' => 'Unknown'], + ['form_id' => 'HR3483', 'name' => 'Unknown'], + ['form_id' => 'HR3486', 'name' => 'Unknown'], + ['form_id' => 'HR3487', 'name' => 'Unknown'], + ['form_id' => 'HR3493E', 'name' => 'Unknown'], + ['form_id' => 'HR3494E', 'name' => 'Unknown'], + ['form_id' => 'HR3495E', 'name' => 'Unknown'], + ['form_id' => 'HR3496E', 'name' => 'Unknown'], + ['form_id' => 'HR3497E', 'name' => 'Unknown'], + ['form_id' => 'HR3498E', 'name' => 'Unknown'], + ['form_id' => 'HR3507', 'name' => 'Unknown'], + ['form_id' => 'HR3540', 'name' => 'Unknown'], + ['form_id' => 'HR3550', 'name' => 'Unknown'], + ['form_id' => 'HR3587', 'name' => 'Unknown'], + ['form_id' => 'HR3588', 'name' => 'Unknown'], + ['form_id' => 'HR3590', 'name' => 'Unknown'], + ['form_id' => 'HR3593', 'name' => 'Unknown'], + ['form_id' => 'HR3594', 'name' => 'Unknown'], + ['form_id' => 'HR3596', 'name' => 'Unknown'], + ['form_id' => 'HR3597', 'name' => 'Unknown'], + ['form_id' => 'HR3623', 'name' => 'Unknown'], + ['form_id' => 'HR3624E', 'name' => 'Unknown'], + ['form_id' => 'HR3625E', 'name' => 'Unknown'], + ['form_id' => 'HR3631', 'name' => 'Unknown'], + ['form_id' => 'HR3632', 'name' => 'Unknown'], + ['form_id' => 'HR3638', 'name' => 'Unknown'], + ['form_id' => 'HR3645', 'name' => 'Unknown'], + ['form_id' => 'HR3646', 'name' => 'Unknown'], + ['form_id' => 'HR3648', 'name' => 'Unknown'], + ['form_id' => 'HR3652', 'name' => 'Unknown'], + ['form_id' => 'HR3656', 'name' => 'Unknown'], + ['form_id' => 'HR3657E', 'name' => 'Unknown'], + ['form_id' => 'HR3660', 'name' => 'Unknown'], + ['form_id' => 'HR3661', 'name' => 'Unknown'], + ['form_id' => 'HR3663', 'name' => 'Unknown'], + ['form_id' => 'HR3664', 'name' => 'Unknown'], + ['form_id' => 'HR3665', 'name' => 'Unknown'], + ['form_id' => 'HR3666', 'name' => 'Unknown'], + ['form_id' => 'HR3668', 'name' => 'Unknown'], + ['form_id' => 'HR3668B', 'name' => 'Unknown'], + ['form_id' => 'HR3673', 'name' => 'Unknown'], + ['form_id' => 'HR3675', 'name' => 'Unknown'], + ['form_id' => 'HR3676', 'name' => 'Unknown'], + ['form_id' => 'HR3682', 'name' => 'Unknown'], + ['form_id' => 'HR3683', 'name' => 'Unknown'], + ['form_id' => 'HR3684', 'name' => 'Unknown'], + ['form_id' => 'HR3719', 'name' => 'Unknown'], + ['form_id' => 'HR3731', 'name' => 'Unknown'], + ['form_id' => 'HR3732', 'name' => 'Unknown'], + ['form_id' => 'HR3733', 'name' => 'Unknown'], + ['form_id' => 'HR3734', 'name' => 'Unknown'], + ['form_id' => 'HR4005', 'name' => 'Unknown'], + ['form_id' => 'HR4006', 'name' => 'Unknown'], + ['form_id' => 'HR4007', 'name' => 'Unknown'], + ['form_id' => 'HR4009', 'name' => 'Unknown'], + ['form_id' => 'HR4016', 'name' => 'Unknown'], + ['form_id' => 'HR4027', 'name' => 'Unknown'], + ['form_id' => 'HR4032', 'name' => 'Unknown'], + ['form_id' => 'HR3655F', 'name' => 'Unknown'], + ['form_id' => 'HR3697F', 'name' => 'Unknown'], + ]; + + foreach ($softwareSourceUpdates as $update) { + $form = Form::where('form_id', $update['form_id'])->first(); + $source = FormSoftwareSource::where('name', $update['name'])->first(); + + if ($form && $source) { + DB::table('form_software_source_form')->insertOrIgnore([ + 'form_id' => $form->id, + 'form_software_source_id' => $source->id, + ]); + } else { + // Log a warning if a title in your spreadsheet doesn't match the DB + if (!$form) { + Log::warning("BulkMetadataUpdateSeeder mismatch: Could not find Form '{$update['form_id']}'"); + } + if (!$source) { + Log::warning("BulkMetadataUpdateSeeder mismatch: Could not find Software Source '{$update['name']}'"); + } + } + } + + // ======================================================================== + // 2. FORM LOCATION UPDATES + // ======================================================================== + Log::warning("BulkMetadataUpdateSeeder: Beginning bulk update of form locations..."); + + $formLocationUpdates = [ + ['form_id' => 'CF0149', 'name' => 'Unknown'], + ['form_id' => 'CF2094', 'name' => 'Unknown'], + ['form_id' => 'HR3689E', 'name' => 'ICM'], + ['form_id' => 'CFL00540', 'name' => 'ICM'], + ['form_id' => 'CFL00544', 'name' => 'ICM'], + ['form_id' => 'HR3687E-AT', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATa', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATb', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATd', 'name' => 'Unknown'], + ['form_id' => 'HR3691E - ATf', 'name' => 'Unknown'], + ['form_id' => 'CF2447', 'name' => 'Unknown'], + ['form_id' => 'CF0725', 'name' => 'Unknown'], + ['form_id' => 'CF2601', 'name' => 'Unknown'], + ['form_id' => 'CF2156', 'name' => 'ICM'], + ['form_id' => 'CF2157', 'name' => 'ICM'], + ['form_id' => 'CF4087', 'name' => 'ICM'], + ['form_id' => 'CFL01015', 'name' => 'ICM'], + ['form_id' => 'CFL01048', 'name' => 'ICM'], + ['form_id' => 'CFL01071', 'name' => 'ICM'], + ['form_id' => 'CFL01072', 'name' => 'ICM'], + ['form_id' => 'CFL04556', 'name' => 'ICM'], + ['form_id' => 'HR3655E', 'name' => 'Unknown'], + ['form_id' => 'CF0605_portal', 'name' => 'Unknown'], + ['form_id' => 'HR3492E', 'name' => 'Unknown'], + ['form_id' => 'HR3600', 'name' => 'Unknown'], + ['form_id' => 'HR3601E', 'name' => 'Unknown'], + ['form_id' => 'HR3602', 'name' => 'Unknown'], + ['form_id' => 'HR3603', 'name' => 'Unknown'], + ['form_id' => 'HR3604', 'name' => 'Unknown'], + ['form_id' => 'HR3605', 'name' => 'Unknown'], + ['form_id' => 'HR3606', 'name' => 'Unknown'], + ['form_id' => 'HR3607', 'name' => 'Unknown'], + ['form_id' => 'HR3608', 'name' => 'Unknown'], + ['form_id' => 'HR3609', 'name' => 'Unknown'], + ['form_id' => 'HR3610', 'name' => 'Unknown'], + ['form_id' => 'HR3611', 'name' => 'Unknown'], + ['form_id' => 'HR3612', 'name' => 'Unknown'], + ['form_id' => 'HR3613', 'name' => 'Unknown'], + ['form_id' => 'HR3654E', 'name' => 'Unknown'], + ['form_id' => 'HR3671', 'name' => 'Unknown'], + ['form_id' => 'HR3711', 'name' => 'Unknown'], + ['form_id' => 'HR3714E', 'name' => 'Unknown'], + ['form_id' => 'MW-001', 'name' => 'Unknown'], + ['form_id' => 'CF0620', 'name' => 'Unknown'], + ['form_id' => 'CF3002', 'name' => 'Unknown'], + ['form_id' => 'CF4182', 'name' => 'Unknown'], + ['form_id' => 'CF4183', 'name' => 'Unknown'], + ['form_id' => 'CF4184', 'name' => 'Unknown'], + ['form_id' => 'CF4185', 'name' => 'Unknown'], + ['form_id' => 'Sample Non-Interactive', 'name' => 'Unknown'], + ['form_id' => 'CF0006', 'name' => 'Unknown'], + ['form_id' => 'Bus Pass Renewal Application', 'name' => 'Unknown'], + ['form_id' => 'CF0075', 'name' => 'Unknown'], + ['form_id' => 'CF0247', 'name' => 'Unknown'], + ['form_id' => 'CF0263', 'name' => 'Unknown'], + ['form_id' => 'CF0270', 'name' => 'Unknown'], + ['form_id' => 'CF0271', 'name' => 'Unknown'], + ['form_id' => 'CF0328', 'name' => 'Unknown'], + ['form_id' => 'CF0380', 'name' => 'Unknown'], + ['form_id' => 'CF0381', 'name' => 'Unknown'], + ['form_id' => 'CF0411A', 'name' => 'Unknown'], + ['form_id' => 'CF0453', 'name' => 'Unknown'], + ['form_id' => 'CF0603', 'name' => 'Unknown'], + ['form_id' => 'CF0610', 'name' => 'Unknown'], + ['form_id' => 'CF0635', 'name' => 'Unknown'], + ['form_id' => 'CF0636', 'name' => 'Unknown'], + ['form_id' => 'CF0637', 'name' => 'Unknown'], + ['form_id' => 'CF0638', 'name' => 'Unknown'], + ['form_id' => 'CF0639', 'name' => 'Unknown'], + ['form_id' => 'CF0652', 'name' => 'Unknown'], + ['form_id' => 'CF0654', 'name' => 'Unknown'], + ['form_id' => 'CF0654A', 'name' => 'Unknown'], + ['form_id' => 'CF0656', 'name' => 'Unknown'], + ['form_id' => 'CF0660', 'name' => 'Unknown'], + ['form_id' => 'CF0664', 'name' => 'Unknown'], + ['form_id' => 'CF0666', 'name' => 'Unknown'], + ['form_id' => 'CF0670', 'name' => 'Unknown'], + ['form_id' => 'CF0673', 'name' => 'Unknown'], + ['form_id' => 'CF0707', 'name' => 'Unknown'], + ['form_id' => 'CF0708', 'name' => 'Unknown'], + ['form_id' => 'CF0753', 'name' => 'Unknown'], + ['form_id' => 'CF0754', 'name' => 'Unknown'], + ['form_id' => 'CF0755', 'name' => 'Unknown'], + ['form_id' => 'CF0756', 'name' => 'Unknown'], + ['form_id' => 'CF0900', 'name' => 'Unknown'], + ['form_id' => 'CF0902B', 'name' => 'Unknown'], + ['form_id' => 'CF0903', 'name' => 'Unknown'], + ['form_id' => 'CF0912', 'name' => 'Unknown'], + ['form_id' => 'CF0927', 'name' => 'Unknown'], + ['form_id' => 'CF0950', 'name' => 'Unknown'], + ['form_id' => 'CF1001', 'name' => 'Unknown'], + ['form_id' => 'CF1005', 'name' => 'Unknown'], + ['form_id' => 'CF1005C', 'name' => 'Unknown'], + ['form_id' => 'CF1036', 'name' => 'Unknown'], + ['form_id' => 'CF1048', 'name' => 'Unknown'], + ['form_id' => 'CF1054', 'name' => 'Unknown'], + ['form_id' => 'CF1297', 'name' => 'Unknown'], + ['form_id' => 'CF1298', 'name' => 'Unknown'], + ['form_id' => 'CF1301', 'name' => 'Unknown'], + ['form_id' => 'CF1304', 'name' => 'Unknown'], + ['form_id' => 'CF1305', 'name' => 'Unknown'], + ['form_id' => 'CF1305A', 'name' => 'Unknown'], + ['form_id' => 'CF1305B', 'name' => 'Unknown'], + ['form_id' => 'CF1305C', 'name' => 'Unknown'], + ['form_id' => 'CF1305D', 'name' => 'Unknown'], + ['form_id' => 'CF1306', 'name' => 'Unknown'], + ['form_id' => 'CF1320', 'name' => 'Unknown'], + ['form_id' => 'CF1320A', 'name' => 'Unknown'], + ['form_id' => 'CF1321', 'name' => 'Unknown'], + ['form_id' => 'CF1324', 'name' => 'Unknown'], + ['form_id' => 'CF1326', 'name' => 'Unknown'], + ['form_id' => 'CF1325a', 'name' => 'Unknown'], + ['form_id' => 'CF1325b', 'name' => 'Unknown'], + ['form_id' => 'CF1330', 'name' => 'Unknown'], + ['form_id' => 'CF1331', 'name' => 'Unknown'], + ['form_id' => 'CF1332', 'name' => 'Unknown'], + ['form_id' => 'CF1335', 'name' => 'Unknown'], + ['form_id' => 'CF1336', 'name' => 'Unknown'], + ['form_id' => 'CF1345', 'name' => 'Unknown'], + ['form_id' => 'CF1346', 'name' => 'Unknown'], + ['form_id' => 'CF1369', 'name' => 'Unknown'], + ['form_id' => 'CF1370', 'name' => 'Unknown'], + ['form_id' => 'CF1371', 'name' => 'Unknown'], + ['form_id' => 'CF1372', 'name' => 'Unknown'], + ['form_id' => 'CF1470a', 'name' => 'Unknown'], + ['form_id' => 'CF1470b', 'name' => 'Unknown'], + ['form_id' => 'CF1703A', 'name' => 'Unknown'], + ['form_id' => 'CF1704A', 'name' => 'Unknown'], + ['form_id' => 'CF1706a', 'name' => 'Unknown'], + ['form_id' => 'CF1706b', 'name' => 'Unknown'], + ['form_id' => 'CF1900', 'name' => 'Unknown'], + ['form_id' => 'CF1901', 'name' => 'Unknown'], + ['form_id' => 'CF1902', 'name' => 'Unknown'], + ['form_id' => 'CF1903', 'name' => 'Unknown'], + ['form_id' => 'CF1904', 'name' => 'Unknown'], + ['form_id' => 'CF1905', 'name' => 'Unknown'], + ['form_id' => 'CF2002', 'name' => 'Unknown'], + ['form_id' => 'CF2003', 'name' => 'Unknown'], + ['form_id' => 'CF2004', 'name' => 'Unknown'], + ['form_id' => 'CF2007', 'name' => 'Unknown'], + ['form_id' => 'CF2040', 'name' => 'Unknown'], + ['form_id' => 'CF2040A', 'name' => 'Unknown'], + ['form_id' => 'CF2044A', 'name' => 'Unknown'], + ['form_id' => 'CF2045', 'name' => 'Unknown'], + ['form_id' => 'CF2185', 'name' => 'Unknown'], + ['form_id' => 'CF2343', 'name' => 'Unknown'], + ['form_id' => 'CF2512', 'name' => 'Unknown'], + ['form_id' => 'CF2514', 'name' => 'Unknown'], + ['form_id' => 'CF2515', 'name' => 'Unknown'], + ['form_id' => 'CF2568', 'name' => 'Unknown'], + ['form_id' => 'CF2600', 'name' => 'Unknown'], + ['form_id' => 'CF2644', 'name' => 'Unknown'], + ['form_id' => 'CF2655', 'name' => 'Unknown'], + ['form_id' => 'CF2657', 'name' => 'Unknown'], + ['form_id' => 'CF2657A', 'name' => 'Unknown'], + ['form_id' => 'CF2657B', 'name' => 'Unknown'], + ['form_id' => 'CF2657C', 'name' => 'Unknown'], + ['form_id' => 'CF2671', 'name' => 'Unknown'], + ['form_id' => 'CF2679', 'name' => 'Unknown'], + ['form_id' => 'CF2682', 'name' => 'Unknown'], + ['form_id' => 'CF2684', 'name' => 'Unknown'], + ['form_id' => 'CF2685', 'name' => 'Unknown'], + ['form_id' => 'CF2710', 'name' => 'Unknown'], + ['form_id' => 'CF2723', 'name' => 'Unknown'], + ['form_id' => 'CF2732', 'name' => 'Unknown'], + ['form_id' => 'CF2734', 'name' => 'Unknown'], + ['form_id' => 'CF2910A', 'name' => 'Unknown'], + ['form_id' => 'CF2932', 'name' => 'Unknown'], + ['form_id' => 'CF2960A', 'name' => 'Unknown'], + ['form_id' => 'CF2961', 'name' => 'Unknown'], + ['form_id' => 'CF2962', 'name' => 'Unknown'], + ['form_id' => 'CF2963', 'name' => 'Unknown'], + ['form_id' => 'CF3030', 'name' => 'Unknown'], + ['form_id' => 'CF3501A', 'name' => 'Unknown'], + ['form_id' => 'CF3700', 'name' => 'Unknown'], + ['form_id' => 'CF4070', 'name' => 'Unknown'], + ['form_id' => 'CF4071', 'name' => 'Unknown'], + ['form_id' => 'CF4072', 'name' => 'Unknown'], + ['form_id' => 'CF4076', 'name' => 'Unknown'], + ['form_id' => 'CF4084', 'name' => 'Unknown'], + ['form_id' => 'CF4085', 'name' => 'Unknown'], + ['form_id' => 'CF4086', 'name' => 'Unknown'], + ['form_id' => 'CF4092', 'name' => 'Unknown'], + ['form_id' => 'CF4101', 'name' => 'Unknown'], + ['form_id' => 'CF4102', 'name' => 'Unknown'], + ['form_id' => 'CF4103', 'name' => 'Unknown'], + ['form_id' => 'CF4104', 'name' => 'Unknown'], + ['form_id' => 'CF4105', 'name' => 'Unknown'], + ['form_id' => 'CF4106', 'name' => 'Unknown'], + ['form_id' => 'CF4107', 'name' => 'Unknown'], + ['form_id' => 'CF4108', 'name' => 'Unknown'], + ['form_id' => 'CF4109', 'name' => 'Unknown'], + ['form_id' => 'CF4110', 'name' => 'Unknown'], + ['form_id' => 'CF4111', 'name' => 'Unknown'], + ['form_id' => 'CF4114', 'name' => 'Unknown'], + ['form_id' => 'CF4156', 'name' => 'Unknown'], + ['form_id' => 'CF4160', 'name' => 'Unknown'], + ['form_id' => 'CF4181', 'name' => 'Unknown'], + ['form_id' => 'CF4112', 'name' => 'Unknown'], + ['form_id' => 'CF4113', 'name' => 'Unknown'], + ['form_id' => 'CF4144', 'name' => 'Unknown'], + ['form_id' => 'CF4145', 'name' => 'Unknown'], + ['form_id' => 'CF4146', 'name' => 'Unknown'], + ['form_id' => 'CFL000001AA', 'name' => 'Unknown'], + ['form_id' => 'CFL000010', 'name' => 'Unknown'], + ['form_id' => 'CFL00010', 'name' => 'Unknown'], + ['form_id' => 'CFL00543', 'name' => 'Unknown'], + ['form_id' => 'CFL01023', 'name' => 'Unknown'], + ['form_id' => 'CFL01024', 'name' => 'Unknown'], + ['form_id' => 'CFL01025', 'name' => 'Unknown'], + ['form_id' => 'CFL01026', 'name' => 'Unknown'], + ['form_id' => 'CFL01027', 'name' => 'Unknown'], + ['form_id' => 'CFL01028', 'name' => 'Unknown'], + ['form_id' => 'CFL01029', 'name' => 'Unknown'], + ['form_id' => 'CFL01030', 'name' => 'Unknown'], + ['form_id' => 'CFL01031', 'name' => 'Unknown'], + ['form_id' => 'CFL01032', 'name' => 'Unknown'], + ['form_id' => 'CFL01033', 'name' => 'Unknown'], + ['form_id' => 'CFL01034', 'name' => 'Unknown'], + ['form_id' => 'CFL01035', 'name' => 'Unknown'], + ['form_id' => 'CFL01036', 'name' => 'Unknown'], + ['form_id' => 'CFL01049', 'name' => 'Unknown'], + ['form_id' => 'CFL01050', 'name' => 'Unknown'], + ['form_id' => 'CFL01051', 'name' => 'Unknown'], + ['form_id' => 'CFL01052', 'name' => 'Unknown'], + ['form_id' => 'CFL01053', 'name' => 'Unknown'], + ['form_id' => 'CFL01054', 'name' => 'Unknown'], + ['form_id' => 'CFL01055', 'name' => 'Unknown'], + ['form_id' => 'CFL01056', 'name' => 'Unknown'], + ['form_id' => 'CFL01057', 'name' => 'Unknown'], + ['form_id' => 'CFL01058', 'name' => 'Unknown'], + ['form_id' => 'CFL01059', 'name' => 'Unknown'], + ['form_id' => 'CFL01060', 'name' => 'Unknown'], + ['form_id' => 'CFL01061', 'name' => 'Unknown'], + ['form_id' => 'CFL01062', 'name' => 'Unknown'], + ['form_id' => 'CFL01063', 'name' => 'Unknown'], + ['form_id' => 'CFL01064', 'name' => 'Unknown'], + ['form_id' => 'CFL01065', 'name' => 'Unknown'], + ['form_id' => 'CFL01066', 'name' => 'Unknown'], + ['form_id' => 'CFL01067', 'name' => 'Unknown'], + ['form_id' => 'CFL01068', 'name' => 'Unknown'], + ['form_id' => 'CFL01069', 'name' => 'Unknown'], + ['form_id' => 'CFL01070', 'name' => 'Unknown'], + ['form_id' => 'CFL02015', 'name' => 'Unknown'], + ['form_id' => 'CFL04555', 'name' => 'Unknown'], + ['form_id' => 'CFL04557', 'name' => 'Unknown'], + ['form_id' => 'CFL04558', 'name' => 'Unknown'], + ['form_id' => 'CFL04559', 'name' => 'Unknown'], + ['form_id' => 'HR0080P', 'name' => 'Unknown'], + ['form_id' => 'HR3374E', 'name' => 'Unknown'], + ['form_id' => 'HR3375E', 'name' => 'Unknown'], + ['form_id' => 'Trauma Brief Screen', 'name' => 'Unknown'], + ['form_id' => 'HR4029', 'name' => 'Unknown'], + ['form_id' => 'CF0643', 'name' => 'Unknown'], + ['form_id' => 'CF0911', 'name' => 'Unknown'], + ['form_id' => 'CF3505', 'name' => 'Unknown'], + ['form_id' => 'Coding Block', 'name' => 'Unknown'], + ['form_id' => 'Petty Cash Reconciliation Replenishment Report', 'name' => 'Unknown'], + ]; + + foreach ($formLocationUpdates as $update) { + $form = Form::where('form_id', $update['form_id'])->first(); + $location = FormLocation::where('name', $update['name'])->first(); + + if ($form && $location) { + DB::table('form_form_location')->insertOrIgnore([ + 'form_id' => $form->id, + 'form_location_id' => $location->id, + ]); + } else { + if (!$form) { + Log::warning("BulkMetadataUpdateSeeder mismatch: Could not find Form '{$update['form_id']}'"); + } + if (!$location) { + Log::warning("BulkMetadataUpdateSeeder mismatch: Could not find Location '{$update['name']}'"); + } + } + } + + // ======================================================================== + // 3. USER TYPE UPDATES + // ======================================================================== + + Log::warning("BulkMetadataUpdateSeeder: Beginning bulk update of form user types..."); + + $userTypeUpdates = [ + ['form_id' => 'CF0149', 'name' => 'Internal'], + ['form_id' => 'CF2094', 'name' => 'Internal'], + ['form_id' => 'HR3740E', 'name' => 'Internal'], + ['form_id' => 'HR3740F', 'name' => 'Internal'], + ['form_id' => 'HR3741', 'name' => 'Internal'], + ['form_id' => 'HR3681', 'name' => 'Internal'], + ['form_id' => 'HR3691F', 'name' => 'Internal'], + ['form_id' => 'HR3704F', 'name' => 'Internal'], + ['form_id' => 'HR4008', 'name' => 'Internal'], + ['form_id' => 'HR4041', 'name' => 'Internal'], + ['form_id' => 'HR4042', 'name' => 'Internal'], + ['form_id' => 'T5007', 'name' => 'Internal'], + ['form_id' => 'Decision Report Template', 'name' => 'Internal'], + ['form_id' => 'CF2186', 'name' => 'Internal'], + ['form_id' => 'CF0671', 'name' => 'Internal'], + ['form_id' => 'CF0711', 'name' => 'Internal'], + ['form_id' => 'CF1071', 'name' => 'Internal'], + ['form_id' => 'CF1072', 'name' => 'Internal'], + ['form_id' => 'CF2190', 'name' => 'Internal'], + ['form_id' => 'CF4177', 'name' => 'Internal'], + ['form_id' => 'CFDRT001', 'name' => 'Internal'], + ['form_id' => 'CFL00514', 'name' => 'Internal'], + ['form_id' => 'CFL01010', 'name' => 'Internal'], + ['form_id' => 'CFL01013', 'name' => 'Internal'], + ['form_id' => 'CFL01014', 'name' => 'Internal'], + ['form_id' => 'CFL01016', 'name' => 'Internal'], + ['form_id' => 'CFL01017', 'name' => 'Internal'], + ['form_id' => 'CFL01018', 'name' => 'Internal'], + ['form_id' => 'CFL01019', 'name' => 'Internal'], + ['form_id' => 'CFL01021', 'name' => 'Internal'], + ['form_id' => 'CFL01041', 'name' => 'Internal'], + ['form_id' => 'CFL01042', 'name' => 'Internal'], + ['form_id' => 'CFL02010', 'name' => 'Internal'], + ['form_id' => 'CFL02012', 'name' => 'Internal'], + ['form_id' => 'CFL02016', 'name' => 'Internal'], + ['form_id' => 'CFL02019', 'name' => 'Internal'], + ['form_id' => 'CFL02021', 'name' => 'Internal'], + ['form_id' => 'CFL04510', 'name' => 'Internal'], + ['form_id' => 'CFL04512', 'name' => 'Internal'], + ['form_id' => 'CFL04515', 'name' => 'Internal'], + ['form_id' => 'CFL04516', 'name' => 'Internal'], + ['form_id' => 'CFL04520', 'name' => 'Internal'], + ['form_id' => 'CFL04523', 'name' => 'Internal'], + ['form_id' => 'CFL04524', 'name' => 'Internal'], + ['form_id' => 'CFL04526', 'name' => 'Internal'], + ['form_id' => 'CFL04527', 'name' => 'Internal'], + ['form_id' => 'CFL04528', 'name' => 'Internal'], + ['form_id' => 'CFL04529', 'name' => 'Internal'], + ['form_id' => 'CFL04530', 'name' => 'Internal'], + ['form_id' => 'CFL04533', 'name' => 'Internal'], + ['form_id' => 'CFL04536', 'name' => 'Internal'], + ['form_id' => 'CFL04541', 'name' => 'Internal'], + ['form_id' => 'CFL04543', 'name' => 'Internal'], + ['form_id' => 'CFL04554', 'name' => 'Internal'], + ['form_id' => 'HR2848', 'name' => 'Internal'], + ['form_id' => 'HR2865', 'name' => 'Internal'], + ['form_id' => 'HR3460', 'name' => 'Internal'], + ['form_id' => 'HR3461', 'name' => 'Internal'], + ['form_id' => 'HR3462', 'name' => 'Internal'], + ['form_id' => 'HR3464', 'name' => 'Internal'], + ['form_id' => 'HR3466', 'name' => 'Internal'], + ['form_id' => 'HR3504', 'name' => 'Internal'], + ['form_id' => 'HR3627', 'name' => 'Internal'], + ['form_id' => 'HR3628', 'name' => 'Internal'], + ['form_id' => 'HR3629', 'name' => 'Internal'], + ['form_id' => 'HR3630', 'name' => 'Internal'], + ['form_id' => 'HR3688R', 'name' => 'Internal'], + ['form_id' => 'HR3701E', 'name' => 'Internal'], + ['form_id' => 'HR3702E', 'name' => 'Internal'], + ['form_id' => 'NOA GST', 'name' => 'Internal'], + ['form_id' => 'CF0025', 'name' => 'Internal'], + ['form_id' => 'CF2511', 'name' => 'Internal'], + ['form_id' => 'HR2916', 'name' => 'Internal'], + ['form_id' => 'HR3503', 'name' => 'Internal'], + ['form_id' => 'HR2892A', 'name' => 'Internal'], + ['form_id' => 'HR3687E', 'name' => 'Internal'], + ['form_id' => 'HR2384', 'name' => 'Internal'], + ['form_id' => 'CF8787', 'name' => 'Internal'], + ['form_id' => 'CF0640', 'name' => 'Internal'], + ['form_id' => 'HR3234', 'name' => 'Internal'], + ['form_id' => 'HR3689E', 'name' => 'Public'], + ['form_id' => 'CFL00540', 'name' => 'Public'], + ['form_id' => 'CFL00544', 'name' => 'Public'], + ['form_id' => 'HR3687E-AT', 'name' => 'Public'], + ['form_id' => 'HR3691E - ATa', 'name' => 'Public'], + ['form_id' => 'HR3691E - ATb', 'name' => 'Public'], + ['form_id' => 'HR3691E - ATd', 'name' => 'Public'], + ['form_id' => 'HR3691E - ATf', 'name' => 'Public'], + ['form_id' => 'CF2447', 'name' => 'Public'], + ['form_id' => 'CF0725', 'name' => 'Public'], + ['form_id' => 'CF2601', 'name' => 'Public'], + ['form_id' => 'HR3260', 'name' => 'Public'], + ['form_id' => 'HR3599', 'name' => 'Public'], + ['form_id' => 'HR3219', 'name' => 'Public'], + ['form_id' => 'HR3262', 'name' => 'Public'], + ['form_id' => 'HR3534', 'name' => 'Public'], + ['form_id' => 'HR3233', 'name' => 'Public'], + ['form_id' => 'HR3236', 'name' => 'Public'], + ['form_id' => 'HR3249', 'name' => 'Public'], + ['form_id' => 'HR3334', 'name' => 'Public'], + ['form_id' => 'HR3335', 'name' => 'Public'], + ['form_id' => 'HR3555', 'name' => 'Public'], + ['form_id' => 'HR3633', 'name' => 'Public'], + ['form_id' => 'HR2247', 'name' => 'Public'], + ['form_id' => 'HR2766', 'name' => 'Public'], + ['form_id' => 'HR3352F', 'name' => 'Public'], + ['form_id' => 'HR3353F', 'name' => 'Public'], + ['form_id' => 'HR3354F', 'name' => 'Public'], + ['form_id' => 'HR3355F', 'name' => 'Public'], + ['form_id' => 'HR3356F', 'name' => 'Public'], + ['form_id' => 'HR3357F', 'name' => 'Public'], + ['form_id' => 'HR3358F', 'name' => 'Public'], + ['form_id' => 'HR3360F', 'name' => 'Public'], + ['form_id' => 'HR3361F', 'name' => 'Public'], + ['form_id' => 'HR3362F', 'name' => 'Public'], + ['form_id' => 'HR3363F', 'name' => 'Public'], + ['form_id' => 'HR3364F', 'name' => 'Public'], + ['form_id' => 'HR3365F', 'name' => 'Public'], + ['form_id' => 'HR3366F', 'name' => 'Public'], + ['form_id' => 'HR3367F', 'name' => 'Public'], + ['form_id' => 'HR3368F', 'name' => 'Public'], + ['form_id' => 'HR3369F', 'name' => 'Public'], + ['form_id' => 'HR3370F', 'name' => 'Public'], + ['form_id' => 'HR3372F', 'name' => 'Public'], + ['form_id' => 'HR3376F', 'name' => 'Public'], + ['form_id' => 'HR3379F', 'name' => 'Public'], + ['form_id' => 'HR3380F', 'name' => 'Public'], + ['form_id' => 'HR3381F', 'name' => 'Public'], + ['form_id' => 'HR3382F', 'name' => 'Public'], + ['form_id' => 'HR3383F', 'name' => 'Public'], + ['form_id' => 'HR3384F', 'name' => 'Public'], + ['form_id' => 'HR3385F', 'name' => 'Public'], + ['form_id' => 'HR3386F', 'name' => 'Public'], + ['form_id' => 'HR3387F', 'name' => 'Public'], + ['form_id' => 'HR3388F', 'name' => 'Public'], + ['form_id' => 'HR3389F', 'name' => 'Public'], + ['form_id' => 'HR3390F', 'name' => 'Public'], + ['form_id' => 'HR3391F', 'name' => 'Public'], + ['form_id' => 'HR3392F', 'name' => 'Public'], + ['form_id' => 'HR3393F', 'name' => 'Public'], + ['form_id' => 'HR3394F', 'name' => 'Public'], + ['form_id' => 'HR3396F', 'name' => 'Public'], + ['form_id' => 'HR3397F', 'name' => 'Public'], + ['form_id' => 'HR3398F', 'name' => 'Public'], + ['form_id' => 'HR3399F', 'name' => 'Public'], + ['form_id' => 'HR3400F', 'name' => 'Public'], + ['form_id' => 'HR3450F', 'name' => 'Public'], + ['form_id' => 'HR3451F', 'name' => 'Public'], + ['form_id' => 'HR3452F', 'name' => 'Public'], + ['form_id' => 'HR3453F', 'name' => 'Public'], + ['form_id' => 'HR3454F', 'name' => 'Public'], + ['form_id' => 'HR3455F', 'name' => 'Public'], + ['form_id' => 'HR3456F', 'name' => 'Public'], + ['form_id' => 'HR3458F', 'name' => 'Public'], + ['form_id' => 'HR3459F', 'name' => 'Public'], + ['form_id' => 'HR3484F', 'name' => 'Public'], + ['form_id' => 'HR3688F', 'name' => 'Public'], + ['form_id' => 'HR3692F', 'name' => 'Public'], + ['form_id' => 'HR3693F', 'name' => 'Public'], + ['form_id' => 'HR3694F', 'name' => 'Public'], + ['form_id' => 'HR3695F', 'name' => 'Public'], + ['form_id' => 'HR3696F', 'name' => 'Public'], + ['form_id' => 'HR3698E-AT', 'name' => 'Public'], + ['form_id' => 'HR3698F', 'name' => 'Public'], + ['form_id' => 'HR3699F', 'name' => 'Public'], + ['form_id' => 'HR3700F', 'name' => 'Public'], + ['form_id' => 'HR3701F', 'name' => 'Public'], + ['form_id' => 'HR3702F', 'name' => 'Public'], + ['form_id' => 'HR3703F', 'name' => 'Public'], + ['form_id' => 'HR3710F', 'name' => 'Public'], + ['form_id' => 'CF1707', 'name' => 'Public'], + ['form_id' => 'CF2602', 'name' => 'Public'], + ['form_id' => 'CF4139', 'name' => 'Public'], + ['form_id' => 'CF4141', 'name' => 'Public'], + ['form_id' => 'vsa433', 'name' => 'Public'], + ['form_id' => 'CF0700', 'name' => 'Public'], + ['form_id' => 'CF0702', 'name' => 'Public'], + ['form_id' => 'CF1234', 'name' => 'Public'], + ['form_id' => 'CF1706', 'name' => 'Public'], + ['form_id' => 'Bus Pass Replacement Application', 'name' => 'Public'], + ['form_id' => 'HR0101', 'name' => 'Public'], + ['form_id' => 'HR3235', 'name' => 'Public'], + ['form_id' => 'HR2724', 'name' => 'Public'], + ['form_id' => 'CF2595', 'name' => 'Public'], + ['form_id' => 'CF2900', 'name' => 'Public'], + ['form_id' => 'CFL01020', 'name' => 'Public'], + ['form_id' => 'CFL00517', 'name' => 'Public'], + ['form_id' => 'CFL00519', 'name' => 'Public'], + ['form_id' => 'CFL00520', 'name' => 'Public'], + ['form_id' => 'CFL00522', 'name' => 'Public'], + ['form_id' => 'CFL00523', 'name' => 'Public'], + ['form_id' => 'CFL00524', 'name' => 'Public'], + ['form_id' => 'CFL00541', 'name' => 'Public'], + ['form_id' => 'CFL00542', 'name' => 'Public'], + ['form_id' => 'CFL00550', 'name' => 'Public'], + ['form_id' => 'CFL01022', 'name' => 'Public'], + ['form_id' => 'HR3700E', 'name' => 'Public'], + ['form_id' => 'HR3704E', 'name' => 'Public'], + ['form_id' => 'HR3710E', 'name' => 'Public'], + ['form_id' => 'CF0035', 'name' => 'Public'], + ['form_id' => 'CF0700A', 'name' => 'Public'], + ['form_id' => 'CF0701', 'name' => 'Public'], + ['form_id' => 'CF0711A', 'name' => 'Public'], + ['form_id' => 'CF0711B', 'name' => 'Public'], + ['form_id' => 'CF1630', 'name' => 'Public'], + ['form_id' => 'CF2151', 'name' => 'Public'], + ['form_id' => 'CF2159', 'name' => 'Public'], + ['form_id' => 'CF2164', 'name' => 'Public'], + ['form_id' => 'CF2201', 'name' => 'Public'], + ['form_id' => 'CF2593', 'name' => 'Public'], + ['form_id' => 'CF2615', 'name' => 'Public'], + ['form_id' => 'CF2631', 'name' => 'Public'], + ['form_id' => 'CF4051', 'name' => 'Public'], + ['form_id' => 'CF0906', 'name' => 'Public'], + ['form_id' => 'CF2933', 'name' => 'Public'], + ['form_id' => 'CFL00516', 'name' => 'Public'], + ['form_id' => 'CFL00518', 'name' => 'Public'], + ['form_id' => 'CFL00521', 'name' => 'Public'], + ['form_id' => 'CFL01040', 'name' => 'Public'], + ['form_id' => 'CFL01043', 'name' => 'Public'], + ['form_id' => 'CFL01044', 'name' => 'Public'], + ['form_id' => 'CFL01045', 'name' => 'Public'], + ['form_id' => 'CF0928', 'name' => 'Public'], + ['form_id' => 'CF0929', 'name' => 'Public'], + ['form_id' => 'CFL00510', 'name' => 'Public'], + ['form_id' => 'CFL00511', 'name' => 'Public'], + ['form_id' => 'CFL00513', 'name' => 'Public'], + ['form_id' => 'CFL00515', 'name' => 'Public'], + ['form_id' => 'HR3634', 'name' => 'Public'], + ['form_id' => 'HR3637', 'name' => 'Public'], + ['form_id' => 'HR3691E', 'name' => 'Public'], + ['form_id' => 'HR3692E', 'name' => 'Public'], + ['form_id' => 'HR3695AE', 'name' => 'Public'], + ['form_id' => 'HR3696E', 'name' => 'Public'], + ['form_id' => 'HR3690E', 'name' => 'Public'], + ['form_id' => 'HR3690E-AT', 'name' => 'Public'], + ['form_id' => 'HR3693E', 'name' => 'Public'], + ['form_id' => 'HR3694E', 'name' => 'Public'], + ['form_id' => 'HR3695E', 'name' => 'Public'], + ['form_id' => 'HR3698E', 'name' => 'Public'], + ['form_id' => 'HR3699E', 'name' => 'Public'], + ['form_id' => 'HR3703E', 'name' => 'Public'], + ['form_id' => 'HR3709E', 'name' => 'Public'], + ['form_id' => 'HR0150A', 'name' => 'Public'], + ['form_id' => 'HR0150B', 'name' => 'Public'], + ['form_id' => 'HR2528', 'name' => 'Public'], + ['form_id' => 'HR2949', 'name' => 'Public'], + ['form_id' => 'HR3032', 'name' => 'Public'], + ['form_id' => 'HR3138', 'name' => 'Public'], + ['form_id' => 'HR3241', 'name' => 'Public'], + ['form_id' => 'HR3245', 'name' => 'Public'], + ['form_id' => 'HR3254', 'name' => 'Public'], + ['form_id' => 'HR3344', 'name' => 'Public'], + ['form_id' => 'HR3582', 'name' => 'Public'], + ['form_id' => 'HR3583', 'name' => 'Public'], + ['form_id' => 'HR3584', 'name' => 'Public'], + ['form_id' => 'HR3585', 'name' => 'Public'], + ['form_id' => 'HR2863', 'name' => 'Public'], + ['form_id' => 'HR3162', 'name' => 'Public'], + ['form_id' => 'HR3463', 'name' => 'Public'], + ['form_id' => 'HR3189', 'name' => 'Public'], + ['form_id' => 'HR3238', 'name' => 'Public'], + ['form_id' => 'HR2894', 'name' => 'Public'], + ['form_id' => 'HR3115', 'name' => 'Public'], + ['form_id' => 'HR3320', 'name' => 'Public'], + ['form_id' => 'HR2883', 'name' => 'Public'], + ['form_id' => 'HR3043', 'name' => 'Public'], + ['form_id' => 'HR0081', 'name' => 'Public'], + ['form_id' => 'HR3371E', 'name' => 'Public'], + ['form_id' => 'HR3375F', 'name' => 'Public'], + ['form_id' => 'HR3687F', 'name' => 'Public'], + ['form_id' => 'HR3689F', 'name' => 'Public'], + ['form_id' => 'HR3690F', 'name' => 'Public'], + ['form_id' => 'HR3709F', 'name' => 'Public'], + ['form_id' => 'HR3689E-AT', 'name' => 'Public'], + ['form_id' => 'CF1702A', 'name' => 'Public'], + ['form_id' => 'CF1707A', 'name' => 'Public'], + ['form_id' => 'CF1707B', 'name' => 'Public'], + ['form_id' => 'CF0040', 'name' => 'Public'], + ['form_id' => 'CF0042', 'name' => 'Public'], + ['form_id' => 'CF0043', 'name' => 'Public'], + ['form_id' => 'CF0044', 'name' => 'Public'], + ['form_id' => 'CF0045', 'name' => 'Public'], + ['form_id' => 'CF0046', 'name' => 'Public'], + ['form_id' => 'CF0047', 'name' => 'Public'], + ['form_id' => 'CF0048', 'name' => 'Public'], + ['form_id' => 'CF2798', 'name' => 'Public'], + ['form_id' => 'HR4025', 'name' => 'Public'], + ]; + + foreach ($userTypeUpdates as $update) { + $form = Form::where('form_id', $update['form_id'])->first(); + $userType = UserType::where('name', $update['name'])->first(); + + if ($form && $userType) { + DB::table('form_user_type')->insertOrIgnore([ + 'form_id' => $form->id, + 'user_type_id' => $userType->id, + ]); + } else { + if (!$form) { + Log::warning("BulkMetadataUpdateSeeder mismatch: Could not find Form '{$update['form_id']}'"); + } + if (!$userType) { + Log::warning("BulkMetadataUpdateSeeder mismatch: Could not find User Type '{$update['name']}'"); + } + } + } + } +} \ No newline at end of file diff --git a/database/seeders/FormLocationSeeder.php b/database/seeders/FormLocationSeeder.php index cc027444..7948a8ee 100644 --- a/database/seeders/FormLocationSeeder.php +++ b/database/seeders/FormLocationSeeder.php @@ -15,17 +15,25 @@ class FormLocationSeeder extends Seeder public function run(): void { $locations = [ - 'ICM Target', - 'ICM Dev', - 'ICM Test', - 'iConnect', - 'Resource Finder', - 'MySelfServe', - 'Loop', + ['name' => 'ICM', 'description' => ''], + ['name' => 'iConnect', 'description' => 'MCFD Intranet'], + ['name' => 'Resource Finder', 'description' => 'SDPR document store and standard operating procedures'], + ['name' => 'MySS', 'description' => 'Form exists in MySelfServe portal as a template'], + ['name' => 'Loop', 'description' => 'SDPR Intranet'], + ['name' => 'BC Gov Web', 'description' => 'Hosted on the BC Government Website'], + ['name' => 'PPM', 'description' => 'Policy and Procedures Manual'], + ['name' => 'ConECCt', 'description' => 'ECC (Education and Childcare) Intranet'], + ['name' => 'FormFoundry', 'description' => ''], + ['name' => 'MyFS', 'description' => 'My Family Services Portal, used by MCFD Autism Services and ECC Child Care Services'], + ['name' => 'OES', 'description' => 'Online Employment Services portal'], + ['name' => 'WorkBC Extranet', 'description' => ''], + ['name' => 'Caregiver Portal', 'description' => ''], + ['name' => 'Unknown', 'description' => 'This form cannot be located'], + ['name' => 'CHEFS', 'description' => ''], ]; - foreach ($locations as $name) { - FormLocation::firstOrCreate(['name' => $name]); + foreach ($locations as $location) { + FormLocation::firstOrCreate($location); } } } diff --git a/database/seeders/FormSeeder.php b/database/seeders/FormSeeder.php index 99b62e61..1a94536c 100644 --- a/database/seeders/FormSeeder.php +++ b/database/seeders/FormSeeder.php @@ -20,6 +20,7 @@ public function run(): void FormUserTypeTableSeeder::class, FormWorkbenchPathsTableSeeder::class, FormBusinessAreaTableSeeder::class, + BulkFormMetadataUpdateSeeder::class, ]); } } diff --git a/database/seeders/FormSoftwareSourceSeeder.php b/database/seeders/FormSoftwareSourceSeeder.php index 257b2396..603e2f4b 100644 --- a/database/seeders/FormSoftwareSourceSeeder.php +++ b/database/seeders/FormSoftwareSourceSeeder.php @@ -14,16 +14,21 @@ class FormSoftwareSourceSeeder extends Seeder public function run(): void { $softwareSources = [ - 'Livecycle', - 'Adobe Acrobat', - 'Orbeon', - 'Microsoft Word', - 'Microsoft PowerPoint', - 'Klamm', + ['name' => 'Adobe Livecycle', 'description' => ''], + ['name' => 'Adobe Acrobat', 'description' => ''], + ['name' => 'Orbeon', 'description' => ''], + ['name' => 'Microsoft Word', 'description' => ''], + ['name' => 'Microsoft PowerPoint', 'description' => ''], + ['name' => 'Klamm', 'description' => 'Form is built in FormFoundry'], + ['name' => 'CHEFS', 'description' => ''], + ['name' => 'Microsoft Excel', 'description' => ''], + ['name' => 'Infopath', 'description' => ''], + ['name' => 'Adobe', 'description' => 'Adobe PDF form'], + ['name' => 'Unknown', 'description' => 'We have no way to verify the software source for this document'], ]; - foreach ($softwareSources as $name) { - FormSoftwareSource::firstOrCreate(['name' => $name]); + foreach ($softwareSources as $source) { + FormSoftwareSource::firstOrCreate($source); } } } diff --git a/database/seeders/FormTagSeeder.php b/database/seeders/FormTagSeeder.php index 03927a1c..213df794 100644 --- a/database/seeders/FormTagSeeder.php +++ b/database/seeders/FormTagSeeder.php @@ -13,8 +13,6 @@ class FormTagSeeder extends Seeder */ public function run(): void { - FormTag::truncate(); - $tags = [ 'BCMailPlus', 'MySS', @@ -24,10 +22,14 @@ public function run(): void 'JAWS', 'MIS', 'ServiceCanada', + 'storeXML', + 'Digital Signature', + 'migration2025', + 'CHEFSCandidate', ]; foreach ($tags as $tag) { - FormTag::create(['name' => $tag]); + FormTag::firstOrCreate(['name' => $tag]); } } } diff --git a/database/seeders/FormsTableSeeder.php b/database/seeders/FormsTableSeeder.php index f5cf3b94..5388e9e2 100644 --- a/database/seeders/FormsTableSeeder.php +++ b/database/seeders/FormsTableSeeder.php @@ -48955,6 +48955,75 @@ public function run() 'program' => NULL, 'deleted_at' => NULL, ], + 124 => [ + 'id' => 2175, + 'form_id' => 'HR3740E', + 'form_title' => 'ELMSD Employability Plan (English)', + 'ministry_id' => 2, + 'form_purpose' => NULL, + 'notes' => 'ADO 3860', + 'fill_type_id' => NULL, + 'decommissioned' => false, + 'form_frequency_id' => NULL, + 'form_reach_id' => NULL, + 'created_at' => NULL, + 'updated_at' => NULL, + 'print_reason' => NULL, + 'retention_needs' => NULL, + 'icm_non_interactive' => NULL, + 'footer_fragment_path' => NULL, + 'dcv_material_number' => NULL, + 'orbeon_functions' => NULL, + 'icm_generated' => true, + 'program' => 'Employment Labour Market Services Division (ELMSD)', + 'deleted_at' => NULL, + ], + 125 => [ + 'id' => 2176, + 'form_id' => 'HR3740F', + 'form_title' => 'ELMSD Employability Plan (French)', + 'ministry_id' => 2, + 'form_purpose' => NULL, + 'notes' => 'ADO 3860', + 'fill_type_id' => NULL, + 'decommissioned' => false, + 'form_frequency_id' => NULL, + 'form_reach_id' => NULL, + 'created_at' => NULL, + 'updated_at' => NULL, + 'print_reason' => NULL, + 'retention_needs' => NULL, + 'icm_non_interactive' => NULL, + 'footer_fragment_path' => NULL, + 'dcv_material_number' => NULL, + 'orbeon_functions' => NULL, + 'icm_generated' => true, + 'program' => 'Employment Labour Market Services Division (ELMSD)', + 'deleted_at' => NULL, + ], + 126 => [ + 'id' => 2177, + 'form_id' => 'HR3741', + 'form_title' => 'ELMSD Employability Planning Authorization and Consent Form', + 'ministry_id' => 2, + 'form_purpose' => NULL, + 'notes' => 'Not yet developed. ADO https://dev.azure.com/BC-SDPR/Forms%20Modernization/_workitems/edit/4010', + 'fill_type_id' => NULL, + 'decommissioned' => false, + 'form_frequency_id' => NULL, + 'form_reach_id' => NULL, + 'created_at' => NULL, + 'updated_at' => NULL, + 'print_reason' => NULL, + 'retention_needs' => NULL, + 'icm_non_interactive' => NULL, + 'footer_fragment_path' => NULL, + 'dcv_material_number' => NULL, + 'orbeon_functions' => NULL, + 'icm_generated' => true, + 'program' => 'Employment Labour Market Services Division (ELMSD)', + 'deleted_at' => NULL, + ], ]); // Reset the ID sequence to avoid conflicts with future inserts