<?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>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>
    <item>
      <title>Connecting third-party PHP libraries in Joomla</title>
      <dc:creator>Sergey Tolkachyov</dc:creator>
      <pubDate>Fri, 27 Dec 2024 07:23:03 +0000</pubDate>
      <link>https://dev.to/sergeytolkachyov/connecting-third-party-php-libraries-in-joomla-4mdl</link>
      <guid>https://dev.to/sergeytolkachyov/connecting-third-party-php-libraries-in-joomla-4mdl</guid>
      <description>&lt;p&gt;PHP libraries are convenient because they can be accessed from anywhere in the Application: from a plugin, component model, module, etc. If someone has already solved a similar problem and designed it as a library (and even updates it), it makes sense to connect this library to your Joomla. For example, to develop a payment method for an online store, you need the official library of a payment aggregator. Or are you satisfied with the official PHP SDK of some CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composer
&lt;/h2&gt;

&lt;p&gt;Joomla does not support working with Composer directly. In order to use the library in your work, you need to "wrap" it in a Joomla extension type &lt;code&gt;library&lt;/code&gt; and install it. In serious projects, the approach of fixing versions of all components of the project is adopted: the code has been checked more than once, tested and allowed to work in the production.&lt;/p&gt;

&lt;p&gt;You create a package with your library, install it wherever you need it. As new versions of the library are released, you update your wrapper and get all the advantages of working with Joomla extensions: updating extensions in the standard way, including through the CLI. View the Changelog of the extension in the admin area BEFORE updating, etc.&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%2F38olm6xgj8zoktk5xrs1.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%2F38olm6xgj8zoktk5xrs1.png" alt="View the Changelog extension in the admin area BEFORE updating to Joomla 5." width="800" height="431"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;View the Changelog extension in the admin area BEFORE updating to Joomla 5.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Joomla, PSR, Symfony
&lt;/h2&gt;

&lt;p&gt;Joomla complies with PSR standards, so it is convenient to work with it in this regard. Some Symfony packages are included in the Joomla core (&lt;code&gt;console&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;vardumper&lt;/code&gt;, &lt;code&gt;yaml&lt;/code&gt;, &lt;code&gt;error-handler&lt;/code&gt; and others), so if you suddenly want to add more, they will fit in and work well. You can see what else is worth in Joomla besides Symfony components in &lt;code&gt;libraries/vendor&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to wrap a 3rd-party PHP library in a Joomla extension?
&lt;/h2&gt;

&lt;p&gt;Nothing complicated. The library files are usually located in the &lt;code&gt;src&lt;/code&gt; folder. Next to this folder, you need to create an XML manifest of the Joomla extension according to the documentation (&lt;a href="https://manual.joomla.org/docs/building-extensions/install-update/installation/manifest" rel="noopener noreferrer"&gt;manual.joomla.org&lt;/a&gt;). Then we pack everything into a zip archive and that's it! It can be installed.&lt;/p&gt;

