<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergey Tolkachyov</title>
    <description>The latest articles on DEV Community by Sergey Tolkachyov (@sergeytolkachyov).</description>
    <link>https://dev.to/sergeytolkachyov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1313911%2F1ea781c7-dc69-467c-988a-32aadc3cd2df.jpeg</url>
      <title>DEV Community: Sergey Tolkachyov</title>
      <link>https://dev.to/sergeytolkachyov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sergeytolkachyov"/>
    <language>en</language>
    <item>
      <title>ProcessForge 1.2.0 Released: Performance Optimizations and Improved Reliability</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:47:32 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/processforge-120-released-performance-optimizations-and-improved-reliability-4if6</link>
      <guid>https://dev.to/sergeytolkachyov/processforge-120-released-performance-optimizations-and-improved-reliability-4if6</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/WebTolk/process-forge" rel="noopener noreferrer"&gt;ProcessForge 1.2.0&lt;/a&gt;, a new version of the open-source framework for organizing and running processes involving AI agents, was released on 18 October 2026. This release focuses primarily on optimizing PF's internal mechanisms, improving the reliability of long-running processes, and resolving issues encountered in practical work with multiple AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing Work with Large Volumes of Data
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Forge / Factory&lt;/strong&gt; multi-agent mode, ProcessForge can launch separate, independent AI agent sessions (&lt;code&gt;workers&lt;/code&gt;). Each worker receives its own assignment (&lt;code&gt;Assignment&lt;/code&gt;), a context capsule (&lt;code&gt;Capsule&lt;/code&gt;) containing the necessary knowledge and instructions, and a limited area of responsibility (&lt;code&gt;Scope&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This allows several agents to work on different parts of a project in parallel without interfering with one another.&lt;/p&gt;

&lt;p&gt;During extended work with workers launched through the command shell (&lt;code&gt;shell workers&lt;/code&gt;), large volumes of logs, events, and task execution history began to accumulate. A problem emerged: some PF operations repeatedly read and parsed data that was not directly relevant to the current work. As the history grew, this created unnecessary overhead.&lt;/p&gt;

&lt;p&gt;Version 1.2.0 reworks several mechanisms.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Moving Event Journals to SQLite
&lt;/h3&gt;

&lt;p&gt;Previously, NDJSON journals were used to store project and hook events. When the history grew large, some operations required records to be read repeatedly.&lt;/p&gt;

&lt;p&gt;Project events and raw hook events (&lt;code&gt;raw hooks&lt;/code&gt;) are now stored in two local SQLite databases with indexes.&lt;/p&gt;

&lt;p&gt;This makes it possible to find the required records quickly, check that events are unique, and process new data without repeatedly scanning the entire accumulated history.&lt;/p&gt;

&lt;p&gt;PF does not require a separate database server to be installed. Shell worker output logs, telemetry, and session transcripts remain separate files.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Faster Selection of the Current Work
&lt;/h3&gt;

&lt;p&gt;In PF, work is defined by a specific Run (a process run), Assignment, and associated context. Previously, even when the required assignment was specified explicitly, the system would sometimes scan the project's entire Run and Assignment history. Now, when the identifiers have been unambiguously verified, PF goes directly to the selected work without iterating over the other assignments.&lt;/p&gt;

&lt;p&gt;On a synthetic project with 400 Runs and 800 Assignments, the number of reads required for this selection dropped from 1,202 to 4, while the operation time fell from approximately 3.2 seconds to 5–6 milliseconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Reusing Parsed YAML
&lt;/h3&gt;

&lt;p&gt;ProcessForge uses YAML to describe processes, assignments, settings, and other structured data. Repeated reads of unchanged documents can now reuse an earlier parse stored in process memory. PF still checks the current file contents. If a document has changed, the system parses it again. Access control and context integrity checks remain enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Recovery After Worker Failures
&lt;/h2&gt;

&lt;p&gt;In multi-agent mode, the Process Execution Inspector monitors tasks carried out by external workers. It tracks the state of launched processes, their activity signals (&lt;code&gt;heartbeat&lt;/code&gt;), completion, and results. Once an assignment has been carried out, the Inspector gathers the required artifacts through the &lt;code&gt;collect&lt;/code&gt; mechanism. During extended work, a worker may finish while result collection fails.&lt;/p&gt;

&lt;p&gt;Previously, persistent automatic collection errors could cause an operation known to be failing to be repeated. PF now distinguishes between temporary errors, where a retry makes sense, and persistent failures that require a separate recovery step. Results that have already been collected remain in the history. Assignment execution state and Runtime health are displayed separately. This helps prevent the results of an agent's lengthy work from being lost because of a problem during result processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safely Continuing Work in a New Session
&lt;/h2&gt;

&lt;p&gt;Another feature of long-running AI processes is the need to continue work after an agent's session ends. ProcessForge provides a &lt;code&gt;Continuation&lt;/code&gt; mechanism. It allows a specific &lt;code&gt;Work&lt;/code&gt; to be resumed while retaining its association with the &lt;code&gt;Run&lt;/code&gt;, &lt;code&gt;Assignment&lt;/code&gt;, context capsule, and selected process. When work resumes, the system checks its state, permissions, and context freshness again. The new session does not gain the right to replace the assignment, expand the Scope, or choose another task on its own simply because the earlier conversation context is unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expanded Capabilities of the ProcessForge MCP Server
&lt;/h2&gt;

&lt;p&gt;Version 1.2.0 significantly expands the set of tools available to AI agents through MCP. Previously, the PF MCP server mainly provided access to project context, knowledge search, Work creation, and process stage management. Many operations, especially those involving the launch of other AI agents, required PF command-line tools. Additional Core capabilities are now available directly through MCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Workers
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;Forge / Factory&lt;/strong&gt; multi-agent mode, the orchestrator distributes work among separate, independent AI agent sessions. Each worker receives a limited assignment (&lt;code&gt;Assignment&lt;/code&gt;), a prepared input context, and its own area of responsibility (&lt;code&gt;Scope&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Four tools have been added for managing workers through MCP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pf.worker.prepare&lt;/code&gt; — prepare the worker's input data, context, and constraints.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pf.worker.start&lt;/code&gt; — launch the worker through the selected registered Runtime Driver. The launch is asynchronous: control returns to the orchestrator immediately.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pf.worker.status&lt;/code&gt; — check execution status, with support for state reconciliation and collection of completed results.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pf.worker.collect&lt;/code&gt; — collect the worker's results and verify the report, checksums, and execution attempt details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During preparation and launch, you can also specify the model (&lt;code&gt;model&lt;/code&gt;) and reasoning effort (&lt;code&gt;reasoning_effort&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The orchestrator can therefore manage the worker lifecycle through MCP without manually invoking CLI commands.&lt;/p&gt;

&lt;p&gt;PF's constraints still apply: a worker can only be launched through a registered agent driver, with a specific &lt;code&gt;Assignment&lt;/code&gt; and a pinned context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuing Work Across Sessions
&lt;/h3&gt;

&lt;p&gt;The tools &lt;code&gt;pf.continuation.status&lt;/code&gt;, &lt;code&gt;pf.continuation.create&lt;/code&gt;, and &lt;code&gt;pf.continuation.resume&lt;/code&gt; have been added. They allow previously started work to be found, a continuation point to be created, and work to be resumed in another AI agent session. The specific &lt;code&gt;Run&lt;/code&gt;, &lt;code&gt;Assignment&lt;/code&gt;, pinned process, and immutable context are preserved. The new session does not gain the right to choose a different assignment arbitrarily or expand its area of responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Searching for Knowledge Within a Work
&lt;/h3&gt;

&lt;p&gt;The new &lt;code&gt;pf.work.search&lt;/code&gt; and &lt;code&gt;pf.work.resolve&lt;/code&gt; tools allow agents to search for and retrieve resources permitted for a specific assignment. Unlike the general &lt;code&gt;pf.search&lt;/code&gt;, which works with project context resources, these tools also take into account the permissions pinned to the Work and the constraints of the current process stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovering Available Commands
&lt;/h3&gt;

&lt;p&gt;The tools &lt;code&gt;pf.actions.list&lt;/code&gt; and &lt;code&gt;pf.actions.describe&lt;/code&gt; have been added. They allow an agent to find out which CLI commands and MCP tools are supported by the installed version of ProcessForge, which parameters they accept, and which parameters are required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pf.actions.list&lt;/code&gt; describes the actions available in the installed PF version, their names, and their purpose. The catalog can be filtered.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pf.actions.describe&lt;/code&gt; describes the exact parameters of the selected action: required fields, types, permitted values, constraints, and declared side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tools &lt;code&gt;pf.runtime_driver.list&lt;/code&gt; and &lt;code&gt;pf.runtime_driver.describe&lt;/code&gt; have been added for working with drivers. They show which drivers are registered and available in PF without exposing their internal launch commands or environment variables. These tools help the orchestrating AI agent choose how to execute a &lt;code&gt;Work&lt;/code&gt;. A driver defines how PF passes prepared work to a worker, removing the need to guess launch methods, paths, commands, or arguments. When this information was unavailable, an agent would often inspect the executable's source code to find those details. The workflow is now more convenient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session Management and Message Recording
&lt;/h3&gt;

&lt;p&gt;The tool &lt;code&gt;pf.session.heartbeat&lt;/code&gt; has been added to renew an existing session's registration in the Agent Ledger (the PF workplace's "gatekeeper's log"), along with &lt;code&gt;pf.chat.record&lt;/code&gt; for recording an agent's message in its own session log. The new &lt;code&gt;pf.work.cancel&lt;/code&gt; tool allows an agent's assignment to be cancelled. All tools that change state retain authorization checks. The relevant operations require an explicit &lt;code&gt;apply: true&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Were These Changes Made?
&lt;/h3&gt;

&lt;p&gt;The goal is to give AI agents a unified programmatic interface for working with ProcessForge. The orchestrator can now use MCP to obtain context, manage &lt;code&gt;Work&lt;/code&gt;, launch workers, monitor their state, collect results, and continue work across sessions.&lt;/p&gt;

&lt;p&gt;The workers themselves do not have to connect to the PF MCP server: they can work independently with a context that has already been computed and prepared, and return results through the defined contract. This reduces dependence on command-line tools and a particular runtime environment while preserving PF's control over processes, permissions, and results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clearer Instructions for Agents Entering a Project
&lt;/h2&gt;

&lt;p&gt;The root &lt;code&gt;AGENTS.md&lt;/code&gt; is now the main entry point into a project. It contains the mandatory contract for an agent's work with ProcessForge: how to obtain context, select or continue a &lt;code&gt;Work&lt;/code&gt;, stay within its area of responsibility (&lt;code&gt;Scope&lt;/code&gt;), and move through process stages. A problem arises during extended work: AI agents tend to append new rules, conclusions, and instructions to &lt;code&gt;AGENTS.md&lt;/code&gt;. The file gradually grows, while different AI clients have their own limits on the amount of instructions they load. As a result, some rules may fall beyond the available limit and never reach the model's context.&lt;/p&gt;

&lt;p&gt;PF 1.2.0 adds &lt;strong&gt;a mechanism for checking the size of startup instructions&lt;/strong&gt; (Instruction Accounting).&lt;/p&gt;

&lt;p&gt;The mandatory PF contract is limited to 4,096 bytes. The system can also check the size of the entire file and the chain of included instructions against explicitly configured limits for a particular AI client. If it detects a risk that mandatory instructions will be truncated, PF blocks the relevant change. If warning mode is configured, it reports that the limit has been exceeded.&lt;/p&gt;

&lt;p&gt;PF does not automatically shorten or rewrite the user's instructions.&lt;/p&gt;

&lt;p&gt;Separate diagnostic profiles are available for different AI clients. They help assess which files should be loaded and whether they fit within the known limits. Whether the instructions actually reach the model requires a separate check. The mechanism is intended to prevent accumulated instructions from causing an agent to miss the mandatory rules for working with the project. These checks run when the entry point is prepared, changed, or diagnosed; they do not continuously monitor the file in the background. The 4,096-byte limit applies to the PF-managed contract, rather than the whole of &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Prepared workers can also begin work with instructions and a capsule that have already been supplied, without connecting to their own PF MCP server or creating the Work again. This reduces unnecessary context and preserves the division of responsibility between the orchestrator and workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Temporary Files
&lt;/h2&gt;

&lt;p&gt;Project temporary files should now be placed in .pf/tmp/, with separate directories allocated to workers. The optional PF &lt;code&gt;app-server&lt;/code&gt; now supports configurable notifications about temporary files that have not been modified for a long time. PF does not delete them automatically: workers may still be using them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does This Mean in Practice?
&lt;/h2&gt;

&lt;p&gt;ProcessForge continues to evolve as a framework independent of any particular AI model or runtime environment. In the simple &lt;code&gt;Garage&lt;/code&gt; mode (the 1-1-1-1 mode: one person, one project, one agent, one run), a single agent can work with a project without starting the PF app-server. The entire project history, including the history of decisions, is stored in the project's &lt;code&gt;.pf&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Forge / Factory&lt;/strong&gt; mode, multiple workers can operate in parallel with separate contexts and assignments, controlled access to resources, result verification, and work handoffs between sessions. All of the "project bookkeeping" remains in the project's &lt;code&gt;.pf&lt;/code&gt; directory, but a shared process, &lt;code&gt;pf-app-server&lt;/code&gt;, is required to organize the workflow correctly. It is optional in Garage mode.&lt;/p&gt;

&lt;p&gt;The changes in version 1.2.0 are primarily intended to keep this system responsive and manageable as the project, task count, and accumulated history grow.&lt;/p&gt;

&lt;p&gt;Upgrade process: when upgrading from an earlier version, preserve the state of your projects and perform a separate migration of the old event journals (tell the agent to use the suggested migration tool).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ProcessForge&lt;/strong&gt; — processes, knowledge, tools, and execution control for AI agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>development</category>
    </item>
    <item>
      <title>Process Forge: a framework for creating AI processes</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Fri, 07 Aug 2026 05:55:40 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/process-forge-a-framework-for-creating-ai-processes-1gmj</link>
      <guid>https://dev.to/sergeytolkachyov/process-forge-a-framework-for-creating-ai-processes-1gmj</guid>
      <description>&lt;p&gt;During the 2026 New Year holidays, in one of the Joomla community chats, a fellow colleague shared a prompt for an AI agent and the result of that prompt. A week before that, he had tried Gemini CLI for the first time. Until then, I had only used AI in web versions, and overall that suited me fine. When I saw that wall of text, the CLI request, and the wall of response, the first thing that hit me was: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I DO NOT WANT to learn all this stuff! Something new again, I'm tired of it...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then came the stages of trial and error, attempts to use ready-made community skills packages from GitHub with role-based suggestion like&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"you are a super-duper mega-backend Joomla developer with 40 years of experience, make it beautiful for me"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Somewhere in the middle of this half-year I started formalising my processes. And by processes I do not mean only development, but also SEO, analytics, business analysis, process study and process design, and so on. In the end, as a rule, all of that is either connected with or leads to a Joomla site. Formalisation brought me to an understanding of several basics that are common to all processes. More on that below.&lt;/p&gt;

&lt;p&gt;Now I want to present to the honourable public my work on a framework for creating formalised work processes for AI agents: &lt;a href="https://github.com/WebTolk/process-forge" rel="noopener noreferrer"&gt;Process Forge (GitHub)&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I ask experienced vibe coders to forgive me in advance for any obvious points or long-known theses if they appear here. Workload did not allow me to spend too much time searching and watching what others were doing. I had to switch myself onto new tracks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Starting Points
&lt;/h2&gt;

&lt;p&gt;To answer the questions "what is all this for?" and "why this way?", I will give several starting points that I mostly felt out through practice over these six months and from which I proceeded when creating Process Forge. Some of these conclusions later found confirmation in other people's findings that I noticed in articles or social media posts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An AI model's internal knowledge about a technology stack is most often seriously behind real modern practice. This varies a lot from stack to stack. You can add "for now", but in general this is still a relevant premise.&lt;/li&gt;
&lt;li&gt;For an AI agent to work properly, it needs to be given everything a human is given for work: knowledge and tools, ready-made templates for reuse.&lt;/li&gt;
&lt;li&gt;An AI agent needs a certain amount of freedom, but within a defined corridor, along a specified vector.&lt;/li&gt;
&lt;li&gt;You must not be tied to any one AI provider ecosystem. There must be a way to "switch the brains" (there should be no &lt;code&gt;.codex&lt;/code&gt;, &lt;code&gt;.claude&lt;/code&gt;, and so on as the agent's home base; there should be something neutral, even though the agents themselves try to bind you to their ecosystem).&lt;/li&gt;
&lt;li&gt;All project memory and processes must be structured in files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You yourself must first understand WHAT is happening, HOW, and WHY.&lt;/strong&gt; Without that, AI will only bring more chaos into your life.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When we work with AI agents, and with AI in general, we expect the work to be done quickly and well, because this is Artificial, damn it, Intelligence. But it turned out not to be so. Instead of a shovel, you were given a turbo-excavator that says to you: "I am ploughing the field, my wheel fell off, I will quickly fix it with a temporary fallback and put a track on one side." You still have to learn how to work with this thing, and know where and how to apply it properly. I should say right away that almost everything written here is the result of my own heuristics and is therefore purely subjective. Only several months later did I get acquainted with AI Factory (&lt;a href="https://github.com/lee-to/ai-factory" rel="noopener noreferrer"&gt;link one&lt;/a&gt;, &lt;a href="https://habr.com/ru/articles/1001142/" rel="noopener noreferrer"&gt;link two (Habr)&lt;/a&gt;) and &lt;a href="https://github.com/open-gsd/gsd-core" rel="noopener noreferrer"&gt;GSD Core&lt;/a&gt;, but it was already too late, because I was already working full steam with my own tool. There may well be other similar tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Process Forge Concepts
&lt;/h2&gt;

&lt;p&gt;I will lay out the concepts in the order in which they appeared and were tested in "combat" conditions as they evolved. This way the history and logic of the decisions will be clearer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Process Instead of Chat
&lt;/h3&gt;

&lt;p&gt;After two months of working in chat mode and with ready-made community skills packages from GitHub, with role-based suggestion like "you are a super-duper mega-backend Joomla developer with 40 years of experience, make it beautiful for me", a quick search and a discussion with AI about my own doubts around all of this led me to the conclusion that I needed to build a development process. At first, only for development.&lt;/p&gt;

&lt;p&gt;Chat history cannot be a reliable source of project data or project memory. Every new session starts from zero for the agent: tabula rasa. The agent has to be loaded with project context: what this thing is and how to deal with it. That is why all project memory and processes must be structured and stored in files. In spring I came across the word "artifact" in the sense of "a file produced as the result of an autonomous process". As far as I can judge by what is happening in the industry, everyone is accumulating their own Markdown files: in folders, in Obsidian, or somewhere else, with different instructions, skills, and so on inside.&lt;/p&gt;

&lt;p&gt;My request was immediately for a process, so after several days of discussion I arrived at an &lt;strong&gt;artifact-based development flow&lt;/strong&gt;. It was a set of folders and files describing the rules for working with projects. They described the workflow stages that I used and improved over three months while creating Joomla libraries, plugins, and components. You can get a general sense of the stages under the cut. In a modified form, this entered Process Forge as one of the "out-of-the-box" processes.&lt;/p&gt;

&lt;p&gt;Example process stages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Requires&lt;/th&gt;
&lt;th&gt;Fills&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;artifact-policy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Normalises artifacts before the stages&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;task-record&lt;/code&gt;, &lt;code&gt;project-context&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;artifact-index&lt;/code&gt;, &lt;code&gt;missing-artifacts-created&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;flow-orchestrator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chooses the current/next stage&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;task-record&lt;/code&gt;, &lt;code&gt;artifact-index&lt;/code&gt;, &lt;code&gt;project-context&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;stage-decision&lt;/code&gt;, &lt;code&gt;next-skill-handoff&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intake-scope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Records the task, boundaries, and success criteria&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;task-request&lt;/code&gt;, &lt;code&gt;project-context&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;brief&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;task-record&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;investigation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gathers facts, causes, and the impact surface&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;brief&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;project-context&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;investigation-report&lt;/code&gt;, &lt;code&gt;impact-analysis&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;domain-surface&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Describes domains, ownership, and risks&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;investigation-report&lt;/code&gt;, &lt;code&gt;impact-analysis&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;decision-log&lt;/code&gt;, updated &lt;code&gt;impact-analysis&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;architecture-plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Designs the solution and the order of changes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;brief&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;decision-log&lt;/code&gt;, &lt;code&gt;impact-analysis&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;architecture&lt;/code&gt;, &lt;code&gt;implementation-plan&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;implementation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Makes changes according to the plan&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;architecture&lt;/code&gt;, &lt;code&gt;implementation-plan&lt;/code&gt;, &lt;code&gt;decision-log&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;changed-files&lt;/code&gt;, &lt;code&gt;change-summary&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code-assurance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks, review, test cases, browser proof&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;changed-files&lt;/code&gt;, &lt;code&gt;change-summary&lt;/code&gt;, &lt;code&gt;implementation-plan&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;review-findings&lt;/code&gt;, &lt;code&gt;test-plan&lt;/code&gt;, &lt;code&gt;test-cases&lt;/code&gt;, &lt;code&gt;browser-verification-report&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;release-delivery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Release packaging/delivery&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;change-summary&lt;/code&gt;, &lt;code&gt;review-findings&lt;/code&gt;, &lt;code&gt;test-cases&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;release-notes&lt;/code&gt;, &lt;code&gt;migration-notes&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;evolve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extracts reusable lessons into rules/extensions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;evolution-cursor&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;evolution-report&lt;/code&gt;, &lt;code&gt;updated-rules-or-extensions&lt;/code&gt;, &lt;code&gt;updated-cursor&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is one of the earlier versions of the artifact-based development flow.&lt;/p&gt;

&lt;p&gt;In addition, the whole flow maintained &lt;code&gt;task-log&lt;/code&gt;, &lt;code&gt;agent-log&lt;/code&gt;, &lt;code&gt;verification-log&lt;/code&gt;, and &lt;code&gt;tool-telemetry.ndjson&lt;/code&gt;; the specific log depended on the stage. Telemetry records tool usage and the reasons for using those tools. For example, if MCP PhpStorm could not perform the required action or got stuck for some reason, plain shell was used as a fallback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting Knowledge to an AI Process
&lt;/h3&gt;

&lt;p&gt;The knowledge built into AI models themselves usually lags behind current reality. Top paid models have some idea of recent events, but even then not always. In my subjective assessment, web search also costs AI agents more time and tokens, so all current knowledge should be close at hand, and the agent should build its context from it. Almost anything can be used as reference material:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source code of current versions,&lt;/li&gt;
&lt;li&gt;snapshots (local copies) of official documentation,&lt;/li&gt;
&lt;li&gt;your own or other people's development articles,&lt;/li&gt;
&lt;li&gt;your notes with recipes for solving a particular task,&lt;/li&gt;
&lt;li&gt;links to sites.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I work with Joomla, so in my stash I have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source code of several Joomla core branches,&lt;/li&gt;
&lt;li&gt;source code of the main Joomla extensions I work with regularly (JoomShopping, RadicalMart, and so on),&lt;/li&gt;
&lt;li&gt;copies of official documentation for web full stack: &lt;a href="http://manual.joomla.org" rel="noopener noreferrer"&gt;manual.joomla.org&lt;/a&gt;, HTML / CSS / JS / PHP, and documentation for several frameworks,&lt;/li&gt;
&lt;li&gt;my own articles and some articles by fellow colleagues,&lt;/li&gt;
&lt;li&gt;documentation for various APIs, which I supplement with dumps of real responses if it is REST,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so on. Yes, I know that &lt;code&gt;Context7&lt;/code&gt; exists (the service and MCP for machine documentation), but as far as I remember it started limiting requests at some point and, predictably, &lt;a href="https://context7.com/plans" rel="noopener noreferrer"&gt;introduced pricing plans with request packages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Overall, this section of the article lives in the plane of &lt;strong&gt;on-premise vs SaaS&lt;/strong&gt;: either we do it ourselves for ourselves, by hand; or someone else does it for us, but for money. For now I am, as you have probably already guessed, a supporter of the former )) Naturally, with this approach you, or your AI agent, take on the obligation to keep knowledge up to date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting Tools, MCP, and Templates
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Templates
&lt;/h4&gt;

&lt;p&gt;I will start with templates because they are simple: in my case these are separate ready-made pieces of code that can be copied, slightly adapted, and they are ready. For an AI agent, it is better to put them into folders and give each folder a &lt;code&gt;README.md&lt;/code&gt; describing &lt;em&gt;what&lt;/em&gt; this template is and &lt;em&gt;how&lt;/em&gt; to work with it.&lt;/p&gt;

&lt;p&gt;But it does not have to be only code: design fragments, video fragments (footage), and so on. Whatever your agent works with.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tools and MCP
&lt;/h4&gt;

&lt;p&gt;By tools I mean what the AI agent can use on your device. For a developer this may include &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;GitHub CLI&lt;/code&gt;, pipeline runs, linters, fixers, packagers/builders, local and remote test stands. For an SEO \ GEO specialist these may be small utilities for automated access to various service APIs (Yandex APIs for Wordstat and SERP data collection), Screaming Frog CLI, and so on. I suppose that a conventional video-production setup also has similar tools of its own.&lt;/p&gt;

&lt;p&gt;The most important point is that &lt;strong&gt;the tool must be configured globally on the workstation&lt;/strong&gt; so that different projects can use it in the same way: one builder instance is used across all 80 plugins you write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; in this case appears as the same kind of tool, just available through Model Context Protocol. It may be &lt;code&gt;Playwright&lt;/code&gt; / &lt;code&gt;Chrome DevTools&lt;/code&gt; / &lt;code&gt;Firefox DevTools&lt;/code&gt; for tasks that require a browser. It may be &lt;code&gt;MCP IDE PhpStorm&lt;/code&gt; in my case, &lt;a href="https://github.com/oraios/serena" rel="noopener noreferrer"&gt;Serena&lt;/a&gt; for symbolic code analysis, and so on.&lt;/p&gt;

&lt;p&gt;I noticed that when working with code through MCP IDE, there are fewer problems with file encodings and file-system permissions. Also, through MCP the agent can receive more information from inspections and run tests. But it can also run shell commands through the PhpStorm console, even though it already has direct access to the console, so catch it in time before it starts doing nonsense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processes Again
&lt;/h3&gt;

&lt;p&gt;In the context of working with Joomla and its extensions, I try to work through a full engineering process: the required functionality is discussed, we plan the implementation, then write the code, run end-to-end tests, finalise and build the package, and publish a release according to semver. After that, post-release tasks roll in: update or write documentation from scratch in two languages, prepare screenshots in two languages, video in one language, publish announcements about new versions with descriptions on 15+ platforms...&lt;/p&gt;

&lt;p&gt;As I came to understand how to work with AI agents, the results of our joint work started accumulating. A new problem appeared: when should all this be checked and published? With agents, less time was spent on code, but more time on planning and testing. And post-release tasks did not disappear...&lt;/p&gt;

&lt;p&gt;So the next experiment was to try adapting the agent for writing documentation, preparing screenshots, and publishing all of this to the site. The file-based process that had already been worked out handled this too. With great distrust, over several dozen iterations, I began expanding the agent's area of responsibility and eventually reached full release publication. In general it works, but there is of course less human spirit in it than one would like.&lt;/p&gt;

&lt;p&gt;Often, certain thoughts or pieces of information circle around us for a long time, appearing before our eyes several times before we notice and understand them. In this case, an understanding began to form that I was surrounded by processes of different complexity in my work. It became logical to go one level above coding and identify a universal process entity, where there may be different numbers of stages, artifacts between them, relationships, and so on. That is how Process Forge began to appear.&lt;/p&gt;

&lt;p&gt;It now has internal processes, out-of-the-box processes, and the ability to create your own processes. Processes can branch, and a parent process can wait for the results of a child process and then continue working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Platforms and Specialisations
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Platforms
&lt;/h4&gt;

&lt;p&gt;The platform entity allows Process Forge knowledge packages to be combined into pluggable packages, and lets you describe the execution environment, templates, and required tools inside them. When you start making "a new plugin for Joomla 6+ that...", the agent understands which platform is meant and adds not only Joomla knowledge to the project context, but also PHP, HTML, CSS, JS knowledge, the required checks package, code style rules, and the Phing extension packager.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platforms can inherit from one another.&lt;/strong&gt; For example, you are writing a plugin for the JoomShopping online store, which does not exist separately from Joomla. But JoomShopping has its own separate knowledge sets, which are not needed when working on the clean engine core or another store. And, predictably, when working with JoomShopping we also need knowledge about Joomla.&lt;/p&gt;

&lt;h4&gt;
  
  
  Specialisations
&lt;/h4&gt;

&lt;p&gt;If our agent writes Joomla code, it needs knowledge for writing code. But if it publishes an article or works with modules on the site in the admin panel, it does not need PHP, JS, or Joomla extension architecture knowledge. It needs knowledge of the admin panel, of the REST API for faster work, maybe basic HTML and the CSS framework of your project's site, image-processing rules, media-content placement rules (what to put in which folder), and so on. That is why specialisations appeared: they combine the knowledge needed for a particular specialisation.&lt;/p&gt;

&lt;p&gt;The point of this entity is that the backend may work with Joomla, or with Laravel, or with Django and Python. Platforms provide applied knowledge about the language, architecture, and tools. Specialisations provide general abstract context: patterns, approaches, principles. Specialisations may also connect their own knowledge, tools, templates, MCP, and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cascading Project Context Computation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In Process Forge, context is not stored in one large prompt or skill. It is computed as a cascade from several levels, by analogy with merging arrays/objects: each next level inherits the data from the previous ones and refines it for a specific situation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;General workspace rules are supplemented by company, area, platform, and project knowledge, then by process, task, and concrete agent settings. More local levels can extend or override general parameters. As a result, the agent receives not the entire accumulated mass of information, but the relevant context assembled immediately before the task is performed.&lt;/p&gt;

&lt;p&gt;In general terms, the context-computation logic is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workspace&lt;/li&gt;
&lt;li&gt;Company / organisation&lt;/li&gt;
&lt;li&gt;Area (SEO, development, content...)&lt;/li&gt;
&lt;li&gt;Platform (Joomla, WordPress, Symfony...)&lt;/li&gt;
&lt;li&gt;Specialisation (backend, frontend, full stack, content, SEO...)&lt;/li&gt;
&lt;li&gt;Project&lt;/li&gt;
&lt;li&gt;Process (software development / SEO audit / ...)&lt;/li&gt;
&lt;li&gt;Task and current stage&lt;/li&gt;
&lt;li&gt;Agent role and profile (in multi-agent mode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each next context source is layered over the already assembled one. The result of this work is a project context snapshot (YAML for machines, Markdown for people), which states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;platform_contracts&lt;/code&gt; / &lt;code&gt;platform_stack&lt;/code&gt;: selected and available platform contracts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resolved_parameters&lt;/code&gt;: final parameters after inheritance and overrides&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parameter_resolution&lt;/code&gt;: parameter sources, provenance, and conflicts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resolved_context&lt;/code&gt;: the result of specialisations, resources, tools, MCP, templates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resolved&lt;/code&gt;: final knowledge packages / resources / templates / tools / processes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;capabilities&lt;/code&gt;: required/optional capabilities and their status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;conflicts&lt;/code&gt;: specialisation and parameter conflicts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;snapshot.health.status&lt;/code&gt;: final state: &lt;code&gt;pass&lt;/code&gt;, &lt;code&gt;warn&lt;/code&gt;, or &lt;code&gt;blocked&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;freshness&lt;/code&gt; / &lt;code&gt;context_policy&lt;/code&gt;: freshness policy and state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and some other information.&lt;/p&gt;

&lt;p&gt;For a specific task, and in multi-agent mode, even more specific snapshot-capsules are assembled and passed to the agent together with its &lt;code&gt;assignment&lt;/code&gt; / &lt;code&gt;process&lt;/code&gt; / &lt;code&gt;stage&lt;/code&gt; / &lt;code&gt;task&lt;/code&gt; layers as machine-readable files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The General Feedback Mechanism: Evolve
&lt;/h3&gt;

&lt;p&gt;Evolve in Process Forge collects useful conclusions that appeared during work on a task: what should be remembered, improved, checked, or moved into shared experience. It does not change rules and knowledge automatically. First, it formalises such findings as proposals with a clear scope: only for this project, for the workspace, for a package, platform, or the core. Then those proposals can be checked, selected, and only then consciously included in shared materials or updates.&lt;/p&gt;

&lt;p&gt;When a proposal turns into an updated knowledge package, template, rule, or other shared material, it is distributed through the normal Process Forge update mechanism. The project sees that an update is available, can prepare it, check it, apply it, and after that rebuild its context with the new version of shared knowledge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Versioning
&lt;/h3&gt;

&lt;p&gt;Both Process Forge itself and its entities are designed with versioning in mind. Since everything lives as files, as separate packages, you can gradually improve and expand them. Accordingly, &lt;strong&gt;you can have your own update server&lt;/strong&gt; for all parts of your workplace: the Process Forge core, separately processes and platforms, separately knowledge packages and templates. For example, you made process changes after evolve from several machines: you publish a new process version. You created new instructions for working with code: you publish &lt;code&gt;code_instructions_v.1.1.0&lt;/code&gt;, and all your workstations receive a new knowledge package. After that, local workers update project contexts and can work with the new knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organising Work in Process Forge
&lt;/h2&gt;

&lt;p&gt;In a surprising way, all of this starts to resemble structures built in human companies and organisations. I assume that the human operator should not have to know slash commands, but should simply direct agents in natural language. That is why Process Forge 2 has documentation both for people and for agents.&lt;/p&gt;

&lt;p&gt;To get started, you need to set up your machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the ProcessForge distribution (unpack the archive into your agent area).&lt;/li&gt;
&lt;li&gt;Run step-by-step workplace setup with a human involved (there is an auto mode, but at your own risk).&lt;/li&gt;
&lt;li&gt;Configure path constants and root directories.&lt;/li&gt;
&lt;li&gt;Specify roots for knowledge and local documentation.&lt;/li&gt;
&lt;li&gt;Register tools and MCP servers.&lt;/li&gt;
&lt;li&gt;Create or import knowledge packages.&lt;/li&gt;
&lt;li&gt;Create a registry of reusable templates.&lt;/li&gt;
&lt;li&gt;Create platforms from existing resources.&lt;/li&gt;
&lt;li&gt;Create specialisations.&lt;/li&gt;
&lt;li&gt;Connect project(s) to the workplace.&lt;/li&gt;
&lt;li&gt;Create your own processes if the out-of-the-box ones are not enough.&lt;/li&gt;
&lt;li&gt;Start working.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes, this takes time to set up the machine. But it scales, and in general it has shown itself fairly well.&lt;/p&gt;

&lt;p&gt;Process Forge assumes two main operating modes: garage mode and forge mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Garage Mode (1-1-1-1)
&lt;/h3&gt;

&lt;p&gt;This is the mode where there is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one human operator,&lt;/li&gt;
&lt;li&gt;one main agent session (console window),&lt;/li&gt;
&lt;li&gt;one project,&lt;/li&gt;
&lt;li&gt;one active process/run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main agent performs the work, runs checks, maintains artifacts, and either finishes the work or hands it off to the next stage or process.&lt;/p&gt;

&lt;p&gt;We open a console / chat with the agent, say &lt;strong&gt;"immerse yourself in the project through .pf. Tell me what has been done and where we stopped?"&lt;/strong&gt;, and continue working within one project. Physically, you may have two or three independent projects open at the same time, each working in garage mode. And in this garage you have an assistant that uses your tools, uses your knowledge, uses your platforms, and works according to your templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forge / Factory Mode
&lt;/h3&gt;

&lt;p&gt;This is a multi-agent mode where work is divided into independent streams.&lt;/p&gt;

&lt;p&gt;Your main agent becomes an orchestrator that receives input data, chooses the process, creates assignments with context capsules, then starts agent sessions (full-fledged agents or subagents), coordinates them, and makes sure agents do not interfere with one another. Then it collects their results and integrates the outcome through result handoffs and reviews.&lt;/p&gt;

&lt;p&gt;In this way, work on a project becomes similar to the work of a team or department on a project. They need to be coordinated, and therefore there appears...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Director&lt;/strong&gt; is a workplace-level role for organised coordination of several agent sessions, processes, or projects. It is not needed in garage mode, but it is needed where there are handoffs, waiting for a suitable executor, errors, parallel work by several orchestrators, or the need to explicitly transfer responsibility. Project agents and orchestrators can send signals to the Director's inbox: for example, that a task is blocked, another executor is needed, an error occurred, or an operator decision is required. Such messages form &lt;code&gt;cases&lt;/code&gt;, which the Director takes into account during the next coordination pass.&lt;/p&gt;

&lt;p&gt;At the same time, in the current ProcessForge implementation the Director is not a constantly running independent LLM agent. In the PF model it is described as an agent role, but it is executed as a CLI coordination layer: it checks the handoff queue, agent availability, leases, and the state of open cases, but it does not perform tasks itself and does not launch agents. In the future, this role can be developed into a full AI agent that reasons about priorities and complex routes, but for now it is a deterministic mechanism for managing workplace coordination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The watchman's log&lt;/strong&gt;. To make it comfortable for agents to work in a Process Forge workplace, there is a "watchman's log" where agents who have "come to work" are recorded. It shows who entered the process, what they saw, what they did, where they stopped, and what risks they left for the next participant.&lt;/p&gt;

&lt;p&gt;The log helps quickly restore the course of work without searching through all files and messages. This is important because work can move between agents, tasks, and runs, while the log preserves context continuity. And it is not a separate agent; it is a CLI tool, so we do not spend tokens here.&lt;/p&gt;

&lt;p&gt;Given that your computer is turning into a kind of company branch or small office, it may later have not only a Director and a watchman. Over time, it may get a "facilities manager" (some kind of background process) that checks whether anything needs to be done with the workplace: send candidates for improving knowledge packages to a central hub for release, or receive new knowledge packages and intelligently update the contexts of all projects. Or perhaps there may be a "cleaner" (a process that occasionally launches an agent for smart cleanup). But these are already fantasies, somewhat detached from reality ))&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In some articles about AI development I have encountered similar ideas: you need to build a pipeline that performs tasks of the same type. The times, apparently, are such in AI that it is easier to create your own custom tool than to take someone else's and adapt to it, or try to customise it. Nevertheless, I will risk sharing my work with the IT community in a field that is completely new to me. I hope it will be useful to someone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/WebTolk/process-forge" rel="noopener noreferrer"&gt;Process Forge on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://habr.com/ru/articles/1066916/" rel="noopener noreferrer"&gt;This article on Habr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web-tolk.ru/blog/process-forge-frejmvork-dlya-sozdaniya-ai-protsessov" rel="noopener noreferrer"&gt;This article on web-tolk.ru&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>programming</category>
      <category>agents</category>
    </item>
    <item>
      <title>How to work with the component menu in the Joomla admin panel using preset.xml and the plugin.</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Sat, 23 May 2026 16:23:17 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/how-to-work-with-the-component-menu-in-the-joomla-admin-panel-using-presetxml-and-the-plugin-37h</link>
      <guid>https://dev.to/sergeytolkachyov/how-to-work-with-the-component-menu-in-the-joomla-admin-panel-using-presetxml-and-the-plugin-37h</guid>
      <description>&lt;p&gt;The Joomla component has several levels of presence in the admin panel. The simplest level is created by the component installer: if the manifest contains &lt;code&gt;&amp;lt;administration&amp;gt;&amp;lt;menu&amp;gt;...&amp;lt;/menu&amp;gt;&amp;lt;/administration&amp;gt;&lt;/code&gt;, Joomla will add the item to the protected admin &lt;code&gt;menutype=main&lt;/code&gt;. But this is often not enough. A normal component needs groups of links, quick actions, links to categories, fields, workflow, dashboard, separate sets of items for different administrative modules, and the ability to add or hide items dynamically.&lt;br&gt;
