🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 vor 1 Jahr 19 Min Lesezeit
0

The anatomy of smart search in Joomla 5 Part 2: Creating a plugin I.

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

In the - official Joomla documentation. It is for Joomla 3, but most of the provisions remained true for Joomla 4 / Joomla 5


  • by Nicholas Dionysopoulos that covers the development of Joomla! extensions under Joomla versions 4 and 5.


  • (Joomla e-commerce component) and

    Joomla 5 smart searh plugin file structure





    File services/provider.php



    The file provider.php allows you to register a plugin in a Joomla DI container and allows you to access plugin methods from the outside using MVCFactory.




    CODE
    <?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;
    }
    );
    }
    };









    Plugin class file



    This is the file that contains the main working code of your plugin. It should be located in the src/Extension folder. In my case, the plugin class \Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder is in the file plugins/finder/wtjoomshoppingfinder/src/Extension/Wtjoomshoppingfinder.php. The namespace of the plugin is Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension.



    There is a minimal set of class properties and methods required for operation (they are accessed, including by the parent Adapter class).






    The minimum required properties of the class





    • $extension - is the name of your component, which defines the type of your content. For example, com_content. In my case, this is com_jshopping.


    • $context - is a unique identifier for the plugin, it sets the context of indexing, in which the plugin will be accessed. In fact, this is the name of the plugin class (element). In our case, Wtjoomshoppingfinder.


    • $layout - is the name of the output layout for the search results element. This layout is used when displaying search results. For example, if the $layout parameter is set to article, then the default view mode will search for a layout file named default_article.php when you need to display a search result of this type. If such a file is not found, then a layout file with the name default_result.php will be used instead. The output layouts with HTML layout are located in components/com_finder/tmpl/search. However, we should place our layouts as overrides - in the html template folder - templates/YOUR_TEMPLATE/html/com_finder/search. In my case, I named the layout product, and the file is called default_product.php.
      Joomla 5 smart search folder path


    • $table - is the name of the table in the database that we are accessing to get data, for example, #__content. In my case, the main table with JoomShopping products is called #__jshopping_products.


    • $state_field - is the name of the field in the database table that is responsible for whether the indexed element is published or not. By default, this field is called state. However, in the case of JoomShopping, this field is called product_publish.




    CODE
    <?php
    // So far, only the namespaces used in the example are listed here.

    use Joomla\Component\Finder\Administrator\Indexer\Adapter;
    use Joomla\Event\SubscriberInterface;
    use Joomla\Database\DatabaseAwareTrait;

    \defined('_JEXEC') or die;

    final class Wtjoomshoppingfinder extends Adapter implements SubscriberInterface
    {
    // We want to use a setDatabase() method in provider.php and getDatabase() method in our plugin.
    // So let's...
    use DatabaseAwareTrait;

    /**
    * The unique identifier of the plugin. You can specify the name of the class.
    *
    * @var string
    * @since 2.5
    */

    protected $context = 'Wtjoomshoppingfinder';

    /**
    * For which component are we indexing the content
    *
    * @var string
    * @since 2.5
    */

    protected $extension = 'com_jshopping';

    /**
    *
    * The name of the suffix for the search results output sublayout.
    * If it is an "article", then the file name will be "default_article.php "
    *
    * @var string
    * @since 2.5
    */

    protected $layout = 'product';

    /**
    * The type of content being indexed. The user can
    * search only among products, only among tags, only
    * among articles, etc.
    *
    * @var string
    * @since 2.5
    */

    protected $type_title = 'Product';

    /**
    * A field in the database that stores the flag whether the item is published or not.
    * Default is "state"
    *
    * @var string
    * @since 1.0.0
    */

    protected $state_field = 'product_publish';

    /**
    * The name of the database table.
    *
    * @var string
    * @since 2.5
    */

    protected $table = '#__jshopping_products';

    /**
    * Whether to load the plugin's language files when initializing the class.
    *
    * @var boolean
    * @since 3.1
    */

    protected $autoloadLanguage = true;

    /**
    * Language tag for JoomShopping products.
    * Non-standard class property, only we need
    * and only for JoomShopping.
    *
    * @var string
    * @since 3.1
    */

    protected string $languageTag = '';

    }









    The minimum required methods of the class





    • setup() : bool - is a method for pre-configuring the plugin, connecting libraries, etc. The method is called during reindexing (the reindex() method), on the onBeforeIndex event. The method must return true, otherwise indexing will be interrupted.


    • index() : void - is the method to start indexing itself. It collects an object of the desired structure from raw SQL query data, which is then passed to the \Joomla\Component\Finder\Administrator\Indexer\Indexer class for indexing. The method is run for each indexed element. The method argument is $item - the result of a query to the database, formatted in the \Joomla\Component\Finder\Administrator\Indexer\Result class.


    • getListQuery() : Joomla\Database\DatabaseQuery - is a method for getting a list of indexed items…



    ... and here we start to dive into the details, since the getListQuery() method is not really mandatory, despite the fact that both the documentation and most articles talk about it.





    Joomla JoomShopping product table structure fragment

    At the same time, we need to design a universal SQL query so that it can be indexed from both the admin panel and the CLI. At the same time, choosing the indexing language when launching the CLI using CRON is also a task. I admit, at the time of writing this article, I have postponed a full-fledged solution to this problem for the time being. The language is selected using our own getLangTag() method, where we either take the main language from the JoomShopping parameters, or the default language of the site. That is, so far this solution is only for a monolingual site. The search in different languages will not work yet.



    However, 3 months later I solved this problem, but already in the smart search plugin for SW JProjects component. I will tell you about the solution further.



    In the meantime, let's look at what happened for JoomShopping




    CODE
    <?php
    use Joomla\Database\DatabaseQuery;

    /**
    * Method to get the SQL query used to retrieve the list of content items.
    *
    * @param mixed $query A DatabaseQuery object or null.
    *
    * @return DatabaseQuery A database object.
    *
    * @since 2.5
    */

    protected function getListQuery($query = null): DatabaseQuery
    {

    $db = $this->db;
    $tag = $this->getLangTag();
    // Check if we can use the supplied SQL query.
    $query = ($query instanceof DatabaseQuery) ? $query : $db->getQuery(true);
    $query->select(
    [
    'prod.product_ean',
    'prod.manufacturer_code',
    'prod.product_old_price',
    'prod.product_price',
    'prod.product_buy_price',
    'prod.min_price',
    'prod.product_weight',
    ]);
    // Columns with ... AS
    $query->select(
    $db->quoteName(
    [
    'prod.product_id',
    'prod.name_' . $tag,
    'prod.alias_' . $tag,
    'prod.description_' . $tag,
    'prod.short_description_' . $tag,
    'prod.product_date_added',
    'prod.product_publish',
    'prod.image',
    'cat.name_' . $tag,
    ],
    [ // ... AS ...
    'slug',
    'title',
    'alias',
    'body',
    'summary',
    'created',
    'state',
    'image',
    'category',
    ]
    )
    );

    $query->from($db->quoteName('#__jshopping_products', 'prod'))
    ->where($db->quoteName('prod.product_publish') . ' = ' . $db->quote(1))
    ->where($db->quoteName('cat.category_publish') . ' = ' . $db->quote(1));


    /**
    * If the JoomShopping option "Use the main category for the product" is available and enabled,
    * then the product has the main_category_id field.
    * If not, use the old JoomShopping approach - take the 1st category id from the table #__jshopping_products_to_categories
    * to do this, we will make a subquery, since category_id should only be 1.
    */

    if (property_exists($this, 'jshopConfig')
    && !empty($this->jshopConfig)
    && $this->jshopConfig->product_use_main_category_id == 1)
    {

    $query->select($db->quoteName('prod.main_category_id', 'catslug'));
    $query->join('LEFT', $db->quoteName('#__jshopping_categories', 'cat') . ' ON ' . $db->quoteName('cat.category_id') . ' = ' . $db->quoteName('prod.main_category_id'));
    }
    else
    {
    $query->select($db->quoteName('cat.category_id', 'catslug'));

    // Create a subquery for the sub-items list
    $subQuery = $db->getQuery(true)
    ->select($db->quoteName('pr_cat.product_id'))
    ->select('MIN(' . $db->quoteName('pr_cat.category_id') . ') AS ' . $db->quoteName('catslug'))
    ->from($db->quoteName('#__jshopping_products_to_categories', 'pr_cat'))
    ->group($db->quoteName('product_id'));

    $query->join('LEFT', '(' . $subQuery . ') AS ' . $db->quoteName('subquery') . ' ON ' . $db->quoteName('subquery.product_id') . ' = ' . $db->quoteName('prod.product_id'));
    $query->join('LEFT', $db->quoteName('#__jshopping_categories', 'cat'), $db->quoteName('cat.category_id') . ' = ' . $db->quoteName('subquery.catslug'));
    }

    return $query;
    }









    Check point



    We created a method to query the database from Joomla and learned a lot about how the smart search plugin works.



    In the next article, we will create a method for indexing content and complete the creation of plugin. We will also get acquainted with how indexed items are stored in the database and understand why this is important and solve the problem of indexing content for multilingual components with a non-standard implementation of multilingualism.






    Joomla Community resources





    • Joomla Community chat in Mattermost (read more)

    Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
  • Wie bewertest du diesen Beitrag?
    1 Klick Feedback
    Teilen mit Netzwerk & Team:

    Community-Analysen & Experten-Meinungen 0

    Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
    Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
    Community Pulse: Relevanz-Einschätzung
    1 Klick Experten-Votum
    🔴 Akute Relevanz 0%
    🟡 In Evaluierung 0%
    🟢 Keine Auswirkung 0%
    Spannende Innovation 0%
    Verwandte Story-Cluster & Quellen (Vektor-KI)
    Port 8095 Engine
    1 Quelle
    Hackers Just Poisoned the Rust Supply Chain | Threat Wire
    1 Quelle
    Hackers Found a Way Into Humanoid Robots | Threat Wire
    1 Quelle
    Bits und so #1021 (Passwort für Laufwerk)
    Ähnliche Beiträge
    🔍 Verwandte News

    Auch interessante Nachrichten The anatomy of smart search in Joomla 5 Part 2: Creating a plugin I.

    Thematisch verwandte Begriffe: anatomy, smart, search, Joomla · 6 Treffer

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...