Catalog Management

Products

How do I remove the manufacturer from the product slug/url?
The manufacturer name is automatically prepended to the product slug (its URL). If you wish for this to not happen you can set the static 
Aero\Catalog\Models\Product::$slugContainsManufacturer boolean to 
false:
\Aero\Catalog\Models\Product::$slugContainsManufacturer = false;
 

Listing Rules

What is the listing rules context builder?
The Listing Rules Context Builder is used to build the rule-specific context that listing rules need when being displayed in the Admin UI.
For example, the 
\Aero\Admin\Pipelines\Pipes\AttachTaggedWithRuleContext pipe attaches the hydrated tags data for the 
\Aero\Catalog\Rules\Types\TaggedWithRule rule:
class AttachTaggedWithRuleContext
{
    public function handle(\Aero\Admin\Pipelines\ListingRulesContextBuilder $pipeline, \Closure $next)
    {
        $tagIds = $pipeline->rules
            ->where('type', \Aero\Catalog\Rules\Types\TaggedWithRule::KEY)
            ->flatMap(function (ListingRule $rule) {
                return collect($rule->options['filters'] ?? [])->flatten()->toArray();
            })
            ->unique();
        $pipeline->data[\Aero\Catalog\Rules\Types\TaggedWithRule::KEY] = [
            'tags' => $tagIds->isNotEmpty()
                ? \Aero\Catalog\Models\Tag::query()
                    ->select('id', 'tag_group_id', 'name')
                    ->with('group')
                    ->whereIn('id', $tagIds)->get()
                    ->mapWithKeys(function (\Aero\Catalog\Models\Tag $tag) {
                        $id = $tag->getKey();
                        return [
                            $id => [
                                'value' => (string) $id,
                                'name' => $tag->getAttribute('name'),
                                'group_id' => (string) $tag->getRelation('group')->getKey(),
                                'group' => $tag->getRelation('group')->getAttribute('name'),
                            ],
                        ];
                    })
                    ->toArray()
                : [],
        ];
        return $next($pipeline);
    }
}
Parameters
Parameter
Type
Description
$pipeline
\Aero\Admin\Pipelines\ListingRulesContextBuilder
The listing rules context builder.
$next
\Closure
The next step in the builder.
Attributes
Attribute
Type
Description
$rules
\Illuminate\Support\Collection
All the rules corresponding to the model (that is being viewed in the Admin).
$data
array
The array of data the rules need (for display purposes only).

What is the listing rules data formatter pipeline?
The Listing Rules Data Formatter is used to format the listing rules data into an array for the Admin UI page.
For example, the 
\Aero\Admin\Pipelines\Pipes\AttachListingRuleAttributes attaches the attributes to the array:
class AttachListingRuleAttributes
{
    public function handle(\Aero\Admin\Pipelines\ListingRulesDataFormatter $pipeline, Closure $next)
    {
        $pipeline->data['id'] = $pipeline->rule->getKey();
        $pipeline->data['type'] = $pipeline->rule->getAttribute('type');
        $pipeline->data['rule_logic'] = $pipeline->rule->getAttribute('rule_logic') ?? 'must';
        $pipeline->data['logic'] = $pipeline->rule->getAttribute('logic') ?? 'and';
        $pipeline->data['group'] = $pipeline->rule->getAttribute('group') ?? 0;
        $pipeline->data['options'] = $pipeline->rule->getAttribute('options') ?? [];
        return $next($pipeline);
    }
}
The formatter also assigns the context built by the 
ListingRulesContextBuilder as view data to the rule.
Parameters
Parameter
Type
Description
$pipeline
\Aero\Admin\Pipelines\ListingRulesDataFormatter
The listing rules data formatter.
$next
\Closure
The next step in the formatter.
Attributes
Attribute
Type
Description
$rule
\Aero\Catalog\Models\ListingRule
The listing rule being formatted.
$data
array
The array of formatted data for the Admin UI.

How can I add a custom listing rule?
To add a custom listing rule you need to create a class that extends 
Aero\Catalog\Rules\ListingRule\AttributedWithRule.
For example we will add a rule that checks if the listing has specific attributes.
First, we need to index the attribute ids so that they can be queried easily:
\Aero\Search\Elastic\Documents\ListingDocument::add('search-result', function (\Aero\Search\Elastic\Documents\ListingDocument $document) {
    return [
        'attribute_ids' => $document->getModel()->attributes->map(function (\Aero\Catalog\Models\Attribute $attribute) {
            return (string) $attribute->getKey();
        }),
    ];
});
Now, we can make the rule:
class AttributedWithRule extends \Aero\Catalog\Rules\ListingRule
{
    public const KEY = 'attributes';
    public string $name = 'have one of these attributes';
    public string $view = 'acme::attributes';
    protected array $options = [
        'attributes' => [],
    ];
    public function viewData(array $context): array
    {
        return [
            'attributes' => collect($this->options['attributes'] ?? [])
                ->flatten()
                ->map(function ($id) use ($context) {
                    return $context['attributes'][$id] ?? null;
                })
                ->toArray(),
        ];
    }
    public function apply(\Aero\Search\Contracts\SearchBuilder $builder): ?\Elastica\Query\BoolQuery
    {
        $attributeIds = $this->options['attributes'] ?? [];
        if (! is_array($attributeIds)) {
            return null;
        }
        return (new BoolQuery())->addMust(
            new \Elastica\Query\Terms('search-result.attribute_ids', $attributeIds),
        );
    }
    public function optionRules(): array
    {
        return [
            'attributes' => 'required|array',
            'attributes.*' => 'required|distinct|int|exists:attributes,id',
        ];
    }
}
For the UI to work we must build the context for our rule:
\Aero\Admin\Pipelines\ListingRulesContextBuilder::extend(function (\Aero\Admin\Pipelines\ListingRulesContextBuilder $pipeline, \Closure $next) {
    $attributeIds = $pipeline->rules
        ->where('type', \Aerocargo\Acme\AttributedWithRule::KEY)
        ->flatMap(function (\Aero\Catalog\Models\ListingRule $rule) {
            return collect($rule->options['attributes'] ?? [])->flatten()->toArray();
        })
        ->unique();
    $pipeline->data[\Aerocargo\Acme\AttributedWithRule::KEY] = [
        'attributes' => $attributeIds->isNotEmpty()
            ? Attribute::query()
                ->select('id', 'name')
                ->whereIn('id', $attributeIds)->get()
                ->mapWithKeys(function (\Aero\Catalog\Models\Attribute $attribute) {
                    $id = $attribute->getKey();
                    return [
                        $id => [
                            'value' => (string) $id,
                            'name' => $attribute->getAttribute('name'),
                        ]
                    ];
                })
                ->toArray()
            : [],
    ];
    return $next($pipeline);
});
And add the view:
<template v-for="attribute in builder.rule.view_data.attributes ?? []">
    <input type="hidden" :name="{{ $name }} + '[attributes][]'" :value="attribute.value">
</template>
<label :for="{{ $id }} + '-attributes-option'">Attributes</label>
<searchable-select :id="{{ $id }} + '-attributes-option'" class="mt-2 w-full"
                   :input-name="'rules[' + builder.group_index + '][' + builder.rule_index + '][view_data][attributes]'"
                   url="{{ route('admin.catalog.attributes.search') }}" track-by="value" label="name" :multiple="true"
                   v-model="builder.rule.view_data.attributes"></searchable-select>
 