# Bongo Form Package - Cursor Rules

## Project Overview

This is the `bongo/form` package, a Laravel package for creating and managing custom forms with dynamic field types. It provides both backend admin interfaces for form creation and frontend rendering with submission handling.

**Package**: `bongo/form`
**Namespace**: `Bongo\Form`
**Dependencies**: `bongo/framework` (^3.0), `bongo/captcha`
**PHP**: 8.2+ | **Laravel**: 10+

## Directory Structure

```
src/
├── Config/
│   └── form.php                    # Configuration (file types, reCAPTCHA, purging)
├── Events/
│   ├── FormCreated.php             # Dispatched after form creation
│   ├── FormUpdated.php             # Dispatched after form update
│   └── FormDeleted.php             # Dispatched after form deletion
├── Http/
│   ├── Controllers/
│   │   ├── Backend/
│   │   │   ├── FormController.php          # CRUD operations
│   │   │   └── FormDatatableController.php # DataTable integration
│   │   └── Frontend/
│   │       └── FormController.php          # Form submission & preview
│   └── Requests/
│       ├── StoreFormRequest.php            # Validation for creating forms
│       └── UpdateFormRequest.php           # Validation for updating forms
├── Mailables/
│   ├── FormMailable.php            # Confirmation email to submitter
│   └── AdminFormMailable.php       # Notification email to admin
├── Migrations/
│   ├── 2021_01_01_000001_create_forms_table.php
│   ├── 2021_01_01_000002_create_form_items_table.php
│   └── [...additional migrations...]
├── Models/
│   ├── Form.php                    # Main form model
│   └── FormItem.php                # Form field/item model
├── Routes/
│   ├── backend.php                 # Admin routes (backend.form.*)
│   └── frontend.php                # Public routes (form.*)
├── Services/
│   └── FormHandler.php             # Form submission processing service
├── Traits/
│   └── HasType.php                 # Type checking methods for FormItem
├── Translations/
│   └── en/
│       ├── backend.php
│       └── frontend.php
├── Views/
│   ├── backend/                    # Admin UI (index, create, edit, show)
│   ├── frontend/                   # Public form rendering & partials
│   └── mail/                       # Email templates
└── FormServiceProvider.php         # Service provider
```

## Architecture & Design

### Extends Bongo Framework

This package extends `Bongo\Framework\Providers\AbstractServiceProvider` which provides:

- **Automatic bootstrapping** via `boot()` method:
  - Config from `src/Config/form.php`
  - Routes from `src/Routes/` (backend.php, frontend.php)
  - Views from `src/Views/form/`
  - Migrations from `src/Migrations/`
  - Translations from `src/Translations/`

- **Route middleware patterns**:
  - `backend.php` → `backend.form.*` prefix with `auth` + `employee` middleware
  - `frontend.php` → `form.*` named routes with honeypot spam protection

- **Service provider** requires `protected string $module = 'form'`

### Core Models

#### Form Model (`Bongo\Form\Models\Form`)

Extends `AbstractModel` with traits:
- `SoftDeletes` - Soft deletion support
- `HasKey` - Generates URL-safe key from name
- `HasUUID` - Automatic UUID generation

**Relationships**:
- `items()`: HasMany relationship to `FormItem` (ordered by `sort_order`)

**Key Methods**:
- `hasItems(): bool` - Check if form has any items
- `getRecipientsAsArray(): ?array` - Parse comma-separated recipients into array

**Fillable**: `name`, `recipients`, `subject`, `success_url`

#### FormItem Model (`Bongo\Form\Models\FormItem`)

Extends `AbstractModel` with traits:
- `HasUUID` - Automatic UUID generation
- `HasType` - Type checking methods (see below)

**Constants**:
- Required flags: `REQUIRED`, `NOT_REQUIRED`
- Label display: `SHOW_LABEL`, `HIDE_LABEL`
- Field types: `CHECKBOX`, `DATE`, `EMAIL`, `FILE`, `HTML`, `IMAGE`, `INPUT`, `SELECT`, `TEXT`, `TEXTAREA`

**Key Methods**:
- `getRulesAttribute(): array` - Generate validation rules based on field type
- `isRequired(): bool` - Check if field is required
- `labelIsVisible(): bool` / `labelIsHidden(): bool` - Label visibility checks
- `hasWidth(): bool` - Check if custom width is set
- `hasOptions(): bool` - Check if field has options (for SELECT)
- `getOptionsAsArray(): ?array` - Parse pipe/comma-separated options

**Fillable**: `form_id`, `label`, `display_label`, `name`, `class`, `width`, `placeholder`, `required`, `options`, `type`, `sort_order`

### HasType Trait (`Bongo\Form\Traits\HasType`)

