Skip to main content
Listing Rules · Revision #1

How can I add a custom listing rule?

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>