在上一篇文章中,我们熟悉了Joomla智能搜索组件的功能,讨论了使用CRON进行定时索引的参数和配置。让我们开始为我们自己的插件创建代码。
在开始技术部分之前,我将提到一些直接涉及主题的文章。以及一般涵盖 Joomla 4 / Joomla 5 现代架构的插件的创建和/或更新的文章。接下来,我将假设读者已经阅读了它们并且通常了解如何制作工作插件对于 Joomla:
对于有经验的开发人员,我会说搜索插件扩展了 JoomlaComponentFinderAdministratorIndexerAdapter 类,该类文件位于 administrator/components/com_finder/src/Indexer/Adapter.php。好吧,然后他们会自己解决这个问题。此外,作为示例,您可以在 plugins/finder 文件夹中研究 Joomla 核心智能搜索插件 - 用于文章、类别、联系人、标签等。我为 JoomShopping(Joomla 电子商务组件)和 SW JProjects(您自己的带有更新服务器的 Joomla 扩展目录组件)组件开发了一个智能搜索插件,因此类名称和一些细微差别将与它们相关联。我将使用 JoomShopping 的示例来展示其中的大部分内容。多语言问题的解决方案基于 SW JProjects 的示例。
Joomshopping 智能搜索插件的文件结构与典型的没有什么不同:
Joomla 5 智能搜索插件文件结构
文件provider.php允许您在Joomla DI容器中注册插件,并允许您使用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; } ); } };
这是包含插件主要工作代码的文件。它应该位于 src/Extension 文件夹中。就我而言,插件类 JoomlaPluginFinderWtjoomshoppingfinderExtensionWtjoomshoppingfinder 位于文件 plugins/finder/wtjoomshoppingfinder/src/Extension/Wtjoomshoppingfinder.php 中。该插件的命名空间是 JoomlaPluginFinderWtjoomshoppingfinderExtension。
操作需要最少的类属性和方法集(它们可以被访问,包括由父 Adapter 类访问)。
<?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; } ); } };
…这里我们开始深入研究细节,因为 getListQuery() 方法并不是真正强制性的,尽管文档和大多数文章都讨论了它。
任何关于“复杂方案”主题的图片都可以在这里。
令人惊讶的是,有时一些信息或想法在我们注意到并意识到之前就已经在我们身边转了一圈了!很多东西,在我们眼前一年多了,还没有达到认知,需要经过多年的体验,我们的注意力才集中到它们上。
关于 Joomla,由于某种原因,它的组件并没有立即呈现出 Joomla 的某种通用架构特征(尽管这是一个明显的事实)。包括数据库表结构层面。让我们看一下 Joomla 内容表的一些字段。我会保留的是,具体的列名对我们来说并不是那么重要(你可以随时查询 SELECT name AS title),一个索引元素的数据结构是多少:
如果我们比较表#__content(Joomla文章)、#__contact_details(联系人组件)、#__tags(Joomla标签)、#__categories(Joomla类别组件),那么我们会发现几乎所有列出的数据类型无处不在。
如果创建智能搜索插件的组件遵循“Joomla方式”并继承其架构,那么您可以在插件类中使用最少的方法。如果开发人员决定不寻找简单的方法而走自己的路,那么您将不得不走困难的路,重新定义 Adapter 类的几乎所有方法。
此方法在 3 种情况下被调用:
让我们看一下 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; } ); } };
getListQuery()方法返回一个DatabaseQuery对象,该对象是查询构造函数的对象,其中已经指定了表的名称和选择的字段。在调用它的方法中继续使用它。
如果从 DatabaseQuery $query 对象中的 getContentCount() 调用 getListQuery(),则 select 的设置值将替换为 COUNT(*)。
如果从 getItem($id) 调用 getListQuery(),则条件 $query->where('a.id = ' . (int) $id) 并且仅选择特定元素。在这里我们已经看到父 Adapter 类在查询中包含作为 a.* 的表名称。这意味着我们还应该在 getListQuery() 方法的实现中使用这些前缀。
在从 getItems() 调用 getListQuery() 的情况下,$offset 和 $limit 将添加到我们构建的查询中,以便在元素列表中移动以进行索引。
总结: getListQuery() - 必须包含三个不同 SQL 查询的“工作片段”。 在这里实现 Joomla 并没有什么特别困难的。但是,如果有必要,您可以自己实现 3 个方法,而无需创建 getListQuery()。
非 Joomla 方式: 就 JoomShopping 而言,我发现一个产品可以有多个类别,并且历史上该产品的类别 id (catid) 组件存储在单独的表中。同时,多年来一直无法指定产品的主要类别。收到产品类别后,查询将发送到类别表,其中仅获取第一个查询结果,按默认类别 ID 排序 - 即升序。如果我们在编辑产品时更改类别,则主要产品类别是 ID 号较小的类别。产品的 URL 以此为基础,产品可以从一个类别跳转到另一个类别。
但是,大约 2 年前,这种 JoomShopping 行为已得到修复。由于该组件历史悠久,受众众多,并且不能仅仅破坏向后兼容性,修复是可选的。必须在组件设置中启用指定产品主类别的功能。然后 main_category_id 将被填充到带有产品的表中。
但是这个功能默认是关闭的。而在智能搜索插件中,我们需要获取JoomShopping组件的参数,看看是否启用了指定主商品类别的选项(并且它最近可能启用,并且未指定某些产品的主类别 - 也是一个细微差别...)并生成 SQL 查询以根据组件参数接收产品:或者是一个简单的查询,其中我们添加 main_category_id字段,或以旧的错误方式获取类别 id 的 JOIN 请求。
在此请求中,多语言的细微差别立即凸显出来。根据 Joomla 方式,为网站的每种语言创建一个单独的元素,并在它们之间建立关联。因此,对于俄语 - 一篇文章。同一篇英文文章正在单独创建。然后我们使用语言关联将它们相互连接起来,当在 Joomla 前端切换语言时,我们将从一篇文章重定向到另一篇文章。
这不是 JoomShopping 中的做法:所有语言的数据都与产品存储在同一个表中(好的)。添加其他语言的数据是通过添加带有这些语言后缀的列来完成的(嗯...)。也就是说,我们的数据库中不仅仅有标题或名称字段。但还有 name_ru-RU、name_en-GB 等字段
Joomla JoomShopping 产品表结构片段
同时,我们需要设计一个通用的 SQL 查询,以便可以从管理面板和 CLI 对其进行索引。同时,使用 CRON 启动 CLI 时选择索引语言也是一项任务。我承认,在撰写本文时,我暂时推迟了对该问题的全面解决方案。使用我们自己的 getLangTag() 方法选择语言,我们可以从 JoomShopping 参数中获取主要语言,也可以使用网站的默认语言。也就是说,到目前为止,该解决方案仅适用于单语言网站。目前还无法进行不同语言的搜索。
但是,3个月后我解决了这个问题,但已经在 SW JProjects 组件的智能搜索插件中了。我会进一步告诉你解决方案。
同时,让我们看看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; } ); } };
我们创建了一种从 Joomla 查询数据库的方法,并了解了很多有关智能搜索插件如何工作的知识。
在下一篇文章中,我们将创建一个索引内容的方法并完成插件的创建。我们还将熟悉索引项如何存储在数据库中,并理解为什么这很重要,并通过多语言的非标准实现解决多语言组件的索引内容问题。
以上是Joomla 艺术中智能搜索的剖析 创建插件 I.的详细内容。更多信息请关注PHP中文网其他相关文章!