Provides type-specific query scopes and boolean checkers for `FormItem`:

**Query Scopes**: `scopeCheckbox()`, `scopeFile()`, `scopeHtml()`, `scopeImage()`, `scopeInput()`, `scopeEmail()`, `scopeSelect()`, `scopeText()`, `scopeTextArea()` (and their `scopeNot*` counterparts)

**Boolean Methods**: `isCheckbox()`, `isFile()`, `isHtml()`, `isImage()`, `isInput()`, `isEmail()`, `isSelect()`, `isText()`, `isTextArea()`

### FormHandler Service (`Bongo\Form\Services\FormHandler`)

Handles form submission processing with validation, file uploads, and cleanup.

**Constructor**: `__construct(Form $form, Request $request)`
- Automatically calls `setFieldsArray()` and `addValuesToFieldsArray()`

**Key Methods**:
- `validateFields(): void` - Validates all fields including reCAPTCHA
- `storeFiles(): void` - Stores uploaded files to temporary storage
- `purgeOldFiles(): void` - Removes old temporary files based on config
- `getFields(): array` - Returns fields array with values (excluding empty)

**Validation**:
- Builds validation rules dynamically from `FormItem->rules` attribute
- Adds reCAPTCHA validation if enabled in config
- Uses `Bongo\Captcha\Rules\Captcha` rule for reCAPTCHA v3 score validation

### Events

All events follow standard Laravel event structure:

- `FormCreated` - Dispatched after `Form::create()` in backend
- `FormUpdated` - Dispatched after `Form::update()` in backend
- `FormDeleted` - Dispatched after `Form::delete()` in backend

Each event has public `Form $form` property.

### Mailables

#### FormMailable (`Bongo\Form\Mailables\FormMailable`)
Sent to form submitters (addresses from email fields).
- Views: `form::mail.response` (HTML), `form::mail.response_plain` (text)
- Reply-to set to form recipients

#### AdminFormMailable (`Bongo\Form\Mailables\AdminFormMailable`)
Sent to form administrators (from `recipients` field).
- Views: `form::mail.admin_response` (HTML), `form::mail.admin_response_plain` (text)
- Contains submitter email addresses

## Configuration

Located at `src/Config/form.php`:

```php
'prefix' => 'forms',                           // URL prefix for routes
'allowed_image_types' => 'png,jpg,jpeg,gif',   // Image upload mimes
'allowed_document_types' => 'png,jpg,jpeg,gif,pdf', // Document mimes
'purge_files' => true,                         // Auto-delete old files
'purge_files_after' => 60,                     // Days before purging
'allowed_html_tags' => '<p><h1>...',           // Tags for HTML field type
'recaptcha' => [
    'enabled' => true,                         // Enable reCAPTCHA
    'min_score' => 0.5,                        // Minimum score (0.1-1.0)
],
```

## Common Development Tasks

### Creating a New Form Field Type

1. Add constant to `FormItem` model (e.g., `const NEWTYPE = 'newtype'`)
2. Add to migration enum in `create_form_items_table.php`
3. Add type checker methods to `HasType` trait:
   ```php
   public function scopeNewType($query) { return $query->where('type', self::NEWTYPE); }
   public function isNewType(): bool { return $this->type === self::NEWTYPE; }
   ```
4. Update `getRulesAttribute()` in `FormItem` if custom validation needed
5. Create blade partial at `src/Views/frontend/partials/newtype.blade.php`
6. Update form rendering logic in `src/Views/frontend/partials/form.blade.php`

### Adding Custom Validation to FormItem

Modify the `getRulesAttribute()` method in `FormItem` model:

```php
public function getRulesAttribute(): array
{
    $rules = $this->isRequired() ? ['required'] : ['nullable'];

    if ($this->isNewType()) {
        $rules = array_merge($rules, ['custom_rule', 'min:10']);
    }

    return $rules;
}
```

### Customizing Email Templates

Email templates are located at `src/Views/mail/`:
- `response.blade.php` / `response_plain.blade.php` - Sent to submitter
- `admin_response.blade.php` / `admin_response_plain.blade.php` - Sent to admin

Both have access to `$form` (Form model) and `$fields` (array of submitted data).

### Listening to Form Events

In your application's `EventServiceProvider`:

```php
use Bongo\Form\Events\FormCreated;

protected $listen = [
    FormCreated::class => [
        YourListener::class,
    ],
];
```

## Code Style Conventions

### Method Signatures
- Always use return types: `public function store(): RedirectResponse`
- Use nullable types where appropriate: `public function getRecipientsAsArray(): ?array`
- Type-hint parameters: `public function __construct(Form $form, Request $request)`

