# Listings

# What is the listing attributes pipeline?

The listing attributes pipeline is used to build the attributes for the `Aero\Search\Elastic\Models\Listing` model.

It can be extended to add new attributes or modify existing attributes.

For example you can modify the listing's name to include the attributes when the listing is split by them.

```php
\Aero\Search\Elastic\Pipelines\ListingAttributes::extend(function (array $attributes, \Closure $next) {
    $splitAttributeGroups = collect($attributes['attribute_groups'] ?? [])
        ->filter(function ($attributeGroup) {
            return $attributeGroup['split'] ?? false;
        });

    if ($splitAttributeGroups->isEmpty()) {
        return $next($attributes);
    }

    $attributeNames = $splitAttributeGroups->flatMap(function (array $attributeGroup) {
        return collect($attributeGroup['attributes'] ?? [])->pluck('name');
    });

    $attributes['name'] = "{$attributeNames->implode(' ')} {$attributes['name']}";

    return $next($attributes);
});
```

This means if you have a product split by a `Colour` attribute group, each listing would now be named `{colour} {name}`, e.g. Red Puffer Jack.

---

### Parameters

<table border="1" id="bkmrk-parameter-type-descr" style="border-collapse: collapse; width: 100%; height: 106.188px;"><colgroup><col style="width: 33.3731%;"></col><col style="width: 33.3731%;"></col><col style="width: 33.3731%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**Parameter**</td><td style="height: 29.7969px;">**Type**</td><td style="height: 29.7969px;">**Description**</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">`$attributes`</td><td style="height: 46.5938px;">`array`</td><td style="height: 46.5938px;">The attributes for the listing model.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">`$next`</td><td style="height: 29.7969px;">`\Closure`</td><td style="height: 29.7969px;">The next step in the pipeline.</td></tr></tbody></table>

# How can I add custom attributes to the listing model?

You can extend the `\Aero\Search\Elastic\Pipelines\ListingAttributes` pipeline to add custom attributes to the listing model.

For example you can modify the listing's name to include the attributes when the listing is split by them.

```php
\Aero\Search\Elastic\Pipelines\ListingAttributes::extend(function (array $attributes, \Closure $next) {
    $splitAttributeGroups = collect($attributes['attribute_groups'] ?? [])
        ->filter(function ($attributeGroup) {
            return $attributeGroup['split'] ?? false;
        });

    if ($splitAttributeGroups->isEmpty()) {
        return $next($attributes);
    }

    $attributeNames = $splitAttributeGroups->flatMap(function (array $attributeGroup) {
        return collect($attributeGroup['attributes'] ?? [])->pluck('name');
    });

    $attributes['name'] = "{$attributeNames->implode(' ')} {$attributes['name']}";

    return $next($attributes);
});
```

This means if you have a product split by a `Colour` attribute group, each listing would now be named `{colour} {name}`, e.g. Red Puffer Jack.

# How can I add a custom facet?

You can add a custom facet by extending the listing document.

For example you can add a Stock Status facet:

```php
\Aero\Search\Elastic\Documents\ListingDocument::add('string-facets', function ($document) {
    $stock = $document->getModel()->availableStock() > 0;

    return [
        \Aero\Search\Elastic\Facet::create('stock', 'Stock Status', (int) $stock, $stock ? 'In stock' : 'Out of stock'),
    ];
});
```

<p class="callout warning">You must run `php artisan aero:search:reindex --type=listing` for these document changes to be applied to existing listings.</p>

And then register it so that it appears on the listings page as a facet:

```php
\Aero\Search\FacetSettings::registerFacet('stock', 'Stock Status');
```

# How can I pre-filter listings?

There may be occasions where you need to pre-filter listings. For example, you may want to only show listings that have stock:

```php
\Aero\Search\Elastic\Repositories\ElasticListings::extend(function (\Aero\Search\Elastic\Repositories\ElasticListings $listings) {
    $filter = new \Elastica\Query\Term(['number-sort.has-stock' => ['value' => 1]]);

    $listings->base()->getQuery()->addFilter($filter);
});
```

---

### Parameters

<table border="1" id="bkmrk-parameter-type-descr" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr><td>**Parameter**  
</td><td>**Type**  
</td><td>**Description**  
</td></tr><tr><td>`$listings`  
</td><td>`\Aero\Search\Elastic\Repositories\ElasticListings`  
</td><td>The elastic listing repository.</td></tr></tbody></table>

### Methods

<table border="1" id="bkmrk-method-description-b" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.1548%;"></col><col style="width: 66.9644%;"></col></colgroup><tbody><tr><td>**Method**  
</td><td>**Description**  
</td></tr><tr><td>`base()`  
</td><td>Returns the underlying `\Elastica\Query` instance, use `->getQuery()` to access the `\Elastica\Query\BoolQuery` directly.</td></tr></tbody></table>

# How do I create a custom sort order for the listings pages?

When you set the sort get parameter in a listing pages url (e.g. ?sort=name-az), a method is called on the`\Aero\Search\Elastic\Query\Builder` class (for name-az the method `sortByNameAz` is called).

You can find the default sort by methods in the `Aero\Search\Elastic\Concerns\CanBeSorted` trait.

To add a custom sort to the listings page you therefore just need to macro a method to the `\Aero\Search\Elastic\Query\Builder` class using the correct naming convention.

For example, we will add a summary sort:

```php
\Aero\Search\Elastic\Documents\ListingDocument::add('string-sort', function (\Aero\Search\Elastic\Documents\ListingDocument $document) {
    return [
        'summary' => $document->getModel()->product->getTranslation('summary', $document->getLanguage()),
    ];
});

 \Aero\Search\Elastic\Query\Builder::macro('sortBySummaryAz', function () {
     $this->query->addSort([
         'string-sort.summary' => [
             'order' => 'asc',
         ],
     ]);
 });

 \Aero\Search\Elastic\Query\Builder::macro('sortBySummaryZa', function () {
     $this->query->addSort([
         'string-sort.summary' => [
             'order' => 'desc',
         ],
     ]);
});
```

<p class="callout warning">You must run `php artisan aero:search:reindex --type=listing` for these document changes to be applied to existing listings.</p>

You can add these as selectable options in the sort-by dropdown on the listings page via the `search` settings `sort_by_options`.

### Advanced Query

It’s possible to do a more advanced query for your Elasticsearch sort by. You can make use of [Elasticsearch’s Painless scripting language](https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-painless.html)

For example, you can add a sort that will execute Java to randomly sort listings:

```php
\Aero\Search\Elastic\Query\Builder::macro('sortByRand', function () {
    $this->query->addSort([
        '_script' => [
            'type' => 'number',
            'script' => [
                'lang' => \Elastica\Script\Script::LANG_PAINLESS,
                'source' => $this->loadJavaFile(__DIR__.'/../scripts/random-sort.java'),
                'params' => $this->parameters,
           ],
           'order' => 'asc',
        ],
    ]);
});
```

```java
// random-sort.java

def sorts = new ArrayList();

Random rand = new Random();
sorts.add(rand.nextInt(1000000));

if (sorts.size() === 0) {
  sorts.add(0);
}

Collections.min(sorts.stream().map(Double::doubleToRawLongBits).collect(Collectors.toList()));
```