Wie man eine eigene Entity in die Suche integriert hatte ich schon erklärt. Was aber wenn man eine vorhandene Entity um weitere durchsuchbare Felder erweitern will? Das geht auch relativ einfach.
Basiert auf diesem Forums-Post: https://forum.shopware.com/t/admin-such-optimierung-default-search-configuration-js/94397/3
const module = Module.getModuleByEntityName('customer');
if (module?.manifest?.defaultSearchConfiguration) {
module.manifest.defaultSearchConfiguration = {
...module.manifest.defaultSearchConfiguration,
extensions: {
// In case some other plugin has already done this trick; we do not want to remove theirs.
...(module.manifest.defaultSearchConfiguration.extensions ?? {}),
// Add our extension fields to the omnisearch
customFields: {
customer_debitor_set_number: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
},
}
},
};
}
Auch wenn dort die Felder hierarchisch angegeben werden, sind diese bei den Snippets flach strukturiert.
{
"sw-profile": {
"tabSearchPreferences": {
"modules": {
"customer": {
"customer_debitor_set_number": "Debitor-Nummer"
}
}
}
}
}
Jetzt kann man die Darstellung der Suchergebnis-Items verbessern, damit man weiß, was man wo gefunden hat. Aber das ist an sich erstmal optional.
Oft ist es sehr viel einfacher direkt etwas in die Suche der Administration einzugeben, als umständlich eine Seite zu öffnen und etwas aus der Liste per Hand oder Browser-Suche heraus zu suchen.
Eigene oder fehlende Entitäten dort zu integrieren ist an sich recht einfach und logisch. Es gibt hier eine Anleitung die aber leider so für mich nicht funktioniert hat, weil ein wichtiger Teil fehlte.
https://developer.shopware.com/docs/guides/plugins/plugins/administration/search-custom-data.html
Routennamen sind hier erstmal nur Beispiel haft vergeben.
Step 1
Ich gehe davon aus das ein Plugin existiert mit einem JS-Module, das mindestens eine Route hat und dessen Name nach dem Schema {vendor}-{name} aufgebaut ist. Zum Module müssen wir wie beschrieben
einige wenige Dinge ergänzen:
{
...
entity: 'ce_my_entity',
...
}
hier kommt später noch was dazu!
Step 2
Den Type (der Entität) hinzufügen. Der Name muss nicht dem Namen der Entität entsprechen, ist aber nicht verkehrt es so zu machen.
Application.addServiceProviderDecorator('searchTypeService', searchTypeService => {
searchTypeService.upsertType('ce_my_entity', {
entityName: 'ce_my_entity',
placeholderSnippet: 'search.general.placeholderSearchBar',
listingRoute: 'hpr.searchexample.index',
});
return searchTypeService;
});
Damit kennt die Suche nun den neuen Type und dann theoretisch schon danach suchen.
Step 3
Jetzt müssen wir festlegen wie unsere Entität bei den Ergebnissen dargestellt werden soll. Dafür erweitern wir ein Template und machen es der Suche bekannt.
Twig:
{% block sw_search_bar_item_cms_page %}
{% parent %}
<router-link v-else-if="type === 'ce_my_entity'"
v-bind:to="{ name: 'hpr.searchexample.detail', params: { id: item.id } }"
ref="routerLink"
class="sw-search-bar-item__link">
{% block sw_search_bar_item_ceme_label %}
<span class="sw-search-bar-item__label">
<sw-highlight-text v-bind:searchTerm="searchTerm"
v-bind:text="item.name">
</sw-highlight-text>
</span>
{% endblock %}
</router-link>
{% endblock %}
JavaScript:
Shopware.Component.override('sw-search-bar-item', {
template: templateItem
});
Step 4
Jetzt muss die Suche nur noch wissen wie unsere Entität bezeichnet werden soll, was wir bei den Snippets des Modules mit hinterlegen.
{
"global": {
"entities": {
"ce_my_entity": "Meine Entität"
}
}
}
Was noch fehlt
Soweit ist alles gut und nach Anleitung. Aber es funktionierte einfach nicht. Es wurde nach allen möglichen Entitäten gesucht nur nicht nach der eigenen. Nach viel Gesuche kam ich dann darauf, dass bei den Preferences, die für die Liste der Entities genutzt wird, meine eigene garnicht aufgelistet wurde. Warum? Weil ich natürlich keine Preferences dafür hinterlegt hatte, weil es nirgendwo angegeben war.
Das JS-Module muss also so aussehen:
{
...
entity: 'ce_my_entity',
defaultSearchConfiguration: {
_searchable: true,
name: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
},
description: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
}
}
...
}
Diese Preferences findet man im Profile seine Admin-Users und kann es dort alles noch genauer anpassen, wie die Suche suchen soll. Hier werden nun die Felder name und description angeboten und auch direkt aktiviert.
Damit funktionierte die Suche dann auch sofort wie gewünscht.
Vor einiger Zeit hatte ich eine PLZ-Suche implementiert, bei der Locations bis zu einer bestimmten Distanz zu einer 2. per PLZ identifizierten Location als Ergebnismenge heraus gesucht wurden.
Es wurde also Also eine Distanz zwischen 2 per GPS-Koordinaten beschrieben Locations berechnet. Ich habe das in PHP erledigt, weil die Locations der zu findenen Orte in einer JSON-Datei vorlagen und ich mir den Import in die MySQL-DB ersparen wollte. Die Orte per PLZ kamen aber aus der MySQL-DB, wobei immer der erste Ort mit der PLZ die Koordinaten geliefert hat.
/**
* https://www.geodatasource.com/developers/php
*
* @param $lat1
* @param $lon1
* @param $lat2
* @param $lon2
* @param string $unit
* @return float
*/
public function calcDistance($lat1, $lon1, $lat2, $lon2, $unit = 'K'): float
{
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
$unit = strtoupper($unit);
if ($unit == 'K') {
return ($miles * 1.609344);
} else if ($unit == 'N') {
return ($miles * 0.8684);
} else {
return $miles;
}
}
Auf der Seite findet man auch Code für PL/SQL (Oracle), Java und JavaScript. Nur leider für MySQL nicht. Die findet man aber hier. Im Grunde kann man sich das aber für jede Sprache selbst ableiten, weil es normale Mathematische Berechnungen sind ohne dass zusätzliche Libs oder Klassen nötig wären (die es aber auch gibt und diese Logik kapseln).
Jeden Falls sind solche Berechnungen an sich ganz einfach und performant, so dass man sich alles schnell selbst schreiben kann und nicht auf 3rd Party Lösungen angewiesen ist.