### Naming Conventions
- Controller methods: RESTful names (`index`, `create`, `store`, `show`, `edit`, `update`, `destroy`)
- Routes: namespaced (`backend.form.index`, `form.store`)
- Views: module namespaced (`form::backend.index`)
- Config: accessed via `config('form.prefix')`
- Translations: accessed via `trans('form::backend.store_success')`

### Model Conventions
- Use constants for enum values: `FormItem::REQUIRED`, `FormItem::EMAIL`
- Boolean methods start with `is`, `has`, or verb: `isRequired()`, `hasItems()`, `labelIsVisible()`
- Accessor methods: `getRecipientsAsArray()`, `getOptionsAsArray()`
- Query scopes: `scopeEmail($query)`, `scopeNotCheckbox($query)`

### Validation
- Use Form Requests for controller validation: `StoreFormRequest`, `UpdateFormRequest`
- Dynamic validation in `FormHandler` service for frontend submissions
- Build rules array from model attributes: `$item->rules`

## Testing

Run tests from package root:

```bash
vendor/bin/phpunit
```

Test structure in `tests/` directory extends `Orchestra\Testbench\TestCase`.

## Code Quality

Format code with Laravel Pint:

```bash
# Check formatting
vendor/bin/pint --test

# Fix formatting
vendor/bin/pint
```

Uses Laravel preset (no custom `pint.json`).

## Key Integration Points

### With bongo/framework
- Extends `AbstractModel`, `AbstractController`, `AbstractServiceProvider`, `AbstractDatatableController`
- Uses framework traits: `HasKey`, `HasUUID`
- Uses framework helpers: `make_key()`, `setting()->reCaptchaEnabled()`

### With bongo/captcha
- Uses `Bongo\Captcha\Rules\Captcha` for reCAPTCHA v3 validation
- Checks `setting()->reCaptchaEnabled()` before adding validation
- Generates action key from form name via `make_key($form->name)`

### With Spatie Honeypot
- Frontend store route protected by `ProtectAgainstSpam` middleware
- Helps prevent spam form submissions

## Security Considerations

- **HTML Field Type**: Uses `strip_tags()` with allowed tags from config
- **File Uploads**: Validates mime types against config whitelist
- **Max File Size**: 10MB limit (10240 KB)
- **reCAPTCHA v3**: Score-based validation (default 0.5 minimum)
- **Honeypot**: Spam protection on frontend submissions
- **Temporary Files**: Auto-purged after configurable days (default 60)

## Frontend Form Rendering

Forms are rendered dynamically based on `FormItem` types. Each type has a dedicated blade partial in `src/Views/frontend/partials/`:
- `checkbox.blade.php`
- `date.blade.php`
- `email.blade.php`
- `file.blade.php`
- `html.blade.php`
- `image.blade.php`
- `input.blade.php`
- `select.blade.php`
- `text.blade.php` (non-input display text)
- `textarea.blade.php`

Main form wrapper: `src/Views/frontend/partials/form.blade.php`

## Route Reference

### Backend Routes (prefix: `/admin/forms`)
- `backend.form.index` - GET `/` - List all forms
- `backend.form.create` - GET `/create` - Show create form
- `backend.form.store` - POST `/store` - Create new form
- `backend.form.datatable` - GET `/datatable` - DataTable data
- `backend.form.show` - GET `/{form}` - View form details
- `backend.form.edit` - GET `/{form}/edit` - Edit form
- `backend.form.update` - POST `/{form}/update` - Update form
- `backend.form.destroy` - DELETE `/{form}/delete` - Delete form

### Frontend Routes (prefix: `/forms`)
- `form.preview` - GET `/preview/{uuid}` - Preview form (auth + developer only)
- `form.store` - POST `/store` - Submit form (honeypot protected)

## Database Schema

### `forms` Table
- `id` (PK), `uuid` (indexed)
- `name`, `key` (indexed)
- `success_url`, `recipients`, `subject`
- Audit columns: `created_by`, `updated_by`, `deleted_by`
- Timestamps + soft deletes

### `form_items` Table
- `id` (PK), `uuid` (indexed)
- `form_id` (FK to forms, cascade delete)
- `label`, `display_label`, `name`, `class`, `width`, `placeholder`
- `options` (text, pipe/comma separated)
- `required` (enum: yes/no)
- `type` (enum: checkbox, date, email, file, html, image, input, select, text, textarea)
- `sort_order` (indexed)
- Audit columns: `created_by`, `updated_by`
- Timestamps

## Alias Loader

The `FormServiceProvider` registers a global alias:
```php
'FormItem' => Bongo\Form\Models\FormItem::class
```

This allows accessing `FormItem` without namespace in views/controllers.
