Im vorherigen Artikel haben wir uns mit den Funktionen der intelligenten Suchkomponente von Joomla vertraut gemacht und über die Parameter und Konfiguration der geplanten Indizierung mit CRON gesprochen. Beginnen wir mit der Erstellung des Codes für unser eigenes Plugin.
Bevor ich mit dem technischen Teil beginne, werde ich einige Artikel erwähnen, die sich direkt mit dem Hauptthema befassen. Sowie Artikel, die sich im Allgemeinen mit der Erstellung und/oder Aktualisierung eines Plugins für die moderne Architektur von Joomla 4 / Joomla 5 befassen. Als nächstes gehe ich davon aus, dass der Leser sie gelesen hat und im Allgemeinen eine Vorstellung davon hat, wie man ein funktionierendes Plugin erstellt für Joomla:
Für erfahrene Entwickler möchte ich sagen, dass das Such-Plugin die Klasse JoomlaComponentFinderAdministratorIndexerAdapter erweitert. Die Klassendatei befindet sich in administrator/components/com_finder/src/Indexer/Adapter.php. Dann werden sie es selbst herausfinden. Als Beispiel können Sie auch die zentralen Plugins für die intelligente Suche von Joomla – für Artikel, Kategorien, Kontakte, Tags usw. – im Ordner plugins/finder studieren. Ich habe an einem intelligenten Such-Plugin für die Komponenten JoomShopping (Joomla E-Commerce-Komponente) und SW JProjects (Ihre eigene Joomla-Erweiterungsverzeichniskomponente mit Update-Server) gearbeitet, sodass ihnen die Klassennamen und einige Nuancen zugeordnet werden. Das meiste davon zeige ich am Beispiel von JoomShopping. Die Lösung des Problems der Mehrsprachigkeit erfolgt am Beispiel von SW JProjects.
Die Dateistruktur des Smart Search Plugins für Joomshopping unterscheidet sich nicht von der typischen:
Dateistruktur des Joomla 5 Smart Search Plugins
Mit der Datei provider.php können Sie ein Plugin in einem Joomla DI-Container registrieren und über MVCFactory von außen auf Plugin-Methoden zugreifen.
<?php /** * @package Joomla.Plugin * @subpackage Finder.Wtjoomshoppingfinder * * @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ \defined('_JEXEC') or die; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder; return new class () implements ServiceProviderInterface { /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 4.3.0 */ public function register(Container $container) { $container->set( PluginInterface::class, function (Container $container) { $plugin = new Wtjoomshoppingfinder( $container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder') ); $plugin->setApplication(Factory::getApplication()); // Our plugin uses DatabaseTrait, so the setDatabase() method appeared // If it is not present, then we use only setApplication(). $plugin->setDatabase($container->get(DatabaseInterface::class)); return $plugin; } ); } };
Dies ist die Datei, die den Hauptarbeitscode Ihres Plugins enthält. Es sollte sich im Ordner src/Extension befinden. In meinem Fall befindet sich die Plugin-Klasse JoomlaPluginFinderWtjoomshoppingfinderExtensionWtjoomshoppingfinder in der Datei plugins/finder/wtjoomshoppingfinder/src/Extension/Wtjoomshoppingfinder.php. Der Namensraum des Plugins ist JoomlaPluginFinderWtjoomshoppingfinderExtension.
Für den Betrieb ist ein minimaler Satz an Klasseneigenschaften und -methoden erforderlich (auf sie wird zugegriffen, auch von der übergeordneten Adapterklasse).
<?php /** * @package Joomla.Plugin * @subpackage Finder.Wtjoomshoppingfinder * * @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ \defined('_JEXEC') or die; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder; return new class () implements ServiceProviderInterface { /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 4.3.0 */ public function register(Container $container) { $container->set( PluginInterface::class, function (Container $container) { $plugin = new Wtjoomshoppingfinder( $container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder') ); $plugin->setApplication(Factory::getApplication()); // Our plugin uses DatabaseTrait, so the setDatabase() method appeared // If it is not present, then we use only setApplication(). $plugin->setDatabase($container->get(DatabaseInterface::class)); return $plugin; } ); } };
... und hier fangen wir an, in die Details einzutauchen, da die getListQuery()-Methode nicht wirklich obligatorisch ist, obwohl sowohl in der Dokumentation als auch in den meisten Artikeln darüber gesprochen wird.
Jedes Bild zum Thema „komplexes Schema“ reicht hier aus.
Es ist erstaunlich, wie oft eine Information oder Idee manchmal im Kreis an uns vorbeizieht, bevor wir sie bemerken und realisieren! Viele Dinge, die länger als ein Jahr vor unseren Augen liegen, werden uns immer noch nicht bewusst und unsere Aufmerksamkeit richtet sich erst nach jahrelanger Erfahrung auf sie.
Im Zusammenhang mit Joomla kommt aus irgendeinem Grund nicht sofort die Vision auf, dass seine Komponenten eine Art gemeinsame Architektur annehmen, die für Joomla charakteristisch ist (obwohl dies eine offensichtliche Tatsache ist). Einschließlich auf der Ebene der Datenbanktabellenstruktur. Schauen wir uns einige Felder der Joomla-Inhaltstabelle an. Ich mache einen Vorbehalt, dass bestimmte Spaltennamen für uns nicht so wichtig sind (Sie können immer SELECT name AS title abfragen), wie groß ist die Datenstruktur für ein indiziertes Element:
Wenn wir die Tabellen #__content (Joomla-Artikel), #__contact_details (Kontaktkomponente), #__tags (Joomla-Tags), #__categories (Joomla-Kategoriekomponente) vergleichen, dann finden wir fast alle aufgeführten Datentypen überall.
Wenn die Komponente, für die intelligente Such-Plugins erstellt werden, der „Joomla-Methode“ folgt und deren Architektur erbt, können Sie mit einem Minimum an Methoden in der Plugin-Klasse auskommen. Wenn die Entwickler beschließen, nicht nach einfachen Wegen zu suchen und ihren eigenen Weg zu gehen, müssen Sie den harten Weg gehen und fast alle Methoden der Adapter-Klasse neu definieren.
Diese Methode wird in 3 Fällen aufgerufen:
Sehen wir uns ein Beispiel für die Implementierung in Joomla-Kern-Plugins an:
<?php /** * @package Joomla.Plugin * @subpackage Finder.Wtjoomshoppingfinder * * @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ \defined('_JEXEC') or die; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder; return new class () implements ServiceProviderInterface { /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 4.3.0 */ public function register(Container $container) { $container->set( PluginInterface::class, function (Container $container) { $plugin = new Wtjoomshoppingfinder( $container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder') ); $plugin->setApplication(Factory::getApplication()); // Our plugin uses DatabaseTrait, so the setDatabase() method appeared // If it is not present, then we use only setApplication(). $plugin->setDatabase($container->get(DatabaseInterface::class)); return $plugin; } ); } };
Die Methode getListQuery() gibt ein DatabaseQuery-Objekt zurück, ein Objekt des Abfragekonstruktors, in dem der Name der Tabelle und die Felder zur Auswahl bereits angegeben sind. Die Arbeit damit wird in den Methoden fortgesetzt, die es aufrufen.
Wenn getListQuery() von getContentCount() im DatabaseQuery $query-Objekt aufgerufen wird, werden die eingestellten Werte für select durch COUNT(*) ersetzt.
Wenn getListQuery() von getItem($id) aufgerufen wird, gilt die Bedingung $query->where('a.id = ' . (int) $id) und es wird nur ein bestimmtes Element ausgewählt. Und schon hier sehen wir, dass die übergeordnete Adapterklasse den Tabellennamen in der Abfrage als.* enthält. Das bedeutet, dass wir diese Präfixe auch in unserer Implementierung der getListQuery()-Methode verwenden sollten.
Im Falle des Aufrufs von getListQuery() aus getItems() werden $offset und $limit zu der von uns erstellten Abfrage hinzugefügt, um durch die Liste der zu indizierenden Elemente zu navigieren.
Zusammenfassung: getListQuery() – muss ein „Arbeitsstück“ für drei verschiedene SQL-Abfragen enthalten. Und es ist hier nichts besonders Schwieriges, Joomla zu implementieren. Bei Bedarf können Sie jedoch drei Methoden selbst implementieren, ohne getListQuery() zu erstellen.
Nicht Joomla-Methode: Im Fall von JoomShopping bin ich auf die Tatsache gestoßen, dass ein Produkt mehrere Kategorien haben kann und in der Vergangenheit die Kategorie-ID-Komponente (Catid) für das Produkt in einer separaten Tabelle gespeichert wurde. Gleichzeitig war es viele Jahre lang nicht möglich, die Hauptkategorie für das Produkt festzulegen. Nach Erhalt der Produktkategorie wurde eine Abfrage an die Tabelle mit Kategorien gesendet, wo nur das erste Abfrageergebnis übernommen wurde, sortiert nach der Standardkategorie-ID – also aufsteigend. Wenn wir beim Bearbeiten eines Produkts die Kategorie geändert haben, war die Hauptproduktkategorie diejenige mit der niedrigeren ID-Nummer. Die URL des Produkts basierte darauf und das Produkt konnte von einer Kategorie zur anderen springen.
Aber vor fast 2 Jahren wurde dieses JoomShopping-Verhalten behoben. Da die Komponente eine lange Geschichte und ein großes Publikum hat und die Abwärtskompatibilität nicht einfach aufgehoben werden kann, wurde der Fix optional gemacht. Die Möglichkeit, die Hauptkategorie für das Produkt anzugeben, muss in den Komponenteneinstellungen aktiviert sein. Anschließend wird die main_category_id mit den Produkten in die Tabelle eingetragen.
Aber diese Funktionalität ist standardmäßig deaktiviert. Und im intelligenten Such-Plugin müssen wir die Parameter der JoomShopping-Komponente abrufen und prüfen, ob die Option zum Angeben der Hauptproduktkategorie aktiviert ist (und es wurde möglicherweise kürzlich aktiviert und die Hauptkategorie für einige Produkte ist nicht angegeben – ebenfalls eine Nuance …) und generiert eine SQL-Abfrage, um das/die Produkt(e) basierend auf den Komponentenparametern zu erhalten: entweder eine einfache Abfrage, bei der wir das Feld main_category_id hinzufügen , oder ein JOIN-Anfrage, um die Kategorie-ID auf die alte falsche Weise zu erhalten.
Bei dieser Anfrage kommt sofort die Nuance der Mehrsprachigkeit zum Vorschein. Gemäß der Joomla-Methode wird für jede Sprache der Website ein separates Element erstellt und es werden Verknüpfungen zwischen ihnen eingerichtet. Also für die russische Sprache - ein Artikel. Der gleiche Artikel auf Englisch wird separat erstellt. Dann verbinden wir sie über Sprachzuordnungen miteinander und beim Wechsel der Sprache im Joomla-Frontend werden wir von einem Artikel zum anderen weitergeleitet.
So wird es in JoomShopping nicht gemacht: Daten für alle Sprachen werden in derselben Tabelle mit Produkten gespeichert (Ok). Das Hinzufügen von Daten für andere Sprachen erfolgt durch Hinzufügen von Spalten mit dem Suffix dieser Sprachen (hmm...). Das heißt, wir haben nicht nur ein Titel- oder Namensfeld in der Datenbank. Aber es gibt Felder name_ru-RU, name_en-GB usw.
Strukturfragment der Joomla JoomShopping-Produkttabelle
Gleichzeitig müssen wir eine universelle SQL-Abfrage entwerfen, damit sie sowohl über das Admin-Panel als auch über die CLI indiziert werden kann. Gleichzeitig ist auch die Auswahl der Indizierungssprache beim Starten der CLI mit CRON eine Aufgabe. Ich gebe zu, dass ich zum Zeitpunkt des Schreibens dieses Artikels eine umfassende Lösung dieses Problems vorerst verschoben habe. Die Sprache wird mit unserer eigenen getLangTag()-Methode ausgewählt, wobei wir entweder die Hauptsprache aus den JoomShopping-Parametern oder die Standardsprache der Website übernehmen. Das heißt, diese Lösung gilt bisher nur für eine einsprachige Website. Die Suche in verschiedenen Sprachen funktioniert noch nicht.
3 Monate später habe ich dieses Problem jedoch gelöst, allerdings bereits im Smart Search Plugin für die SW JProjects-Komponente. Ich werde Ihnen die Lösung weiter erläutern.
Schauen wir uns in der Zwischenzeit an, was bei JoomShopping passiert ist
<?php /** * @package Joomla.Plugin * @subpackage Finder.Wtjoomshoppingfinder * * @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ \defined('_JEXEC') or die; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder; return new class () implements ServiceProviderInterface { /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 4.3.0 */ public function register(Container $container) { $container->set( PluginInterface::class, function (Container $container) { $plugin = new Wtjoomshoppingfinder( $container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder') ); $plugin->setApplication(Factory::getApplication()); // Our plugin uses DatabaseTrait, so the setDatabase() method appeared // If it is not present, then we use only setApplication(). $plugin->setDatabase($container->get(DatabaseInterface::class)); return $plugin; } ); } };
Wir haben eine Methode zum Abfragen der Datenbank von Joomla erstellt und viel über die Funktionsweise des Smart-Search-Plugins gelernt.
Im nächsten Artikel erstellen wir eine Methode zur Indizierung von Inhalten und schließen die Erstellung des Plugins ab. Wir werden uns auch damit vertraut machen, wie indizierte Elemente in der Datenbank gespeichert werden, verstehen, warum dies wichtig ist, und das Problem der Indizierung von Inhalten für mehrsprachige Komponenten mit einer nicht standardmäßigen Implementierung der Mehrsprachigkeit lösen.
Das obige ist der detaillierte Inhalt vonDie Anatomie der intelligenten Suche in der Joomla-Kunst. Erstellen eines Plugins I.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!