# ProductTags

WordPress-style tagging for shop products. Adds a "Product Tags" admin menu for
tag CRUD and an inline tag-selector (search + create-on-the-fly) on the product
edit page. Tags are loaded via a `productTags` relation on `Mtc\Shop\Item` —
templates that need them opt in with `->with('productTags')` or a direct
relation access (which triggers a single extra query for the set).

## Installation (first time)

The plugin ships **disabled by default** so that dropping the files into a
fresh checkout can't break product queries before the schema is in place. The
two-step install is:

**1. Run migrations.**

```sh
php artisan migrate
```

This creates `product_tags` and `items_product_tags`. The migrations are
registered via `loadMigrationsFrom` in the service provider, so a standard
`migrate` picks them up — no extra path flag needed.

**2. Enable the plugin** via the admin Settings UI (see below). On the first
page load after the plugin files are in place, a `PRODUCT_TAGS_ENABLED`
setting row is auto-seeded with a default of `0` (disabled), grouped under
the "Product Tags" module so it shows up in the settings screen.

## Enable / disable

The plugin is gated by the `PRODUCT_TAGS_ENABLED` row in the `settings` table.
This follows the same pattern as `COMPLIMENTED_ITEMS`, `BRANDS_IMAGES`,
`ITEMS_KEYWORDS`, etc. — the setting becomes a PHP constant at runtime via
`Setting::loadAndDefine()` during bootstrap.

**To toggle:** open the admin Settings page, find the "Product Tags" group,
and flip `PRODUCT_TAGS_ENABLED` between `0` and `1`. No `.env` edit, no
`config:clear`. The change takes effect on the next request.

**Default:** `0` (disabled). Set automatically on first load of the plugin if
the setting doesn't yet exist (see `admin/includes/install.php`).

When disabled:

- The product edit page no longer shows the "Tags" fieldset.
- The `productTags` relation is not registered, so existing product queries
  aren't touched.
- The admin menu entry is hidden automatically. The row stays in `admin_menu`
  but carries a `constant = 'PRODUCT_TAGS_ENABLED'` reference, and
  `Menu::allowed()` filters it out whenever the constant is falsy.
- Migrations stay applied — existing tag/pivot rows are preserved.

The `ProductTagsServiceProvider` is registered unconditionally in
`config/app.php`, but it only calls `loadMigrationsFrom`. All runtime
wiring happens in `ProductTags::init()`, called from `loader.php` only when
the constant is truthy.

## Admin usage

- **Manage tags:** sidebar → *Shop → Product Tags*. Create, edit, delete.
  Slug auto-fills from name, but can be overridden.
- **Assign tags to a product:** on the product edit page, a "Tags" fieldset
  sits just above "Custom fields". Type to search existing tags; press Enter
  (or comma) to add — unknown names are created on the fly. `↑/↓` navigates
  suggestions, `Esc` closes them, `Backspace` on an empty input removes the
  last chip.
- **Save flow:** tags are saved as part of the standard "Save product"
  submit — no separate save button.

## Using tags in Twig

Eager-load `productTags` in the controller/query that builds the item list,
then access `item.productTags` freely in the template:

```php
$items = Item::with('productTags')->get();
```

```twig
{% if item.productTags is not empty %}
    <ul class="product-tags">
        {% for tag in item.productTags %}
            <li class="product-tag" data-slug="{{ tag.slug }}">
                {{ tag.name }}
            </li>
        {% endfor %}
    </ul>
{% endif %}
```

Each `tag` exposes: `id`, `name`, `slug`, `description`, `created_at`,
`updated_at`.

Check for a specific tag:

```twig
{% set has_vegan = item.productTags|filter(t => t.slug == 'vegan')|length > 0 %}
```

Accessing `item.productTags` on an item that wasn't eager-loaded triggers
one query per item — the usual N+1 hazard. Use `->with('productTags')`
on list pages.

## Using tags in PHP (non-Twig views)

Same relation, plain Eloquent:

```php
use Mtc\Shop\Item;

$item = Item::with('productTags')->find($id);
$names = $item->productTags->pluck('name');  // Collection<string>

foreach ($item->productTags as $tag) {
    echo $tag->name;
}
```

Reverse — all items for a tag:

```php
use Mtc\Plugins\ProductTags\Models\Tag;

$items = Tag::where('slug', 'vegan')->first()->items;
```

Legacy `\Item` wrapper class: reach the Eloquent model first.

```php
$tags = $legacyItem->eloquent()->productTags;
```

## What the plugin installs

| Component | Location |
|---|---|
| Tables | `product_tags`, `items_product_tags` (pivot) |
| Settings row | `PRODUCT_TAGS_ENABLED` in `settings` table (module *Product Tags*) |
| Admin menu | Entry under *Shop* → *Product Tags* (`/plugins/ProductTags/admin/index.php`); gated by the `PRODUCT_TAGS_ENABLED` constant. Seeded once by `admin/includes/install.php` |
| Product edit widget | Rendered via `Mtc\Shop\Events\ModelFormFooter` listener |
| Save sync | Listens to `AdminShopItemSaved` string event from `shop/admin/products/item.php` |
| Relation | `productTags` on `Mtc\Shop\Item` — opt-in eager load via `->with('productTags')` |

## Uninstall

```sh
php artisan migrate:rollback --path=plugins/ProductTags/database/migrations
```

Then remove the `ProductTagsServiceProvider::class` line from `config/app.php`
and delete the `plugins/ProductTags/` directory. The two one-line hooks added
to `shop/admin/products/item.php` (`ModelFormFooter` dispatch and
`AdminShopItemSaved` dispatch) are harmless to leave — any other plugin can
use them — but can also be removed if you want a clean revert.