&lt;p&gt;If you need your own tables in the database for the library to work, you need to add the necessary files with SQL queries during installation or update. Since Joomla 4+ works with namespaces, it is important to specify this namespace in the XML manifest for the extension. Here is an abbreviated example of an XML manifest for the Joomla library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" ?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;extension&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"library"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;WebTolk AmoCRM library&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;libraryname&amp;gt;&lt;/span&gt;Webtolk/Amocrm&lt;span class="nt"&gt;&amp;lt;/libraryname&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.2.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
     ...
     &lt;span class="nt"&gt;&amp;lt;namespace&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Webtolk\Amocrm&lt;span class="nt"&gt;&amp;lt;/namespace&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;files&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;folder&amp;gt;&lt;/span&gt;src&lt;span class="nt"&gt;&amp;lt;/folder&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;filename&amp;gt;&lt;/span&gt;amocrm.xml&lt;span class="nt"&gt;&amp;lt;/filename&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;/files&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/extension&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;libraryname&amp;gt;&lt;/code&gt; tag means that the &lt;code&gt;src&lt;/code&gt; folder from our archive will be copied to &lt;code&gt;JPATH_SITE/libraries/Webtolk/Amocrm&lt;/code&gt;. In the &lt;code&gt;&amp;lt;files&amp;gt;&lt;/code&gt; section, we indicate what needs to be filled in from the archive. And &lt;code&gt;&amp;lt;namespace path="src"&amp;gt;Webtolk\Amocrm&amp;lt;/namespace&amp;gt;&lt;/code&gt; says that the namespace &lt;code&gt;Webtolk\Amocrm&lt;/code&gt; must be registered for the &lt;code&gt;src&lt;/code&gt; folder in &lt;code&gt;JPATH_SITE/libraries/Webtolk/Amocrm&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important notes!
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before Joomla 4.2.7, in fact, the tag &lt;code&gt;&amp;lt;namespace&amp;gt;&lt;/code&gt; from the XML manifest did not work.&lt;/strong&gt; Therefore, it was necessary to add a system plugin to the library package that would register the namespace on the &lt;code&gt;onAfterInitialize&lt;/code&gt; event (Event Dispatcher) using &lt;code&gt;JLoader&lt;/code&gt; class. Accordingly, it was necessary to build a package from the library and the plugin. &lt;strong&gt;Starting with Joomla 4.2.7, it has been fixed and you can do without the plugin.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;So far, updating the library = reinstalling.&lt;/strong&gt; That is, the extension is removed and installed. This decision was made somewhere in the depths of the versions of Joomla 3.x. Why? - hidden under the mountains of PR. We need to search. Why is this important? Because when installing any extension, an entry is created in the "registry" of extensions - in the database in the &lt;code&gt;#__extensions&lt;/code&gt; table. This table has 2 columns of type &lt;code&gt;TEXT&lt;/code&gt; - &lt;code&gt;params&lt;/code&gt; and &lt;code&gt;custom_data&lt;/code&gt;. And this, you must admit, is a considerable amount of data. If you store some library parameters in a database using &lt;code&gt;Joomla\CMS\Helper\LibraryHelper&lt;/code&gt;, then you need to take this behavior of the installer into account and pre-save and then add the saved parameters back to the extension's installer script when updating the library.
&lt;/li&gt;
&lt;/ul&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\Helper\LibraryHelper&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\Cache\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Function called before extension installation/update/removal procedure commences.
 *
 * @param   string            $type     The type of change (install or discover_install, update, uninstall)
 * @param   InstallerAdapter  $adapter  The adapter calling this method
 *
 * @return  boolean  True on success
 *
 * @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;preflight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;InstallerAdapter&lt;/span&gt; &lt;span class="nv"&gt;$adapter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&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;$type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'uninstall'&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="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="cd"&gt;/**
     *
     *  Joomla when updating extensions of the library type, it actually deletes them (along with the data in the database),
     *  and then installs it again.
     *  In order to avoid losing library data from the database, we are writing this crutch.
     *
     * @see https://github.com/joomla/joomla-cms/issues/39360
     *
     */&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;$type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'update'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$lib_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LibraryHelper&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;'Webtolk/Amocrm'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$jconfig&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;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$options&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'defaultgroup'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'wt_amo_crm_temp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'caching'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'cachebase'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$jconfig&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;'cache_path'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'storage'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$jconfig&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;'cache_handler'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$cache&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getInstance&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;$options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lib_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wt_amo_crm_temp'&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="kc"&gt;true&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;And in the &lt;code&gt;postflight()&lt;/code&gt; method accordingly, we put the saved parameters back using &lt;code&gt;LibraryHelper::saveParams('Webtolk/Amocrm', $lib_params);&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For the library to work, it must be enabled in the extension manager&lt;/strong&gt; (Menu - System - Management - Extensions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Often, certain parameters (API keys, tokens, etc.) are needed for the library to work, which must be specified by people in the Joomla admin area.&lt;/strong&gt; For these purposes, it is convenient to write a plugin (an extension type &lt;code&gt;library&lt;/code&gt; does not have its own interface for configuring parameters). Either the system one or your own custom group doesn't matter. Inside your library, you can get plugin parameters pretty quickly like this:
&lt;/li&gt;
&lt;/ul&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\Plugin\PluginHelper&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\Registry\Registry&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;PluginHelper&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="s1"&gt;'system'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wt_amocrm'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;$plugin&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PluginHelper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'system'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wt_amocrm'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nv"&gt;$params&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;\json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plugin&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;$param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// OR you can use Joomla\Registry\Registry&lt;/span&gt;
      &lt;span class="nv"&gt;$params&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;$plugin&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;$param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&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;'param'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'defatul value if empty'&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;
  
  
  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>webdev</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
