Dans l'article précédent, nous nous sommes familiarisés avec les capacités du composant de recherche intelligente de Joomla, avons parlé des paramètres et de la configuration de l'indexation planifiée à l'aide de CRON. Commençons par créer le code de notre propre plugin.
Avant de commencer la partie technique, je mentionnerai quelques articles qui abordent directement le sujet principal. Ainsi que des articles qui couvrent généralement la création et/ou la mise à jour d'un plugin pour l'architecture moderne de Joomla 4 / Joomla 5. Ensuite, je supposerai que le lecteur les a lus et a généralement une idée sur la façon de créer un plugin fonctionnel. pour Joomla :
Pour les développeurs expérimentés, je dirai que le plugin de recherche étend la classe JoomlaComponentFinderAdministratorIndexerAdapter, le fichier de classe se trouve dans administrator/components/com_finder/src/Indexer/Adapter.php. Eh bien, alors ils le découvriront par eux-mêmes. En outre, à titre d'exemple, vous pouvez étudier les principaux plugins de recherche intelligente de Joomla - pour les articles, les catégories, les contacts, les balises, etc. - dans le dossier plugins/finder. J'ai travaillé sur un plugin de recherche intelligent pour les composants JoomShopping (composant e-commerce Joomla) et SW JProjects (votre propre composant de répertoire d'extensions Joomla avec serveur de mise à jour), de sorte que les noms de classe et certaines nuances leur seront associés. Je vais en montrer l'essentiel en utilisant l'exemple de JoomShopping. La solution à la question du multilinguisme s'appuie sur l'exemple de SW JProjects.
La structure des fichiers du plugin de recherche intelligente pour Joomshopping ne diffère pas de celle typique :
Structure des fichiers du plugin de recherche intelligente Joomla 5
Le fichier provider.php vous permet d'enregistrer un plugin dans un conteneur Joomla DI et vous permet d'accéder aux méthodes du plugin de l'extérieur en utilisant MVCFactory.
<?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; } ); } };
Il s'agit du fichier qui contient le code de travail principal de votre plugin. Il doit se trouver dans le dossier src/Extension. Dans mon cas, la classe du plugin JoomlaPluginFinderWtjoomshoppingfinderExtensionWtjoomshoppingfinder est dans le fichier plugins/finder/wtjoomshoppingfinder/src/Extension/Wtjoomshoppingfinder.php. L'espace de noms du plugin est JoomlaPluginFinderWtjoomshoppingfinderExtension.
Il existe un ensemble minimal de propriétés et de méthodes de classe requises pour le fonctionnement (elles sont accessibles, y compris par la classe Adapter parent).
<?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; } ); } };
... et ici nous commençons à entrer dans les détails, puisque la méthode getListQuery() n'est pas vraiment obligatoire, malgré le fait que la documentation et la plupart des articles en parlent.
N'importe quelle image sur le thème du « schéma complexe » fera l'affaire ici.
C'est incroyable combien de fois une information ou une idée nous passe parfois en cercle avant que nous ne la remarquions et ne nous en rendions compte ! Beaucoup de choses, étant sous nos yeux depuis plus d'un an, n'atteignent toujours pas notre conscience, et notre attention ne se concentre sur elles qu'après des années d'expérience.
En ce qui concerne Joomla, pour une raison quelconque, la vision ne vient pas immédiatement que ses composants assument une sorte d'architecture commune caractéristique de Joomla (bien que ce soit un fait évident). Y compris au niveau de la structure des tables de la base de données. Regardons quelques champs de la table de contenu Joomla. Je ferai une réserve sur le fait que les noms de colonnes spécifiques ne sont pas si importants pour nous (vous pouvez toujours interroger SELECT nom AS titre), quelle est la structure des données pour un élément indexé :
Si l'on compare les tableaux #__content (articles Joomla), #__contact_details (composant contact), #__tags (balises Joomla), #__categories (composant catégorie Joomla), alors nous retrouverons presque tous les types de données répertoriés partout.
Si le composant pour lequel les plugins de recherche intelligente sont créés a suivi la "voie Joomla" et hérite de son architecture, alors vous pouvez vous contenter d'un minimum de méthodes dans la classe plugin. Si les développeurs décident de ne pas chercher de solutions faciles et de suivre leur propre chemin, vous devrez alors emprunter la voie difficile, en redéfinissant presque toutes les méthodes de la classe Adapter.
Cette méthode est appelée dans 3 cas :
Regardons un exemple d'implémentation dans les plugins principaux de Joomla :
<?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; } ); } };
La méthode getListQuery() renvoie un objet DatabaseQuery, un objet du constructeur de requête, où le nom de la table et les champs de sélection sont déjà spécifiés. Le travail avec lui se poursuit dans les méthodes qui l'appellent.
Si getListQuery() est appelé depuis getContentCount() dans l'objet DatabaseQuery $query, les valeurs définies pour select sont remplacées par COUNT(*).
Si getListQuery() est appelé depuis getItem($id), la condition $query->where('a.id = ' . (int) $id) et seul un élément spécifique est sélectionné. Et déjà ici, nous voyons que la classe parent Adapter contient le nom de la table dans la requête sous la forme a.*. Cela signifie que nous devons également utiliser ces préfixes dans notre implémentation de la méthode getListQuery().
Dans le cas de l'appel de getListQuery() depuis getItems(), $offset et $limit sont ajoutés à la requête que nous avons construite afin de parcourir la liste des éléments à indexer.
Résumé : getListQuery() - doit contenir un "élément de travail" pour trois requêtes SQL différentes. Et il n'y a rien de particulièrement difficile à implémenter Joomla ici. Mais, si nécessaire, vous pouvez implémenter vous-même 3 méthodes sans créer getListQuery().
Façon non Joomla : Dans le cas de JoomShopping, je suis tombé sur le fait qu'un produit peut avoir plusieurs catégories et qu'historiquement, le composant identifiant de catégorie (catid) du produit était stocké dans une table séparée. Dans le même temps, pendant de nombreuses années, il n’a pas été possible de préciser la catégorie principale du produit. Dès réception de la catégorie de produit, une requête a été envoyée à la table avec les catégories, où seul le premier résultat de la requête a été pris, trié par identifiant de catégorie par défaut, c'est-à-dire par ordre croissant. Si nous modifiions les catégories lors de la modification d'un produit, alors la catégorie de produit principale était celle avec le numéro d'identification le plus bas. L'URL du produit était basée sur celle-ci et le produit pouvait passer d'une catégorie à une autre.
Mais il y a presque 2 ans, ce comportement de JoomShopping a été corrigé. Étant donné que le composant a une longue histoire, un large public et ne peut pas simplement rompre la compatibilité descendante, le correctif a été rendu facultatif. La possibilité de spécifier la catégorie principale du produit doit être activée dans les paramètres du composant. Ensuite, le main_category_id sera renseigné dans le tableau avec les produits.
Mais cette fonctionnalité est désactivée par défaut. Et dans le plugin de recherche intelligente, nous devons obtenir les paramètres du composant JoomShopping, voir si l'option pour spécifier la catégorie principale de produits est activée (et elle peut être activé récemment et la catégorie principale pour certains produits n'est pas spécifiée - également une nuance...) et générer une requête SQL pour recevoir le(s) produit(s) en fonction des paramètres du composant : soit une simple requête où l'on ajoute le champ main_category_id , ou un Rejoignez la demande pour obtenir l'identifiant de la catégorie dans le mauvais sens.
Immédiatement, la nuance du multilinguisme apparaît dans cette demande. Selon la méthode Joomla, un élément distinct est créé pour chaque langue du site et des associations sont établies entre eux. Donc, pour la langue russe - un article. Le même article en anglais est créé séparément. Ensuite, nous les connectons entre eux à l'aide d'associations de langues et lors du changement de langue sur le frontend Joomla, nous serons redirigés d'un article à l'autre.
Ce n'est pas ainsi que cela se fait dans JoomShopping : les données pour toutes les langues sont stockées dans le même tableau avec les produits (Ok). L'ajout de données pour d'autres langues se fait en ajoutant des colonnes avec le suffixe de ces langues (hmm...). Autrement dit, nous n’avons pas seulement un champ de titre ou de nom dans la base de données. Mais il y a des champs name_ru-RU, name_en-GB, etc.
Fragment de structure de table de produits Joomla JoomShopping
Dans le même temps, nous devons concevoir une requête SQL universelle afin qu'elle puisse être indexée à la fois depuis le panneau d'administration et la CLI. Dans le même temps, choisir le langage d'indexation lors du lancement de la CLI à l'aide de CRON est également une tâche. J'avoue qu'au moment de la rédaction de cet article, j'ai reporté pour le moment une solution à part entière à ce problème. La langue est sélectionnée à l'aide de notre propre méthode getLangTag(), où nous prenons soit la langue principale des paramètres de JoomShopping, soit la langue par défaut du site. Autrement dit, jusqu'à présent, cette solution concerne uniquement un site monolingue. La recherche dans différentes langues ne fonctionnera pas encore.
Cependant, 3 mois plus tard, j'ai résolu ce problème, mais déjà dans le plugin de recherche intelligente pour le composant SW JProjects. Je vous parlerai plus loin de la solution.
En attendant, regardons ce qui s'est passé pour JoomShopping
<?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; } ); } };
Nous avons créé une méthode pour interroger la base de données de Joomla et avons beaucoup appris sur le fonctionnement du plugin de recherche intelligente.
Dans le prochain article, nous créerons une méthode d'indexation du contenu et terminerons la création du plugin. Nous nous familiariserons également avec la manière dont les éléments indexés sont stockés dans la base de données, comprendrons pourquoi cela est important et résoudrons le problème de l'indexation du contenu pour les composants multilingues avec une implémentation non standard du multilinguisme.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!