# Block Registry

ACF-style block management for the MTC Pharmacy platform. Define reusable content blocks with configurable fields, assign them to page templates, and manage block instances on pages, products, and categories.

## Setup

The module is registered as a Laravel service provider in `config/app.php`. Run migrations to create the required tables:

```sh
make migrate
```

This creates: `block_registry`, `block_registry_fields`, `block_registry_categories`, `block_registry_template_assignments`, and `entity_blocks`.

Then import block definitions from the JSON files:

```sh
php artisan blocks:import --all --force
```

On production, this runs automatically as part of `deploy.sh`.

## Block Sync

Block definitions are stored as JSON files in `blocks/{slug}.json` and tracked via git. This replaces the old seed migration approach.

### Workflow

1. Edit blocks via the admin UI on **local or staging**
2. Export to JSON via the admin sync page or `php artisan blocks:export`
3. Commit the JSON files to your branch
4. Merge via normal git workflow
5. Production auto-imports on deploy

### Artisan Commands

```sh
php artisan blocks:export {slug?} {--all}   # DB → JSON (blocked on production)
php artisan blocks:import {slug?} {--all} {--force}  # JSON → DB
php artisan blocks:diff {slug?}             # Show differences between DB and JSON
```

### Admin Sync Page

Access at `/admin/blocks/sync`. Shows all blocks with their sync status:

- **In Sync** — DB matches JSON file
- **Local Changes** — DB differs from JSON (expandable diff view)
- **Not Exported** — exists in DB but no JSON file
- **Not Imported** — JSON file exists but not in DB

Per-block and bulk export/import buttons available. Export is hidden on production.

### Production Lock

On production (`APP_ENV=production`):
- Block definition editing is disabled (create, edit, delete, duplicate, toggle active)
- Export is blocked
- Import still works (that's how production receives updates)
- Entity block data editing (page/product content) still works normally

## Admin

Access the Block Registry at `/admin/blocks`. From here you can:

- **Define blocks** — create block definitions with fields, icons, categories, and optional Twig templates
- **Manage categories** — organise blocks into categories
- **Global blocks** — create site-wide blocks that can be referenced from any page/product/category
- **Template assignments** — control which blocks are available on which page templates, and which are added by default
- **Sync** — export/import block definitions as JSON

Blocks are also available as a "Blocks" tab when editing pages, products, and categories in the admin.

## Field Types

| Type | Description |
|------|-------------|
| `text` | Single-line text input |
| `textarea` | Rich text editor (TinyMCE) |
| `checkbox` | Boolean toggle |
| `select` | Dropdown with predefined options |
| `image` | Image upload with optional cropping and srcset generation |
| `file` | File upload |
| `datetime` | Date and time picker |
| `colorpicker` | Colour picker |
| `product_selector` | Product search/select |
| `page_selector` | CMS page picker (manual) or auto-fetch children from a listing container |
| `link` | Internal/external link picker |
| `repeater` | Repeatable group of sub-fields (no nesting) |

## Using Blocks in Templates

### Render all blocks for a page

```twig
{{ render_entity_blocks('page', p.id) }}
```

Each block is rendered using its assigned Twig template (set in the block definition's `template` field).

### Render blocks for a single region

```twig
{{ render_entity_blocks('page', p.id, 'header') }}
{{ render_entity_blocks('page', p.id, 'main') }}
{{ render_entity_blocks('page', p.id, 'footer') }}
```

Pass a region slug as the third argument to render only blocks placed in that region. Templates with multiple declared regions (see *Template Regions* below) typically call this once per region. Templates with no region calls render all regions in order via the two-argument form above.

### Render blocks of a specific type

```twig
{{ render_blocks_by_type('page', p.id, 'hero-banner') }}
```

### Render a single block

```twig
{{ render_block(entityBlockId) }}
```

### Render a global block

```twig
{{ render_global_block(42) }}
```

### Get block data without rendering

```twig
{% set blocks = get_entity_blocks('page', p.id) %}
{% for item in blocks['hero-banner'] %}
    <h1>{{ item.title.value }}</h1>
{% endfor %}
```

Pass `true` as a third argument to include pre-rendered HTML alongside the data:

```twig
{% set blocks = get_entity_blocks('page', p.id, true) %}
```

### Entity types

Blocks can be attached to any of these entity types:

- `page` — CMS pages
- `item` — Products
- `category` — Product categories
- `global` — Site-wide blocks (not tied to a specific entity)

## Block Templates

When a block has a Twig template assigned, the template receives these variables:

| Variable | Description |
|----------|-------------|
| `block` | The block definition model |
| `block_name` | Block name string |
| `block_slug` | Block slug string |
| `fields` | Simple key/value map of field data |
| `block_data` | Full field data with value, type, label, and required |
| `slides` | Repeater field data (auto-mapped) |

## Conditional Field Visibility

Fields can be shown or hidden in the data editor based on the value of a `select`-type field (e.g. a "Layout" dropdown). This is configured per-field via the **Conditional Visibility** section in the block definition editor.

### How it works

1. In the block definition editor (`/admin/blocks/{id}/edit`), expand any field
2. Under **Conditional Visibility**, choose a `select` field to depend on
3. Check which values should make this field visible
4. Save the block definition

In the data editor, dependent fields automatically show/hide when the controlling select changes. Hidden fields retain their values.

### Settings format

Visibility rules are stored in the field's `settings` JSON column:

```json
{
  "visibility": {
    "field": "Layout",
    "values": ["carousel"]
  }
}
```

This works for both top-level fields and repeater sub-fields. The rule means "show this field only when the field named `Layout` has one of the listed values."

## Template Regions

Page templates can declare multiple named "regions" (e.g. `header`, `main`, `footer`) to host blocks in distinct areas around fixed page chrome like sidebars. A template not listed in the registry implicitly has a single `main` region — existing block-based templates continue to behave exactly as before.

### Declaring regions

Edit `Services/TemplateRegionRegistry.php` to map a template name to its ordered region slugs:

```php
private const TEMPLATE_REGIONS = [
    'content_sidebar.twig' => ['header', 'main', 'footer'],
];
```

The order here determines the order regions appear in the admin UI. Use kebab-case or snake_case for region slugs.

### Admin UI

When an entity is on a multi-region template:

- The blocks editor renders one sortable list per region
- Each region has its own "Add New Block" button that pre-tags the new block with that region
- Drag-reordering is scoped to within a region (regions are independent ordering sequences)
- Blocks placed in regions the template no longer declares still appear under their stored region so editors can move or remove them

Single-region templates keep the old single-list behaviour with no visible change.

### Frontend rendering

Call `render_entity_blocks('page', p.id, 'region-slug')` once per region in your Twig template. Each call only renders blocks placed in that region. The `main` region also matches legacy rows where `region` is `NULL`, so existing pages migrate cleanly.

### Storage

Regions are stored on `entity_blocks.region` (`VARCHAR(64)` nullable). The `2026_05_04_100000` migration adds the column and an index on `(entity_type, entity_id, region)`. The follow-up `100001` migration backfills existing `NULL` rows to `'main'` so ordering sequences don't collide between legacy and new rows.

## Image Processing

Image fields with cropping enabled will automatically:

- Crop to the specified aspect ratio
- Generate responsive `srcset` images at multiple widths (default: 400, 800, 1200, 1600px)
- Cache processed images to `/uploads/cache/blocks/{entityBlockId}/`

Configure per field via the image settings in the block definition editor.