To do this, Joomla 6.1 uses the menu preset mechanism.&lt;/p&gt;

&lt;p&gt;How to work with &lt;code&gt;preset.xml&lt;/code&gt; and modify the admin panel menu using the plugin in this article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web-tolk.ru/blog/kak-rabotat-s-menyu-komponenta-v-adminke-joomla-s-pomoshchyu-preset-xml-i-plagina?utm_source=dev.to"&gt;Read the article (in Russian)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>joomla</category>
      <category>php</category>
      <category>development</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CFI Plugin v.2.0.0 Released - Export/Import Joomla Articles to/from CSV</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Mon, 19 Jan 2026 09:44:59 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/cfi-plugin-v200-released-exportimport-joomla-articles-tofrom-csv-35d8</link>
      <guid>https://dev.to/sergeytolkachyov/cfi-plugin-v200-released-exportimport-joomla-articles-tofrom-csv-35d8</guid>
      <description>&lt;p&gt;This plugin allows you to export Joomla articles to a CSV file. You can then edit the data in a spreadsheet editor (e.g., Excel or Open Office Calc) and upload the file back. In some cases, this is more convenient and faster. The plugin allows exporting tag IDs, working with custom article fields in Joomla, as well as usually non-editable article properties like hits, modification date, etc.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Initial versions of the CFI plugin were created by developer AlekVolsk for Joomline. He created versions 1.0.0-1.0.16 for Joomla 3. Subsequently, in 2026, the plugin was transferred to WebTolk for further development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  v.2.0.0. What's New?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring.&lt;/strong&gt; The plugin has been completely rewritten from scratch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New Import/Export Interface.&lt;/strong&gt; Export and import now open in a modal window. Functionality to select article properties, tags, and custom fields for export right before the export itself has been added.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article Filtering During Export.&lt;/strong&gt; You can filter the articles you need using standard search parameters in the admin panel, and then export only those: only featured ones, only published ones, only articles from 2 categories, only articles with a specific tag, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import/Export All Article Data.&lt;/strong&gt; Previously, the plugin could only export some article data. Now, during export and import, you can change all article data found in the article editing form, even non-editable ones like hits or modification date. This way, you can quickly update all meta-descriptions or just the field data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import Custom Fields Only.&lt;/strong&gt; The &lt;code&gt;articleid&lt;/code&gt; field remains mandatory for import. Thus, you can import only custom field data without changing the article data itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joomla 6.&lt;/strong&gt; The plugin has been rewritten with the current structure for Joomla 4+ and successfully tested on Joomla 6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For Developers: Pre-processing by Plugins.&lt;/strong&gt; You can process data with your own plugins of the &lt;code&gt;cfi&lt;/code&gt; group on-the-fly, directly before importing or exporting data. Events: &lt;code&gt;onExportPrepareData($event)&lt;/code&gt; and &lt;code&gt;onImportPrepareArticleData($event)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;System Requirements:&lt;/strong&gt; Joomla 4.3+, PHP 8.1+.&lt;/p&gt;

&lt;p&gt;To update from older versions, release 1.0.16 is available, which adds a new update server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extension Page (&lt;a href="https://web-tolk.ru/dev/joomla-plugins/cfi-eksport-import-materialov-joomla?utm_source=dev.to"&gt;https://web-tolk.ru/dev/joomla-plugins/cfi-eksport-import-materialov-joomla?utm_source=dev.to&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Extension GitHub (&lt;a href="https://github.com/WebTolk/cfi" rel="noopener noreferrer"&gt;https://github.com/WebTolk/cfi&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Version for Joomla 3 and Joomla 4 (&lt;a href="https://joomline.ru/rasshirenija/plugin/cfi.html" rel="noopener noreferrer"&gt;https://joomline.ru/rasshirenija/plugin/cfi.html&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;GitHub Repository for Joomla 3+ versions (&lt;a href="https://github.com/AlekVolsk/cfi" rel="noopener noreferrer"&gt;https://github.com/AlekVolsk/cfi&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>joomla</category>
      <category>webdev</category>
      <category>seo</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Joomla View Logs component v.2.3.0 has been released!</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Sun, 11 Jan 2026 11:22:44 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/joomla-view-logs-component-v230-has-been-released-4a36</link>
      <guid>https://dev.to/sergeytolkachyov/joomla-view-logs-component-v230-has-been-released-4a36</guid>
      <description>&lt;p&gt;The component allows you to view error logs and notifications in the Joomla admin panel. Logs of both Joomla itself and extensions that use the standard kernel logger are displayed. It works with Joomla 4+, Joomla 5+.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujznvgh21tt2ameu39ha.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujznvgh21tt2ameu39ha.webp" alt="Joomla ViewLogs component admin panel" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  v.2.3.0 What's new?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The option to disable the display of the PHP log has been added in the component settings&lt;/li&gt;
&lt;li&gt;Added a message about the inability to delete a PHP log from the log list page&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added a link to download the zip archive in the message about archiving the log&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;disabled&lt;/code&gt; attribute has been added to the "archive" and "delete" buttons for the PHP log&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed the reading path for the PHP log&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed links for downloading logs in &lt;code&gt;csv&lt;/code&gt; format from the logs list page&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed downloading logs in &lt;code&gt;csv&lt;/code&gt; format from the logs page (previously logs were downloaded with html code the template)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed translation of column headers of the log table&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://web-tolk.ru/en/dev/components/view-logs?utm_source=dev.to"&gt;Extension page&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/WebTolk/View-logs" rel="noopener noreferrer"&gt;Extension GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>joomla</category>
      <category>coding</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Joomla-дайджест. 2-е полугодие 2025 года.</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Sat, 03 Jan 2026 17:17:53 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/joomla-daidzhiest-2-ie-polughodiie-2025-ghoda-35nh</link>
      <guid>https://dev.to/sergeytolkachyov/joomla-daidzhiest-2-ie-polughodiie-2025-ghoda-35nh</guid>
      <description>&lt;p&gt;Все главные новости из мира Joomla с июля по декабрь 2025 года в одной статье. Я честно пытался напрячь самые разные ИИ сортировкой постов по смыслу и, к сожалению, ни один из них не справился с этой задачей так, как нужно мне. Поэтому, напрягая собственный интеллект, представляю читателю наш дайджест, который обозревает новости, расширения, шаблоны и статьи из мира Joomla. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://habr.com/ru/articles/982520/" rel="noopener noreferrer"&gt;Читать на Хабре&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>joomla</category>
      <category>digest</category>
      <category>russian</category>
    </item>
    <item>
      <title>WT IndexNow - send your Joomla urls to search engines via IndexNow protocol</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Sat, 03 Jan 2026 09:03:05 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/wt-indexnow-send-your-joomla-urls-to-search-engines-via-indexnow-protocol-590e</link>
      <guid>https://dev.to/sergeytolkachyov/wt-indexnow-send-your-joomla-urls-to-search-engines-via-indexnow-protocol-590e</guid>
      <description>&lt;p&gt;A free package of plugins that provides manual and automatic sending Joomla URLs to search engines for reindexing using the IndexNow protocol (&lt;a href="https://indexnow.org" rel="noopener noreferrer"&gt;https://indexnow.org&lt;/a&gt;).&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficpo45vs2dlqfmp0ic5m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficpo45vs2dlqfmp0ic5m.jpg" alt="indexNow joomla 6 plugins admin panel" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported components
&lt;/h2&gt;

&lt;p&gt;At the moment, the package includes providers for the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Joomla articles and aritcles categories&lt;/li&gt;
&lt;li&gt;Joomla contacts and contacts categories&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://web-tolk.ru/dev/components/sw-jprojects?utm_source=dev-to" rel="noopener noreferrer"&gt;SW JProjects&lt;/a&gt; &lt;a href="https://github.com/WebTolk/SWJProjects" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;a href="https://extensions.joomla.org/extensions/extension/authoring-a-content/content-construction/swjprojects/" rel="noopener noreferrer"&gt;Joomla Extensions Directory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.webdesigner-profi.de/joomla-webdesign/shop.html" rel="noopener noreferrer"&gt;JoomShopping&lt;/a&gt; &lt;a href="https://extensions.joomla.org/extension/joomshopping/" rel="noopener noreferrer"&gt;Joomla Extensions Directory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.phoca.cz/phocadownload" rel="noopener noreferrer"&gt;Phoca Download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.phoca.cz/phocacart" rel="noopener noreferrer"&gt;Phoca Cart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://radicalmart.ru?utm_source=dev-to" rel="noopener noreferrer"&gt;RadicalMart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1cyln8ocwtl9nfnx0l9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1cyln8ocwtl9nfnx0l9.jpg" alt="IndexNow in Joomla" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F275ktsnyv03zwhrsdieh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F275ktsnyv03zwhrsdieh.jpg" alt="Phoca Cart IndexNow button" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The plugin package is designed to send the URLs of Joomla sites to search engines for reindexing using the IndexNow. According to the documentation, the protocol is supported by all major search engines except Google. The protocol is used to receive URLs from sites that need to be reindexed or indexed for the first time as quickly as possible. Sending site addresses in this way will significantly speed up the indexing of new or modified content by search engines. Search engines use this protocol to exchange data with each other, so by sending a URL to one of them, you share it with everyone at once.&lt;/p&gt;

&lt;p&gt;Using the IndexNow protocol, no more than 10000 URLs can be sent per day. Keep this in mind when working with content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://extensions.joomla.org/extension/site-management/seo-a-metadata/wt-indexnow/" rel="noopener noreferrer"&gt;Joomla Extensisons Directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web-tolk.ru/en/dev/joomla-plugins/wt-indexnow?utm_source=dev-to" rel="noopener noreferrer"&gt;Extension page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/WebTolk/WT-Index-now-joomla-package" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;See &lt;a href="https://www.youtube.com/watch?v=tLk_yxqks6s" rel="noopener noreferrer"&gt;YouTube video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>joomla</category>
      <category>seo</category>
      <category>indexnow</category>
    </item>
    <item>
      <title>WT Yandex map items v.2.0.5 Joomla module has been released</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Wed, 30 Jul 2025 05:09:25 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/wt-yandex-map-items-v205-joomla-module-has-been-released-1350</link>
      <guid>https://dev.to/sergeytolkachyov/wt-yandex-map-items-v205-joomla-module-has-been-released-1350</guid>
      <description>&lt;p&gt;Display data from various component's custom fields like &lt;a href="https://yandex.com/maps" rel="noopener noreferrer"&gt;Yandex.Maps&lt;/a&gt; placemarks. Joomla articles are displayed as Yandex.maps placemarks. The articles categories and contacts are not displayed yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo91thwvht1wefu6ft49n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo91thwvht1wefu6ft49n.png" alt="WT Yandex map items Joomla module screenshot" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  v.2.0.5. What's new?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  UX improving
&lt;/h3&gt;

&lt;p&gt;When interacting with a page on mobile devices, if the map is displayed in full width, it starts scrolling instead of the web page. To avoid this situation, an overlay for the map has been added. Now interaction with the map is possible after clicking on the map. The overlay automatically turns on when the cursor leaves the borders of the map container and the map can be scrolled again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web-tolk.ru/en/dev/joomla-modules/wt-yandex-map-items?utm_source=devto" rel="noopener noreferrer"&gt;Extension page&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/sergeytolkachyov/WT-Yandex-map-items-joomla-module" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>joomla</category>
      <category>webdev</category>
      <category>yandex</category>
      <category>map</category>
    </item>
    <item>
      <title>Joomla 5.2.4 Security &amp; Bugfix Release</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Wed, 19 Feb 2025 05:23:10 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/joomla-524-security-bugfix-release-4hpo</link>
      <guid>https://dev.to/sergeytolkachyov/joomla-524-security-bugfix-release-4hpo</guid>
      <description>&lt;p&gt;Let’s celebrate! The Joomla! Project is pleased to announce the release of Joomla 5.2.4 and Joomla 4.4.11. This is a security and bug fix release for the 5.x and 4.x series of Joomla.&lt;/p&gt;

&lt;p&gt;These releases continue Joomla’s high standards in accessible web design, highlighting Joomla's values of inclusiveness, simplicity and security into an even more powerful open-source web platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Fix
&lt;/h2&gt;

&lt;p&gt;[20250201] - Core - SQL injection vulnerability in Scheduled Tasks component. &lt;a href="https://developer.joomla.org/security-centre/958-20250201-core-sql-injection-vulnerability-in-scheduled-tasks-component.html" rel="noopener noreferrer"&gt;Read more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug fixes and Improvements with 5.2.4
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fix namespace map creation on PHP 8.4 (#44789)&lt;/li&gt;
&lt;li&gt;Fix PHP Warning for debuguser, debuggroup (#44721)&lt;/li&gt;
&lt;li&gt;Fix handling of null values on update row (#39607)&lt;/li&gt;
&lt;li&gt;Fix cache counting issue - correctly count number of files (#43986)&lt;/li&gt;
&lt;li&gt;Fix permissions for manually running scheduled tasks (#36719)&lt;/li&gt;
&lt;li&gt;Tag Router: Allow numeric/CSV IDs (Regression) (#44784)&lt;/li&gt;
&lt;li&gt;Fix for Composer update to enshrined/svg-sanitize to resolve SVG upload issues (#44746)&lt;/li&gt;
&lt;li&gt;Fix for Article cannot be saved successfully on the front-end (#44680)&lt;/li&gt;
&lt;li&gt;Fix media downloads with spaces (Follow-up of #37396) (#44745)&lt;/li&gt;
&lt;li&gt;Finder Router: Filter out unnecessary query elements (#44055)&lt;/li&gt;
&lt;li&gt;Jooa11y plugin and page cache conflicts (#41956)&lt;/li&gt;
&lt;li&gt;Fix a11y issue in accordion (role attribute) (#40578)&lt;/li&gt;
&lt;li&gt;Remove alt-text for menu items when both image and title are set (a11y fix) (#40675)&lt;/li&gt;
&lt;li&gt;Fix breadcrumbs color in light and dark mode (#44212)&lt;/li&gt;
&lt;li&gt;Email alt text fix in contact component (#44491)&lt;/li&gt;
&lt;li&gt;Web Asset Manager: Incorrect loading of external resource with / at the end (#44774)&lt;/li&gt;
&lt;li&gt;Fix media downloads with spaces (Follow-up of #37396) (#44745)&lt;/li&gt;
&lt;li&gt;Fix multi-select behavior in Media Manager (Follow-up of #39824) (#44747)&lt;/li&gt;
&lt;li&gt;Fix error handling when creating folders in Media Manager (#39878)&lt;/li&gt;
&lt;li&gt;Fix assets for com_scheduler on new installations (#44684)&lt;/li&gt;
&lt;li&gt;Fix password reset broken in backend (#44723)&lt;/li&gt;
&lt;li&gt;Email cloak plugin fails for emails with IDN (Internationalized Domain Names) (#39888)&lt;/li&gt;
&lt;li&gt;Fix handling of root path removal only when it is at the beginning (#36685)&lt;/li&gt;
&lt;li&gt;Load the namespace from the cached manifest (Reverted) (#44755)&lt;/li&gt;
&lt;li&gt;Fix for Codemirror duplicated assets entries (#44674)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full list on GitHub is here: &lt;a href="https://github.com/joomla/joomla-cms/milestone/136?closed=1" rel="noopener noreferrer"&gt;https://github.com/joomla/joomla-cms/milestone/136?closed=1&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.joomla.org/announcements/release-news/5920-joomla-5-2-4-security-bugfix-release.html" rel="noopener noreferrer"&gt;Read more&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Joomla Community resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joomla.org/" rel="noopener noreferrer"&gt;https://joomla.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.joomla.org/Mattermost" rel="noopener noreferrer"&gt;Joomla Community chat in Mattermost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>joomla</category>
      <category>php</category>
      <category>coding</category>
    </item>
    <item>
      <title>Joomla tip: show notifications by Joomla.renderMessages</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Mon, 17 Feb 2025 13:49:06 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/joomla-tip-show-notifications-by-joomlarendermessages-13lm</link>
      <guid>https://dev.to/sergeytolkachyov/joomla-tip-show-notifications-by-joomlarendermessages-13lm</guid>
      <description>&lt;p&gt;The user performs some actions on the site and receives messages about the successful or unsuccessful result, as well as just informational messages. In php code, we are used to using the &lt;code&gt;enqueueMessage()&lt;/code&gt; method for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Message'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getApplication&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;enqueueMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'warning'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most often, language constants are used as messages so that users can receive messages in their own language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Language\Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getApplication&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;enqueueMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SOME_LANG_CONSTANT'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'info'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rendering of Joomla messages in the frontend
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/vgQlk7OoLvE"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Here we will need the connected core files &lt;code&gt;core.js&lt;/code&gt; and &lt;code&gt;messages.js&lt;/code&gt;. A little excerpt from the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Render messages send via JSON
 * Used by some javascripts such as validate.js
 *
 * @param   {object}  messages JavaScript object containing the messages to render.
 *          Example:
 *          const messages = {
 *              "message": ["This will be a green message", "So will this"],
 *              "error": ["This will be a red message", "So will this"],
 *              "info": ["This will be a blue message", "So will this"],
 *              "notice": ["This will be same as info message", "So will this"],
 *              "warning": ["This will be a orange message", "So will this"],
 *              "my_custom_type": ["This will be same as info message", "So will this"]
 *          };
 * @param  {string} selector The selector of the container where the message will be rendered
 * @param  {bool}   keepOld  If we shall discard old messages
 * @param  {int}    timeout  The milliseconds before the message self destruct
 * @return  void
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what it looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Joomla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;renderMessages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Joomla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COM_SWJPROJECTS_USER_KEYS_KEY_SUCCESSFULLY_COPYED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we see that we can use language constants as a message in Javascript. And that's cool! To do this, we use the &lt;code&gt;Joomla.Text._()&lt;/code&gt; method (similar to &lt;code&gt;Text::_()&lt;/code&gt; in PHP). But Javascript has to get the values of these language constants from somewhere. And to do this, in the php code of our page, we need to take care of it and add the language constants necessary for js using the &lt;code&gt;Text::script()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Language\Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SOME_LANG_CONSTANT_SUCCESS'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SOME_LANG_CONSTANT_FAIL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way I can access the values of the language constants &lt;code&gt;SOME_LANG_CONSTANT_SUCCESS&lt;/code&gt; and &lt;code&gt;SOME_LANG_CONSTANT_FAIL&lt;/code&gt; in javascript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Joomla Community resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joomla.org/" rel="noopener noreferrer"&gt;https://joomla.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.joomla.org/Mattermost" rel="noopener noreferrer"&gt;Joomla Community chat in Mattermost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web-tolk.ru/en/dev" rel="noopener noreferrer"&gt;WebTolk Joomla extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web-tolk.ru/blog/sovet-po-joomla-pokaz-uvedomlenij-joomla-rendermessages" rel="noopener noreferrer"&gt;This article in Russian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>joomla</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>coding</category>
    </item>
    <item>
      <title>Joomla tip: Use the Joomla\Uri\Uri class to create a URL.</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Thu, 16 Jan 2025 11:15:47 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/joomla-tip-use-the-joomlauriuri-class-to-create-a-url-7ib</link>
      <guid>https://dev.to/sergeytolkachyov/joomla-tip-use-the-joomlauriuri-class-to-create-a-url-7ib</guid>
      <description>&lt;p&gt;When building a url in the code, you can collect all the strings with concatenation of the form&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/index.php?option='&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$option&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'&amp;amp;view='&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$view&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'&amp;amp;param1='&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$value1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and for small strings, it can even be convenient.  But if there are a lot of parameters or they require standardization/cleaning in the process, then not everything is so convenient and obvious. For example, there may be a leading slash in part of the url (a slash at the beginning of the url fragment) and the incoming domain for the request may also end in a slash, and so we get an incorrect url for the request with a double slash somewhere in the middle...&lt;/p&gt;

&lt;p&gt;For the tasks of standardization and uniformity of url retrieval, Joomla has the &lt;code&gt;Joomla\Uri\Uri&lt;/code&gt; class. Previously, with Joomla 1.6 and older, it was called &lt;code&gt;JUri&lt;/code&gt;. This class provides work with urls according to the &lt;strong&gt;RFC3986&lt;/strong&gt; standard and takes over the work of parsing URLs for spare parts or assembling URLs from spare parts))&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: get a specific parameter from the url.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\Uri\Uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://web-tolk.ru/dev/biblioteki?param=value'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// output the line 'value' here&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'param'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, there is a native PHP function &lt;code&gt;parse_url&lt;/code&gt;, you might say... But the &lt;code&gt;Uri&lt;/code&gt; class ensures safe operation with UTF-8 in urls, including Cyrillic domains. In order not to write various checks yourself, you can use the features of the Joomla core.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build required url in the code in Joomla
&lt;/h2&gt;

&lt;p&gt;Here, too, everything is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\Uri\Uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'web-tolk.ru'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// setPath() starts with leading slash&lt;/span&gt;
&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dev/biblioteki'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// GET-parameters can be passed as an array&lt;/span&gt;
&lt;span class="nv"&gt;$vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'param1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'value1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'param2'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'value2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'param3'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'value3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$vars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// echo our url like string&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hierarchy of &lt;code&gt;Uri&lt;/code&gt; classes in Joomla is designed so that getter methods are in the &lt;code&gt;AbstractUri&lt;/code&gt; class, and setters are in the &lt;code&gt;Uri&lt;/code&gt; class. The setters can be viewed in the file &lt;strong&gt;libraries/vendor/joomla/uri/src/Uri.php&lt;/strong&gt; . We look at Hetaera in the file &lt;strong&gt;libraries/vendor/joomla/uri/src/AbstractUri.php&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If you have PHPStorm, then it knows Joomla perfectly well and tells you everything you need.&lt;br&gt;
Well, you can give a link to the &lt;a href="https://docs.joomla.org/API16:JURI" rel="noopener noreferrer"&gt;old documentation page&lt;/a&gt;, which is still mostly relevant, adjusted for the use of namespaces.&lt;/p&gt;

&lt;p&gt;Uri structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     foo://example.com:8042/over/there?name=ferret#nose
     \_/   \______________/\_________/ \_________/ \__/
      |           |            |            |        |
   scheme     authority       path        query   fragment

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Joomla Community resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joomla.org/" rel="noopener noreferrer"&gt;https://joomla.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.joomla.org/Mattermost" rel="noopener noreferrer"&gt;Joomla Community chat in Mattermost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web-tolk.ru/en/dev" rel="noopener noreferrer"&gt;WebTolk Joomla extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://habr.com/ru/articles/851520/" rel="noopener noreferrer"&gt;This article in Russian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>joomla</category>
      <category>php</category>
      <category>coding</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The anatomy of smart search in Joomla 5. Creating a plugin Part 3</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Fri, 03 Jan 2025 06:29:11 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/the-anatomy-of-smart-search-in-joomla-5-creating-a-plugin-part-3-2931</link>
      <guid>https://dev.to/sergeytolkachyov/the-anatomy-of-smart-search-in-joomla-5-creating-a-plugin-part-3-2931</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/sergeytolkachyov/the-anatomy-of-smart-search-in-joomla-5-part-1-introduction-4mfo"&gt;first article&lt;/a&gt;, we got acquainted with the capabilities of the Joomla smart search component, talked about the parameters and configuration of scheduled indexing using CRON. And in the &lt;a href="https://dev.to/sergeytolkachyov/the-anatomy-of-smart-search-in-joomla-5-part-2-creating-a-plugin-i-2fch"&gt;second article&lt;/a&gt; we've started to code our custom plugin for Joomla smart search component. So, let's finish it!&lt;/p&gt;

&lt;h2&gt;
  
  
  The index() method
&lt;/h2&gt;

&lt;p&gt;This method should adapt the data obtained from the database in order to submit it for indexing. As an argument, the &lt;code&gt;$item&lt;/code&gt; element (article, product, tag, etc.) is passed to it as an instance class of the &lt;code&gt;\Joomla\Component\Finder\Administrator\Indexer\Result&lt;/code&gt; class. The properties of &lt;code&gt;$item&lt;/code&gt; match those that we selected from the database. The ultimate goal of this method is to call &lt;code&gt;$this-&amp;gt;indexer-&amp;gt;index($item)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You need to understand what the search results will look like in order to understand what to compare with what.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xi8swn5ngefurba099o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xi8swn5ngefurba099o.png" alt="Joomla 5 smart search result structure" width="800" height="153"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Joomla 5 smart search result structure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here I take the names straight from the code. In this case, the images are not shown in the search results, but they are also there - &lt;code&gt;$this-&amp;gt;result-&amp;gt;imageUrl&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;imageUrl&lt;/strong&gt; - a picture of the Joomla article, product, tag, contact. It is displayed if enabled in the settings of the smart search component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;title&lt;/strong&gt; - the title of the material, the name of the product, contact, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;description&lt;/strong&gt; and &lt;strong&gt;body&lt;/strong&gt; - a text description. We remember that many entities in Joomla have a short and complete description or an introductory and full text. Here they are combined and then trimmed to the character limit specified in the settings. &lt;strong&gt;body&lt;/strong&gt; is the full text or description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;getTaxonomy()&lt;/strong&gt; - the method receives and outputs taxonomy data for a given search result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, we have few data visible to the user - only 4 types. And we get more data from the database. We need to understand which of them will be available for indexing by search, and which will only be displayed.&lt;/p&gt;

&lt;p&gt;Here is the code of the &lt;code&gt;index()&lt;/code&gt; method with comments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Method to index an item. The item must be a Result object.
 *
 * @param   Result  $item  The item to index as a Result object.
 *
 * @return  void
 *     
 * @throws  \Exception on database error.
 * @since   2.5
 */&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Result&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Setting the language of the indexed element - the default language of the site&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setLanguage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Check if JoomShopping is enabled in Joomla.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ComponentHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// We take part of the paths to the pictures from the component parameters.&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadJshopConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Setting the context for indexing&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'com_jshopping.product'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// We collect all the parameters in a bunch: the search component, JoomShopping and the site.&lt;/span&gt;
    &lt;span class="c1"&gt;// They will be available to us in the output layout&lt;/span&gt;
    &lt;span class="nv"&gt;$registry&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;clone&lt;/span&gt; &lt;span class="nc"&gt;ComponentHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'com_jshopping'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$registry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jshopConfig&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="c1"&gt;// Meta-data: meta-keywords, meta description, author,&lt;/span&gt;
    &lt;span class="c1"&gt;// index / no-index values for robots&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Process the content with content plugins - the onContentPrepare event.&lt;/span&gt;
    &lt;span class="c1"&gt;// Content plugins always have a context check.&lt;/span&gt;
    &lt;span class="c1"&gt;// If it is equal to 'com_finder.indexer', then content plugins will usually not work.&lt;/span&gt;
    &lt;span class="c1"&gt;// ONLY TEXT should be given for indexing.&lt;/span&gt;
    &lt;span class="c1"&gt;// Neither pictures nor YouTube videos should get there, so&lt;/span&gt;
    &lt;span class="c1"&gt;// the indexed content will be cleared of HTML tags.&lt;/span&gt;
    &lt;span class="c1"&gt;// The raw short codes are just text and can get&lt;/span&gt;
    &lt;span class="c1"&gt;// into the search results.&lt;/span&gt;

    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Helper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;prepareContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Helper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;prepareContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Include here JoomShopping classed.&lt;/span&gt;
    &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;JPATH_SITE&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/components/com_jshopping/bootstrap.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;\JSFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;loadAdminLanguageFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;languageTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// A trick. We want to be able to search by price, product code, etc too.&lt;/span&gt;
    &lt;span class="c1"&gt;// Attaching all this data to the body.&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;

    &lt;span class="c1"&gt;// Manufacturer code&lt;/span&gt;
    &lt;span class="nv"&gt;$manufacturer_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'manufacturer_code'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$manufacturer_code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSHOP_MANUFACTURER_CODE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;': '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$manufacturer_code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// EAN&lt;/span&gt;
    &lt;span class="nv"&gt;$product_ean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_ean'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_ean&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSHOP_EAN'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;': '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product_ean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Old price&lt;/span&gt;
    &lt;span class="nv"&gt;$product_old_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_old_price'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_old_price&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$product_old_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\JSHElper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_old_price'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;        &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSHOP_OLD_PRICE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;': '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product_old_price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Buy price&lt;/span&gt;
    &lt;span class="nv"&gt;$product_buy_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_buy_price'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_buy_price&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$product_buy_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\JSHElper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_buy_price'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;        &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSHOP_PRODUCT_BUY_PRICE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;': '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product_buy_price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Price&lt;/span&gt;
    &lt;span class="nv"&gt;$product_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_price'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_price&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$product_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\JSHElper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;    &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSHOP_PRODUCT_PRICE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;': '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product_price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="c1"&gt;// URL - the unique key of the element in the table. Read more about this after the sample code&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'com_jshopping'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;catslug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Link to element - to JoomShopping product&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// A menu item can be created for product. Menu item has own page header.&lt;/span&gt;
    &lt;span class="c1"&gt;// Joomla menu has higher priority then we'll take data from it.&lt;/span&gt;
    &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getItemMenuTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Adjust the title if necessary.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'use_menu_title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Add product image&lt;/span&gt;
    &lt;span class="nv"&gt;$product_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_image&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image_product_live_path'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product_image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;imageAlt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// The product has no author. But you can do this if you search by author/user&lt;/span&gt;
    &lt;span class="c1"&gt;// For example, you enabled search by author in the smart search component settings&lt;/span&gt;
    &lt;span class="c1"&gt;// and it should search for everything related to this author&lt;/span&gt;
    &lt;span class="c1"&gt;// $item-&amp;gt;metaauthor = $item-&amp;gt;metadata-&amp;gt;get('author');&lt;/span&gt;

    &lt;span class="c1"&gt;// Add the metadata processing instructions.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addInstruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Indexer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;META_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'metakey'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addInstruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Indexer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;META_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'metadesc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// $item-&amp;gt;addInstruction(Indexer::META_COTNTEXT, 'metaauthor');&lt;/span&gt;
    &lt;span class="c1"&gt;// $item-&amp;gt;addInstruction(Indexer::META_CONTEXT, 'author');&lt;/span&gt;
    &lt;span class="c1"&gt;// $item-&amp;gt;addInstruction(Indexer::META_CONTEXT, 'created_by_alias');&lt;/span&gt;

    &lt;span class="c1"&gt;// Access group for the default search result.&lt;/span&gt;
    &lt;span class="c1"&gt;// We hardcode "1" - that is, for everyone. But here you can&lt;/span&gt;
    &lt;span class="c1"&gt;// take the access group from the product.&lt;/span&gt;
    &lt;span class="c1"&gt;// Or show different search results to different access groups.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if the category for the product is published.&lt;/span&gt;
    &lt;span class="c1"&gt;// Products should only be published if their category is published.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;translateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cat_state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the list of taxonomies to display from the plugin parameters&lt;/span&gt;
    &lt;span class="nv"&gt;$taxonomies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'taxonomies'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'language'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="c1"&gt;// Name of the search type in the drop-down list of types: materials, contacts.&lt;/span&gt;
    &lt;span class="c1"&gt;// In our case - product. We take a language constant for this.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTaxonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSHOP_PRODUCT'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// Add product categories to the drop-down list&lt;/span&gt;
    &lt;span class="c1"&gt;// categories so that you can search only in a specific&lt;/span&gt;
    &lt;span class="c1"&gt;// categories. Here we already transfer the names of the categories.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTaxonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Search only in the desired language&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTaxonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLangTag&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// Search results can be limited by publication start and end dates&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;publish_start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toSql&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toSql&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Add additional data for the indexed element.&lt;/span&gt;
    &lt;span class="c1"&gt;// Here in the helper the "onPrepareFinderContent" event is called&lt;/span&gt;
    &lt;span class="c1"&gt;// This is usually how comments, tags, labels&lt;/span&gt;
    &lt;span class="c1"&gt;// and other things that should be available for search are added.&lt;/span&gt;
    &lt;span class="c1"&gt;// Accordingly, individual plugins work with this event.&lt;/span&gt;
    &lt;span class="c1"&gt;// In our case, we do not need this yet.&lt;/span&gt;
    &lt;span class="c1"&gt;// Helper::getContentExtras($item);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add custom fields (com_fields) Joomla, if the component&lt;/span&gt;
    &lt;span class="c1"&gt;// supports them.&lt;/span&gt;
    &lt;span class="c1"&gt;// In our case, we do not need this yet.&lt;/span&gt;
    &lt;span class="c1"&gt;// Helper::addCustomFields($item, 'com_jshopping.product');&lt;/span&gt;

    &lt;span class="c1"&gt;// Index the item.&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;indexer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Types of instructions for "marking up the weight" of content for indexing.
&lt;/h2&gt;

&lt;p&gt;I am not an expert in fine-tuning indexing, so I will try to describe what I could see in the code. In the parameters of the smart search component, there are weight settings for each part of the indexed content: title, main text, metadata, url, additional text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1esuihhwyqs019ne2dzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1esuihhwyqs019ne2dzj.png" alt="Joomla 5 smart search params - indexing weight settings for title, body, metadata" width="800" height="545"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Joomla 5 smart search params - indexing weight settings for title, body, metadata&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We have already seen these settings above in the first article in seria, but for convenience we will repeat the screenshot.&lt;/p&gt;

&lt;p&gt;In our smart search plugin, we can specify which data in our object for indexing belongs to which type by adding instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// In the index() method&lt;/span&gt;
&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addInstruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Indexer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TEXT_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'product_buy_price'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We look at the types of context for instructions and their names by default in the class &lt;code&gt;\Joomla\Component\Finder\Administrator\Indexer\Result&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1v82vex2784mq1mj2ct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1v82vex2784mq1mj2ct.png" alt="Joomla smart search indexing instructions types for weight markup" width="581" height="251"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Joomla smart search indexing instructions types for weight markup&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As I later found out, &lt;code&gt;list_price&lt;/code&gt; and &lt;code&gt;sale_price&lt;/code&gt; refer to indexing, not to the terminology of an online store.&lt;/p&gt;
&lt;h2&gt;
  
  
  getUrl() method
&lt;/h2&gt;

&lt;p&gt;The unique key for searching is essentially the url of the element in its system form: &lt;strong&gt;index.php?option=com_content&amp;amp;view=article&amp;amp;id=1&lt;/strong&gt;. In the database in the &lt;strong&gt;#__finder_links&lt;/strong&gt; table, it is stored in the &lt;strong&gt;url&lt;/strong&gt; column. But to build a link in the frontend to the desired element from the search results, a more complex option is used with a combination of id and aliases in the url: &lt;strong&gt;index.php?option=com_content&amp;amp;view=article&amp;amp;id=1:article-alias&amp;amp;catid=2&lt;/strong&gt;, which is stored in the adjacent route column. But Joomla routing will determine the final url without specifying an alias, in which case the content of the url and route will be the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Fragment of index() method of smart search plugin for Joomla materials&lt;/span&gt;

&lt;span class="c1"&gt;// Create a URL as identifier to recognise items again.&lt;/span&gt;
&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Build the necessary route and path information.&lt;/span&gt;
&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RouteHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getArticleRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;catid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system url for the indexed element looks different in different components. In the components that follow the "Joomla way", you can use one controller, which, if no specific controller is found, will immediately show the desired &lt;code&gt;View&lt;/code&gt;. Therefore, in standard Joomla components, we usually do not find links indicating the controller in the GET parameters. They will all look like &lt;strong&gt;index.php?option=com_content&amp;amp;view=article&amp;amp;id=15&lt;/strong&gt;. This is the url that the &lt;code&gt;getUrl()&lt;/code&gt; method of the &lt;code&gt;Adapter&lt;/code&gt; class returns to us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Method to get the URL for the item. The URL is how we look up the link
 * in the Finder index.
 *
 * @param   integer  $id         The id of the item.
 * @param   string   $extension  The extension the category is in.
 * @param   string   $view       The view for the URL.
 *
 * @return  string  The URL of the item.
 *
 * @since   2.5
 */&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'index.php?option='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$extension&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;view='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$view&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;id='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, JoomShopping has its own story and URLs are built somewhat differently. We will not be able to use the standard method and we are redefining it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Uri\Uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * @param string $product_id  Product id
 * @param string $extension  Always 'com_jshopping'
 * @param string $view  Not used for JoomShopping
 *
 * @return string
 *
 * @since 1.0.0
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'not_used'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * There is the trick. For JoomShopping product url construction
     * we need only in product id and category id
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadJshopConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Keeping in mind the difficulties with categories in JoomShopping&lt;/span&gt;
    &lt;span class="c1"&gt;// we separate the process of getting the category into a separate method.&lt;/span&gt;
    &lt;span class="nv"&gt;$category_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProductCategoryId&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'index.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'option'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'com_jshopping'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'controller'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'task'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'category_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$category_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'product_id'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="c1"&gt;// When constructing a url in JoomShopping, it is advisable to find and specify&lt;/span&gt;
    &lt;span class="c1"&gt;// The correct itemId is the id of the menu item for JoomShopping.&lt;/span&gt;
    &lt;span class="c1"&gt;// Otherwise, we may have duplicate pages by url&lt;/span&gt;
    &lt;span class="c1"&gt;// We connected the JoomShopping API earlier, so the JSHelper should already be here.&lt;/span&gt;
    &lt;span class="nv"&gt;$defaultItemid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\JSHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getDefaultItemid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Itemid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$defaultItemid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  An atypical implementation of multilingualism. Indexation.
&lt;/h2&gt;

&lt;p&gt;Let's return to the problem of multilingualism, in which we do not have a separate indexed entity for each content language. But there is an indexed entity that contains values for all languages at once.&lt;/p&gt;

&lt;p&gt;The solution is to get a list of all languages inside the &lt;code&gt;index()&lt;/code&gt; method, collect the &lt;code&gt;Result&lt;/code&gt; object for each language and then give each object for indexing. We need to divide the data into those that are the same for both languages (usually &lt;code&gt;catid&lt;/code&gt;, &lt;code&gt;access&lt;/code&gt;, etc.) and different (&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;fulltext&lt;/code&gt; etc.). That is, the &lt;code&gt;$this-&amp;gt;indexer-&amp;gt;index()&lt;/code&gt; method will be called several times inside the &lt;code&gt;Index()&lt;/code&gt; method of our plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Method to index an item. The item must be a Result object.
 *
 * @param   Result  $item  The item to index as a Result object.
 *
 * @return  void
 *
 * @throws  \Exception on database error.
 * @since   2.5
 */&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Result&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Initialise the item parameters.&lt;/span&gt;
    &lt;span class="nv"&gt;$registry&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;clone&lt;/span&gt; &lt;span class="nc"&gt;ComponentHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'com_swjprojects'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$registry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'com_swjprojects.project'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$lang_codes&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LanguageHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getLanguages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lang_code'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$translates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTranslateProjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;catid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Translate the state. projects should only be published if the category is published.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;translateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cat_state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get taxonomies to display&lt;/span&gt;
    &lt;span class="nv"&gt;$taxonomies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'taxonomies'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'language'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add the type taxonomy data.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;\in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$taxonomies&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTaxonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Project'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$translates&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Trigger the onContentPrepare event.&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Helper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;prepareContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;introtext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Helper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;prepareContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fulltext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$metadata&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metakey&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$metadata&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'keywords'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metadesc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$metadata&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;introtext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Add the metadata processing instructions.&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addInstruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Indexer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;META_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'metakey'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addInstruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Indexer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;META_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'metadesc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Multilanguage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lang_codes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$lang_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$lang_code&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;lang_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$lang_code&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a URL as identifier to recognise items again.&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$lang&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the necessary route and path information.&lt;/span&gt;
        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RouteHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getProjectRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;catid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Get the menu title if it exists.&lt;/span&gt;
        &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getItemMenuTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Adjust the title if necessary.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'use_menu_title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Add the category taxonomy data.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;\in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$taxonomies&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTaxonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$translate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Add the language taxonomy data.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;\in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$taxonomies&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTaxonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;


        &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ImagesHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'projects'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'icon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Add the image.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$icon&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$icon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;imageAlt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Add the meta author.&lt;/span&gt;
        &lt;span class="c1"&gt;// $item-&amp;gt;metaauthor = $item-&amp;gt;metadata-&amp;gt;get('author');&lt;/span&gt;

        &lt;span class="c1"&gt;// Get content extras.&lt;/span&gt;
        &lt;span class="c1"&gt;// Helper::getContentExtras($item);&lt;/span&gt;
        &lt;span class="c1"&gt;// Helper::addCustomFields($item, 'com_swjprojects.project');&lt;/span&gt;

        &lt;span class="c1"&gt;// Index the item.&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;indexer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to unify the value for the &lt;code&gt;route&lt;/code&gt; field for each language, so we make our own implementation of the &lt;code&gt;getUrl()&lt;/code&gt; method and add the &lt;code&gt;$lang&lt;/code&gt; parameter to the url.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * @param   int     $id
 * @param   string  $extension
 * @param   string  $view
 * @param   string  $lang  Language SEF code like `ru`, `en` etc
 *
 * @return string
 *
 * @since 2.1.0
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'index.php?option='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$extension&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;view='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$view&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;id='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lang&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;lang='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$lang&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  getItems() and getContentCount() methods
&lt;/h2&gt;

&lt;p&gt;In general, we don't need anything else for manual indexing and reindexing of content, as well as scheduled via the CLI. However, if we come across some very unusual beast in the form of non-Joomla data, some third-party database, then we can completely redefine the logic of the parent Adapter class for these purposes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getContentCount()&lt;/code&gt; - the method should return an integer - the number of indexed elements.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getItems($offset, $limit, $query = null)&lt;/code&gt; - under the hood, calls &lt;code&gt;getListQuery()&lt;/code&gt; and sets &lt;code&gt;$offset&lt;/code&gt; and &lt;code&gt;$limit&lt;/code&gt;, brings everything to a single view - objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If using one &lt;code&gt;getListQuery()&lt;/code&gt; method and accessing it from 3 other methods is inconvenient for some reason, you can customize requests and their processing in redefined methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reindexing content on the fly
&lt;/h2&gt;

&lt;p&gt;To solve this problem, we have several ways, two of which are the periodic manual and CRON indexing, as I wrote above. However, this is fraught with an untimely index update and site users may not receive updated data in search results on time. Therefore, there is another way: &lt;strong&gt;reindexing the content on the fly, immediately after saving the changes&lt;/strong&gt;. To do this, a &lt;strong&gt;content group&lt;/strong&gt; plugin is created, which triggers smart search plugin events at the right moments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjs3ezc4vx5xdstir1kpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjs3ezc4vx5xdstir1kpp.png" alt="Content plugin for joomla smart search" width="800" height="494"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Content plugin for joomla smart search&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard Joomla Model Events.&lt;/strong&gt;&lt;br&gt;
If the component is written according to the canons of Joomla and inherits its classes, then many models (&lt;code&gt;Model&lt;/code&gt; - MVC) trigger standard events, among which we are interested in several:&lt;br&gt;
onContentBeforeSave - The event is triggered before any Joomla entity is saved.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onContentAfterSave&lt;/code&gt; - The event is triggered after saving any Joomla entity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onContentAfterDelete&lt;/code&gt; - The event is triggered after deleting any Joomla entity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onContentChangeState&lt;/code&gt; - the event is triggered after a state change (unpublished/published).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onCategoryChangeState&lt;/code&gt; - The event is triggered after the category status changes (if the standard Joomla categories component is used).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, the smart search plugin causes the content to be reindexed at the listed points in time. In each of these events, the context of the event call is passed in the form of &lt;code&gt;&amp;lt;component&amp;gt;.&amp;lt;entity&amp;gt;&lt;/code&gt;, for example, &lt;code&gt;com_content.article&lt;/code&gt; or &lt;code&gt;com_menus.menu&lt;/code&gt;. According to the desired context, you can determine whether to start reindexing or not. We are already doing this check in the smart search plugin. An example from the code of the Finder content plugin for Joomla articles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Event\Finder&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;FinderEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Smart Search after save content method.
 * Content is passed by reference, but after the save, so no changes will be saved.
 * Method is called right after the content is saved.
 *
 * @param   string  $context  The context of the content passed to the plugin (added in 1.6)
 * @param   object  $article  A \Joomla\CMS\Table\Table\ object
 * @param   bool    $isNew    If the content has just been created
 *
 * @return  void
 *
 * @since   2.5
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onContentAfterSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$isNew&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;importFinderPlugins&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Trigger the onFinderAfterSave event.&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDispatcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'onFinderAfterSave'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FinderEvent\AfterSaveEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'onFinderAfterSave'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'context'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'subject'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'isNew'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$isNew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the &lt;code&gt;onFinderAfterSave&lt;/code&gt; event is called here, which is specific specifically for smart search plugins. And in the &lt;code&gt;onFinderAfterSave()&lt;/code&gt; method of our smart search plugin, there is already a check for the desired context and reindexing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\CMS\Event\Finder&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;FinderEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Smart Search after save content method.
 * Reindexes the link information for an article that has been saved.
 * It also makes adjustments if the access level of an item or the
 * category to which it belongs has changed.
 *
 * @param   FinderEvent\AfterSaveEvent   $event  The event instance.
 *
 * @return  void
 *
 * @since   2.5
 * @throws  \Exception on database error.
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onFinderAfterSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FinderEvent&lt;/span&gt;&lt;span class="nc"&gt;\AfterSaveEvent&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$row&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$isNew&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getIsNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// We only want to handle articles here.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'com_content.article'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'com_content.form'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check if the access levels are different.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$isNew&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;old_access&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Process the change.&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;itemAccessChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Reindex the item.&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reindex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check for access changes in the category.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'com_categories.category'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check if the access levels are different.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$isNew&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;old_cataccess&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;categoryAccessChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same way, the work is organized when the state changes and the article or product is deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  getItem() method
&lt;/h2&gt;

&lt;p&gt;This method gets the indexed element by its &lt;code&gt;id&lt;/code&gt;. It is called when reindexing after saving articles, products, etc. - on the &lt;code&gt;onFinderAfterSave&lt;/code&gt; event. Internally, it receives an SQL query from the &lt;code&gt;getListQuery()&lt;/code&gt; method, adds the &lt;code&gt;id&lt;/code&gt; of the requested entity to it, and executes the query. However, in the parent class, the &lt;code&gt;id&lt;/code&gt; field with the prefix &lt;code&gt;a&lt;/code&gt; is hardcoded for tables - &lt;code&gt;$query-&amp;gt;where('a.id = ' . (int) $id)&lt;/code&gt;. Since in our case both the prefix and the name of the field for the request are different, we redefine the method too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// I have indicated here only the namespaces used in the example&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\Utilities\ArrayHelper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Joomla\Component\Finder\Administrator\Indexer\Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Method to get a content item to index.
 *
 * @param   integer  $id  The id of the content item.
 *
 * @return  Result  A Result object.
 *
 * @throws  \Exception on database error.
 * @since   2.5
 */&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the list query and add the extra WHERE clause.&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getListQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'prod.product_id = '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the item to index.&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadAssoc&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Convert the item to a result object.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ArrayHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toObject&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set the item type.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Set the item layout.&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This articles seria does not pretend to be a complete description of the mechanics of smart search in Joomla, even after 4 months of work on it. And in this case it is not a metaphor. But I hope this article will help those who will take up writing plugins for indexing data from Joomla components or third-party systems.&lt;/p&gt;

&lt;p&gt;I will gratefully accept suggestions for improving the article and additions in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Joomla Community resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://joomla.org/" rel="noopener noreferrer"&gt;https://joomla.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://magazine.joomla.org/all-issues/december-2024/the-anatomy-of-smart-search-in-joomla-5-part-2-creating-a-plugin-ii" rel="noopener noreferrer"&gt;This article in Joomla Community Magazine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Joomla Community chat in Mattermost (&lt;a href="https://docs.joomla.org/Mattermost" rel="noopener noreferrer"&gt;read more&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>joomla</category>
      <category>php</category>
      <category>webdev</category>
      <category>search</category>
    </item>
  </channel>
</rss>
