0% found this document useful (0 votes)
484 views549 pages

Joomla 4 Development

Uploaded by

Darius
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
484 views549 pages

Joomla 4 Development

Uploaded by

Darius
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 549

Joomla 4 - Developing Extensions Content

Content

1. Intro 1

2. Preface 5

I. Component 25

3. The first view in the backend 27

4. The First View in Frontend 43

5. A Menu Item 49

6. The M in MVC: Model 53

7. Extend the Menu Item with a Variable 57

8. A Joomla database table for your extension 63

9. Using the Database 71

10. Using the Database Data in the Frontend 85

11. Using Language Files 101

12. Configuration 113

13. Access Control List (ACL) 119

14. Server Side Validierung 129

15. Client Side Validierung 137

16. Set Up Categories in Backend 143

17. Publish and Unpublish / Hide 153

Page i January 2023


Joomla 4 - Developing Extensions Content

18. Integrate Custom Fields in Backend 163

19. Integrate Custom Fields in Frontend 171

20. Multilingual Associations 179

21. Filter, Sort, Search 205

22. Toolbar Actions 219

23. Parameter 225

24. Pagination 237

25. Layouts 241

26. Checkin und Checkout 247

27. Batch 253

28. Help Sites 259

29. Featured 265

30.Backend Form 287

31. Frontend Editing 291

32. View by Categories 315

33. Add a Service - Routing 331

34. Dependency Injection 341

35. Dashboard 361

36. Tags 367

37. Web Services 379

II. Plugins 389

38. Plugins 391

Page ii January 2023


Content Joomla 4 - Developing Extensions

III. Module 425

39. First Steps 427

40.Namespaces and Helper 433

41. Parameter 437

42. Installation script 443

IV. Template 449

43. First Steps 451

44.Modul Positions 465

45. Overrides 475

46.Parameter and Variables 495

47. Web Asset Manager 503

48.Dark Mode 507

49.Favicon 513

50.Lighthouse 517

V. This and That 521

51. Package 523

52. Joomla update and change log setup 527

53. Form Fields 535

54. Tests 537

VI. Outro 539

55. We have come to the end 541

January 2023 Page iii


Joomla 4 - Developing Extensions Content

Index 543

Page iv January 2023


Joomla 4 - Developing Extensions 1. Intro

1. Intro

In this book you will learn the basics of Joomla 4 and create extensions without complicated tools.
The book contains references to further reading material and exercises at the end of each chapter.
After reading the book, you will know the basics to create your own Joomla 4 extension. And, what is
important nowadays: I’ll keep the text up to date.

1.1. Thanks

Thank you very much to everyone who reported an error in the text at github.com/astridx/meinblog/pulls
or in the sample code at codeberg.org/astrid/j4examplecode/pulls. These suggestions are a great help
and very important to improve the content.

1.2. FAQ

How do you ideally read this book? Please do not read the text on the sofa, but in front of a computer
on which Joomla 4 is installed. The folder structure of the files here in the book is the same as that of
Joomla. In each chapter there is a section that describes the new files to be added and one that lists
the files to be changed. Copy all this data into your Joomla installation. There is no need to type long
sections of code yourself. You can copy them from the example files. Most changes are immediately
visible and testable in Joomla. Sometimes it is necessary to reinstall the extension. The last section of
each chapter describes the easiest way to install and test the new functions.

Where can you find the sample code?

Not all the texts in the administration section are readable on the pictures in the printed book. My
main idea was to give you some orientation. I assume that you opened the backend at the same time
as you were reading. In my opinion, the arrows in the pictures are sufficient to follow the examples.

You are not satisfied with the quality of the pictures in the book?

The sample code is on Github. Please note that the zip you download from Github via the Code button
is not an installable file. How to create this installable file is explained in the book and there is an

Page 1 January 2023


Joomla 4 - Developing Extensions 1. Intro

example under Releases in the Github repository. The final version of the sample code can be found
at codeberg.org/astrid/j4examplecode. Each chapter has its own branch. At the beginning of each
chapter it says:

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t0...t1

You can find the names of the branches here. t0 is the branch of the previous chapter and t1 is the branch
that is worked on in this chapter. The link https://codeberg.org/astrid/j4examplecode/compare/t0...t1
shows you the current changes in the tab Changed Files.

How do I get updates? You can always find the latest content on my website1 .

Is the learning material up to date? Books on programming are often out of date shortly after their
publication. Since I have published this book as a self-publisher, it is possible for me to update it at
short notice if necessary. Whenever something significant changes, I will revise the book and publish a
new version.

How can I get help reading the book? The sample files are in a Github Repository2 . Feel free to open
an issue there. This way you get help. Or you offer help yourself. Supporting each other helps you and
others. Please don’t expect a direct answer from me, because I don’t always have the time. Would you
like some feedback, please ask in the Joomla Community, for example in the Joomla Forum3 or at
Joomla Stack Exchange4 .

How and where do I report an error? If you find an error in the code, please report it via Github Issue5
in the repository of the sample files. You found a bug in the text of the book? Please open an issue in
the repository with the contents of my blog6 or write me an email. Contact information can be found
on the website astrid-guenther.de. I am very grateful for your help!

How do you read the code examples in the book? If a file is new, it is printed in full. If a file has
changed, I just printed the change. I have marked lines that have been added with a plus sign. Deleted
lines are preceded by a minus sign.

1
blog.astrid-guenther.de/en/der-weg-zu-joomlae4-erweiterungen
2
codeberg.org/astrid/j4examplecode
3
forum.joomla.org/
4
joomla.stackexchange.com
5
codeberg.org/astrid/j4examplecode/issues
6
github.com/astridx/meinblog/issues

Page 2 January 2023


1. Intro Joomla 4 - Developing Extensions

1.2.1. Who is this book for?

You don’t know Joomla yet? If you are learning web development and have a basic understanding of
CSS and HTML, this book will give you the essentials you need to program Joomla 4 extensions. If you
feel unsure and think your Joomla knowledge is incomplete, fill in that gap before continuing with the
book. In the book you will find hints and links to basic knowledge.

If you used Joomla extensively before, the new Joomla 4 era seems overwhelming. The basic knowl-
edge hasn’t changed, it’s still Joomla and HTML — so this book will help you make the switch.

If you use any other content management system, you are familiar with the various aspects of web
development. After learning the basics with this book, you will quickly find your way around Joomla
4.

If you work in design, user interaction, usability or user experience, don’t hesitate to pick up this
book. If you are familiar with HTML and CSS, this will be beneficial. After going through some Joomla
basics, you will understand the contents of this book. Nowadays, UI and UX are getting closer to the
implementation details. It benefits you to know how things work in the code.

1.3. The author Astrid Günther

Interested in who I am? I am happy about that! You can find information about me or information
about a working relationship on my website astrid-guenther.de.

January 2023 Page 3


Joomla 4 - Developing Extensions 2. Preface

2. Preface

If you are new to Joomla, please read Absolute basics of how a component works1 .
This tutorial is intended for Joomla 4. For information on creating a component for Joomla 3, see
Developing a Model View Controller Component / 3.x2 .
You need Joomla 4.x for this tutorial. You can find Joomla 4 on GitHub3 on the Developer Website4 or
create a free website at launch.joomla.org.

2.1. Aim of this tutorial?

This tutorial does not create a practical example. I have intentionally kept everything general. My main
goal is to show you how Joomla works - and to help you understand it better yourself. In the end, you
replace the name “foo” in all files with the name of your component and extend it with your special
requirements. If you like, you can use the script duplicate.sh5 for this.
Therefore, this tutorial is primarily intended for programmers who want to create a new com-
ponent and do not know Joomla yet. The tutorial is also a help for programmers of a Joomla
3 component, if they extend their component for Joomla 4. For example, if you are working on
validating your Joomla 3 component, you will find what you need in the chapters on server-side
and client-side validation - no more and no less.

2.2. The structure of this tutorial

Each chapter builds on the previous builds. However, if you are interested in a specific topic, feel free
to look at a separate chapter. Be aware, however, that elements integrated in a previous chapter may
be necessary.
Why this structure? There are many examples of components in the standard Joomla. For example
1
docs.joomla.org/Absolute_Basics_of_How_a_Component_Functions
2
docs.joomla.org/J3.x:Developing_an_MVC_Component
3
github.com/joomla/joomla-cms
4
developer.joomla.org/nightly-builds.html
5
github.com/astridx/boilerplate/blob/t43/duplicate.sh

Page 5 January 2023


Joomla 4 - Developing Extensions 2. Preface

• com_content
• com_banner
• com_tags or
• com_contact

In each component you see implementation details in context. Each is complex and finding and
separating certain elements, such as page numbering or custom fields, is a hassle. This tutorial focuses
on one detail per chapter.

You create a component for Joomla 4, reusing the many existing implementations in Joomla. You
are not reinventing the wheel. Joomla offers a whole range of standard functions.

If you want to get started immediately, scroll to The first view in the backend. Below you will find some
things about Joomla 4 that you do not necessarily need for editing. However, some of them are good
to know.

2.3. Basics

2.3.1. Joomla 4 offers five types of extensions

• Components: A component fills the main content of the site. It usually uses data that is stored in
the database.
• Modules: A module is an add-on to the site that extends the functionality. It has a secondary part
on the web page and is displayed at different positions. It is selectable on which active menu
elements it is displayed. Modules are light and flexible extensions. They are used for small parts
of the page that are less complex and are displayed across different components.
• Plugins: A plugin processes the output generated by the system. It is not normally called as a
separate part of the site. It takes data from other sources and manipulates it before output. A
plugin works in the background.
• Languages: The most basic extensions are languages. Essentially, language package files consist
of key/value pairs that allow the translation of static text strings in the Joomla source code.
• Templates: A template defines the design of your Joomla website.

2.3.2. Joomla 4 consists of five different applications

• Installation (used to install Joomla and must be deleted after installation);


• Administrator (backend - to manage content);
• Website (frontend - used to display content);

Page 6 January 2023


2. Preface Joomla 4 - Developing Extensions

• CLI (used to access Joomla from the command line and for cron jobs);
• API (Web Services - used to create APIs for machine-accessible content);

2.4. Basic knowledge

2.4.1. Development environment

This text is about the Joomla code. It is not about the latest tools for developers. However, a few things
are essential.

2.4.1.1. Test environment

You want to program an extension for Joomla and therefore need an environment in which Joomla is
installed. In my opinion, a XAMPP server package6 on a local workstation is an ideal prerequisite for
developing new extensions. The direct access to the files of Joomla in the local file system facilitates
the handling.

2.4.1.2. Code editors or IDE

A good editor is also essential. This should be one you feel comfortable with. Wikipedia maintains a list
of editors.7 .
More convenience is offered by an integrated development environment IDE. By convenience I mean
functions like

• versioning: integration of GIT8


• Code completion: Complement user input in a meaningful way
• Syntax highlighting: colour highlighting of variables, class names or statements.
• Coding standards: Observance of rules
• Debugging: Finding errors

You can also get an overview of IDEs from Wikipedia using a List of IDEs9 .
In the Joomla community, the IDE PHPStorm10 , which is subject to a fee, is popular. Users of Visual
Studio Code11 are becoming increasingly common. Also worth mentioning are NetBeans and Eclipse.
6
www.apachefriends.org/index.html
7
en.wikipedia.org/wiki/List_of_text_editors
8
wikipedia.org/wiki/Git
9
en.wikipedia.org/wiki/Comparison_of_integrated_development_environments
10
www.jetbrains.com/phpstorm/
11
code.visualstudio.com/

January 2023 Page 7


Joomla 4 - Developing Extensions 2. Preface

Are you looking for instructions on how to set up the development environment? Joomla with Vi-
sual Studio Code can be found in the Joomla Documentation12 . For PHPStorm, Jetbrains provides a
description.

If you like, you can read my first steps with Visual Studio Code at blog.astrid-guenther.de/ubuntu-
vscode-docker-lamp.

2.4.2. Joomla 3 and Joomla 4 in comparison

2.4.2.1. New in Joomla 4

2.4.2.1.1. Frontend and Backend accessible and with Bootstrap 5 Joomla 4 integrates Bootstrap
5 into the Joomla core. Thereby, the included templates are accessible and correspond to level AA of
WCAG 2.1. WCAG 2.1 complements WCAG 2.0 and is the web standard for digital accessibility, which is
mandatory for public bodies in the European Union. As an extension developer, it is not mandatory
that you use Bootstrap 5. But technically it should comply with the latest standards. It would be a
shame not to use the good template that Joomla offers.

2.4.2.1.2. Optimised Web Assets With a single call, Web Assets allow developers to load multiple
Javascript and CSS files in a specified order. For example, if an extension developer uses styles that
depend on Font Awesome being loaded first and they know that Joomla 4 uses the icon font set, Web
Assets come into play. Web Assets are described in several places in this tutorial.

Use the Web Asset Manager when developing for Joomla 4. All calls to HTMLHelper::_('
stylesheet or script ...) work, but these assets are appended after the Web Asset Man-
ager assets. This results in overriding styles that are set in the template. Thus, a user does
not have the possibility to manipulate by means of a user.css. See in this context (Issue
35706)https://github.com/joomla/joomla-cms/issues/35706.

2.4.2.1.3. Web Services enable automated data exchange Joomla 4 web services make content
accessible to other websites or mobile applications. What is a web service? Different definitions cause
confusion. The SOAP standards are referred to as web services. Others know them under the term
REST API. The W3C generally defines a web service as an interface for automated communication via
computer networks. The API integration in Joomla 4 implements such an interface in the core of the
CMS with the help of REST. In the example we are building in this text, we also support the Joomla
API.
12
docs.joomla.org/Visual_Studio_Code

Page 8 January 2023


2. Preface Joomla 4 - Developing Extensions

2.4.2.1.4. Workflow With the new Workflow component, it is possible to link website content to a
workflow. Thirt Party extensions can also offer a workflow with the core extension. This function is not
yet included here in the book.

2.4.2.1.5. Many other changes and improvements Joomla 4 includes new security features such
as support for prepared statements for database systems. This prevents SQL injection because the
database checks the validity of parameters. In addition, the code base has been restructured. The code
was thoroughly cleaned up, obsolete functions removed and PHP namespaces introduced.

2.4.2.2. Backwards compatibility with Joomla 3

This text is primarily written for developers who are starting a new extension. Nevertheless, issues re-
lated to compatibility with Joomla 3 are of interest. A page in the Joomla documentation13 summarises
the important points.

2.4.3. Never change the core files

The purpose of Joomla extensions is to have a system that can be extended. It is possible that your
code and Joomla Core code can be provided with new functions independently of each other.

If make changes to Joomla itself, these will be overwritten with the next update.

You have the feeling that your function can only be implemented with a core hack? Your feeling is
wrong! There is always a solution that leaves the system files untouched.

2.4.4. Learn from the core files

That you should not change the system files does not mean that you do not even look at them. Quite
the opposite! By reading you will come across lots of code that is not documented anywhere. If you
are not sure how to best implement a function, just rummage around in the Joomla code. The solution
is usually be found in the heart of Joomla.

2.4.5. The autoload_psr4.php file

During the installation, entries are made in /administrator/ cache/autoload_psr4.php. This


is new in Joomla 4. If you encounter strange problems, delete this file. It will be recreated the next
time you load. Sometimes this solves a problem.
13
docs.joomla.org/Potential_backward_compatibility_issues_in_Joomla_4

January 2023 Page 9


Joomla 4 - Developing Extensions 2. Preface

The following text was added to the README on Github using the PR 28436a : “Joomla cre-
ates a cache of the namespaces of its extensions in JOOMLA_ROOT/administrator/ cache/
autoload_psr4.php. If extensions are created, deleted or removed in git then this file needs to
be recreated. You can simply delete the file and it will be regenerated on the next call to Joomla.”
a
github.com/joomla/joomla-cms/pull/28436

2.4.6. Namespace

Note the namespace entry at the top of most PHP files.

Namespace FooNamespace\ Component\Foos\Administrator\View\Foos;

and as a tag in the manifest file

<Namespace>FooNamespace\ Component\Foos</ Namespace>.

Remember to include the path="src" parameter if you put the namespace files in the src sub-
directory. This is common in Joomla and the sample extensions created in this tutorial also use this
directory[github.com/astridx/boilerplate/blob/62a970704ee2899addd3922e88c918b7f6af72a2/
src/administrator/components/com_foos/foos.xml#L12].

Why use namespaces? All PHP classes are thus organised in a defined structure and automatically
loaded via the Classloader. Thereby ContentModelArticles becomes Joomla\Component\
Content\ Administrator\Model\ArticlesModel.

JLoader can process the namespaces automatically and distinguishes between front-end and back-
end classes.
Files with namespaces can be found in the directory /srca
a
github.com/joomla/joomla-cms/pull/27687

2.4.7. Capitalisation of folder and file names

You will notice that some of the Joomla 4 folder and file names start with upper case letters and others
with lower case letters. At first glance, this seems unstructured. At second glance it makes sense.

The folders in upper case contain PHP classes with namespace. Those in lower case contain XML files,
template files. There are a few lower case folders that contain PHP files. These are necessary to ensure
compatibility with Joomla 3. Often these are helper files.

For more information, see: github.com/joomla/joomla-cms/issues/22990.

Page 10 January 2023


2. Preface Joomla 4 - Developing Extensions

2.4.8. Meaningful class names

The component MVC classes have more meaningful names in Joomla 4. For example, the
controllers now have controller as a suffix for their class name. Thus, FooNamespace\Component
\Foos\ Administrator\Controller\Foos becomes FooNamespace\Component\Foos\
Administrator\Controller\ FoosController.

Additionally, the default controller, which in Joomla 3 is just called Controller, gets the name
DisplayController to better reflect what the class does. See: https://github.com/joomla/joomla-
cms/pull/17624

2.4.9. index.html?

Do you need an empty file index.html in each folder of your component? The index.html is no
longer needed, as that is directory listings not allowed in the default configuration14 . If you are further
interested read the discussion on the topic in the Google Group15 .

2.4.10. Technical requirements

Do you know how those responsible at Joomla decide which functions are supported and what is
not pursued further? That’s what the statistics plugin16 is for. Thanks to the users who activate this
extension, important information flows into the development.

2.4.11. Why is a blank line inserted at the end of a source code file in Joomla files?

There are several reasons why a blank line at the end of a file is included as a requirement in the Joomla
Coding Standards:

• Apart from the fact that it is a nicer cursor position when you go to the end of a file in a text editor,
a line break at the end of the file allows you to easily check that the file has not been truncated.
• When you paste something at the end of a file, the difference display in Git shows that you’ve
changed the last line, when the only thing you’ve actually pasted is a line break. This is confusing.
• Today it doesn’t matter, but: many older tools in the programming field misbehave if the last
line of data in a file is not terminated with a newline or a carriage return/newline combination.
14
github.com/joomla/joomla-cms/pull/4171
15
groups.google.com/forum/#!topic/joomla-dev-cms/en1G7QoUW2s
16
developer.joomla.org/about/stats.html

January 2023 Page 11


Joomla 4 - Developing Extensions 2. Preface

2.4.12. PHP

2.4.12.1. Why should you omit the closing tag of PHP sections at the end of a file?

The closing tag of a PHP block at the end of a file is optional, and in some cases it is helpful to omit it.
By omitting the closing tag, you can avoid accidentally inserting spaces or line breaks at the end of the
file. For further explanation, see php.net[php.net/basic-syntax.instruction-separation].

2.4.12.2. PHP operators for equality (== two equal signs) and identity (=== three equal signs)

The comparison operator[php.net/manual/en/language.operators.comparison.php #lan-


guage.operators.comparison] == compares between two different types if they are different,
while the === operator performs a type-safe comparison. This means that it returns true only if both
operands have the same type and the same value. Examples:

1 === 1: true 1 == 1: true 1 === "1": false // 1 is an integer, “1” is a string 1 == "1": true // “1” is
converted to an integer, which is 1 "foo"=== "foo": true // both operands are strings and have the
same value

Note: Two instances of the same class with equivalent elements will be evaluated by the operator with
three equal signs === with false. Example:

1 $a = new stdClass();
2 $a->foo = "bar";
3 $b = clone $a;
4 var_dump($a === $b); // bool(false)

In Joomla, we use type-safe comparison whenever possible because it is more accurate.

2.4.12.3. Single quotes and double quotes

In Joomla, we use single quotes. Using single quotes is more performant, usually more readable and
more straightforward when used with associative arrays. PHP does not need any additional processing
to interpret what is inside the single quote. If you use double quotes, PHP must check to see if there
are any variables in the string.

More information about this and the explanation of two other ways to use strings in PHP can be
found on the website php.neta .
a
php.net/manual/en/language.types.string.php

Page 12 January 2023


2. Preface Joomla 4 - Developing Extensions

2.4.12.3.1. Single quotes The simplest way to specify a string is to enclose it in single quotes. Single
quotes are generally faster, and anything enclosed in quotes is treated as a single string. Example:

1 echo 'Start with a single string';


2 echo 'String with \' apostrophe';
3 echo 'String with a php variable' . $name;

2.4.12.3.2. Double quotes Use double quotes in PHP to avoid using a period when separating. Use
curly braces {} in strings to enclose variables if you don’t like to use the concatenation operator (.).
Example:

1 var $name = "Peter";


2 echo "Hello {$name}"

2.4.12.4. Alternative syntax for control structures

PHP offers an additional notation for control structures. This is especially handy when outputting
larger blocks of HTML directly - without using echo. Use them in template files. This way they remain
clear.

Use

1 <?php foreach ($this->items as $i => $item) : ?>


2 <?php echo $item->name; ?>
3 </br>
4 <?php endforeach; ?>

instead of

1 foreach ($this->items as $i => $item) {


2 echo $item->name;
3 echo '</br>';
4 }

In this way, a single line is self-contained and the HTML code is still clearly structured.

2.4.13. Database table prefix

As an extension developer, you ideally develop your extension so that the database prefix is variable.
For this purpose, one uses the string #__. The string #__ is replaced with the correct prefix at runtime
by Joomla.

January 2023 Page 13


Joomla 4 - Developing Extensions 2. Preface

2.4.14. JavaScript, CSS and image files?

Where do you best store JavaScript, CSS and image files? Store these data in the directory media in the
Joomla root directory. This way it is possible to overwrite them. This is particularly advantageous for
CSS files to make the design of the whole Joomla Website consistent. The Best Practice Guidelines17
also recommend this.
Examples: For this tutorial extension I will later use media/com_foos/js/ for the JavaScript files
of the component. The CSS files of the module mod_articles_news can be found in the directory
media/mod_articles_news/css/. And the images for the plugin plg_content_vote are in
the folder media/plg_content_vote/images/.

2.4.15. Fontawesome Icons

You want to use icons but don’t want to add your own library. Use the free icons from fontawe-
some.com/icons in the frontend and backend. At least if you use the standard templates Cassiopeia
and Atum, this will work. If your template does not support FontAwesome, you can load the icons
yourself via the WebassetManager. In Joomla Fontawesome is delivered with the template. Marking
them as dependency18 is sufficient.

Attention: In Joomla Core files, you cannot simply copy them, because Joomla add the text icon-
in front of the icon name. This is then converted via the file build/media_source/ system/
scss/_icomoon.scss for Fontawesome. In this way, only the icons included in the previously
mentioned file will work. Why does Joomla complicate the selection of Font Awesome icons? The
reason for this is as follows: Extensions that were programmed for Joomla 3 can still be used.

The HTML code

1 <i class="fas fa-align-left"></i>

displays the left-aligned character, for example.

2.4.16. Use images

A new JLayout19 as of Joomla 4.0.5 allows developers to output HTML image tags easily:

So instead of writing something like this:


17
docs.joomla.org/Development_Best_Practices
18
templates/cassiopeia/joomla.asset.json
19
gist.github.com/dgrammatiko/a20236039586a2fbc5c77caadffc3de8

Page 14 January 2023


2. Preface Joomla 4 - Developing Extensions

1
2 <?php
3 echo '<img src="' . $imageURL .'" alt="' . htmlspecialchars($imageAlt,
ENT_COMPAT, 'UTF-8') . '">';
4 ?>

The recommended way is to use the JLayout:

1 <?php
2 echo LayoutHelper::render('joomla.html.image', ['src' => imageURL, 'alt
' => $imageAlt]);
3 ?>

Advantages:

• The URL and the alt attribute are escaped correctly.


• The developer does not have to worry about the “#” at the end of the URL.
• The image tag gets a loading="lazy" attribute if the image has the width and height at-
tributes defined.
• The alt attribute is ignored if the value passed is false.
• All other attributes are rendered correctly, pass them as in the array (for example 'class'=> '
my-class').

2.4.17. Dates

An error occurred in one of my Joomla extensions. Dates and times were not displayed correctly. The
time zone was obviously the problem. The solution seemed simple at first glance. I had worked with
dates and the class DateTime in PHP 20 in the past and had experience with time zones. In Joomla,
however, there are special features.

Let’s look at my concrete problem. A user who lives in the time zone “Australia/Adelaide” (UTC/GMT
+10:30 hours) fills out a form in the summer which contains a field in which a date is stored. The
timezone “Australia/Adelaide” has a difference to the timezone “UTC” of +10:30 hours in summer, in
wintertime the difference is +9:30 hours.

20
php.net/manual/en/class.datetime.php

January 2023 Page 15


Joomla 4 - Developing Extensions 2. Preface

Figure 2.1.: Joomla 4 | Set time zone at user

The server is located in Johannesburg, thus in South Africa. The time zone of the server is set to
“Africa/Johannesburg” in the global configuration. The time zone “Africa/Johannesburg” has a dif-
ference to the time zone “UTC” of +2:00 hours in summer time, in winter time the difference is +1:00
hours.

Figure 2.2.: Joomla 4 | Set time zone of the server in the global configuration

My extension is a contest. The date is 4.10.2022. The time is 00:00:01. That is exactly when the competi-
tion ends. It is important that the game is made inactive at the same time all over the world. This is

Page 16 January 2023


2. Preface Joomla 4 - Developing Extensions

different from, for example, an Advent Calendar. With the Advent calendar, it may be intentional that
something happens at a certain time in each time zone. The first door opens in Australia, in Africa and
in Europe when it is locally the 1.12. and not at the same time.

I use the field of type Calendar 21 in the form under Joomla.

1 <field
2 name="advent_publish_up"
3 type="calendar"
4 label="COM_AGADVENTS_FIELD_PUBLISH_UP_LABEL"
5 translateformat="true"
6 showtime="true"
7 size="22"
8 filter="user_utc"
9 />

To my surprise, I notice during my first tests that instead of 2022-10-04 00:00:01 the string
2022-10-03 13:30:01 is stored in the database. With a little research, I realise that the calendar
field converts the date to the UTC time zone and stores it in this form. The information about the time
zone itself is not stored in the database. The latter is not necessary if it is ensured that one and the
same time zone is always used. In the case of Joomla, this is UTC.

In the code for the calendar field of the file /libraries/src/Form/Field/CalendarField.php


this process can be read in the function filter:

1 ...
2 public function filter($value, $group = null, Registry $input = null)
3 {
4 // Make sure there is a valid SimpleXMLElement.
5 if (!($this->element instanceof \SimpleXMLElement)) {
6 throw new \UnexpectedValueException(sprintf( %s::filter `
element ` is not an instance of S i m p l e X M L E l e m e n t , \
get_class($this)));
7 }
8
9 if ((int) $value <= 0) {
10 return ;
11 }
12
13 if ($this->filterFormat) {
14 $value = DateTime::createFromFormat($this->filterFormat, $value
)->format( Y -m-d H:i: s );
15 }
16
17 $app = Factory::getApplication();
18
19 // Get the field filter type.

21
docs.joomla.org/Calendar_form_field_type

January 2023 Page 17


Joomla 4 - Developing Extensions 2. Preface

20 $filter = (string) $this->element[ filter ];


21
22 $return = $value;
23
24 switch (strtoupper($filter)) {
25 // Convert a date to UTC based on the server timezone offset.
26 case SERVER_UTC :
27 // Return an SQL formatted datetime string in UTC.
28 $return = Factory::getDate($value, $app->get( offset ))
->toSql();
29 break;
30
31 // Convert a date to UTC based on the user timezone offset.
32 case USER_UTC :
33 // Get the user timezone setting defaulting to the server
timezone setting.
34 $offset = $app->getIdentity()->getParam( timezone ,
$app->get( offset ));
35
36 // Return an SQL formatted datetime string in UTC.
37 $return = Factory::getDate($value, $offset)->toSql();
38 break;
39 }
40
41 return $return;
42 }
43 ...

The variable $value contains the value entered by the user, in this case 2022-10-04 00:00:01. This
is converted to the time zone UTC and then stored in the variable $return. The converted value is
passed to the database for saving. This way you can always be sure that the date stored in the database
for the time zone UTC is correct.
In Joomla, SERVER_UTC and USER_UTC offer the possibility of outputting the date either in the time
zone with which the web server is configured or in the time zone set by the user. In short, the constants
indicate whether the value of the variable $value is in the time zone stored at the user or the time
zone of the webserver, which is configurable in the global configuration.

Why is the date converted to the time zone UTC for saving in the database? Actually, it doesn’t
matter which time zone you choose. It makes sense to define one. That way you always have a fixed
starting point. Otherwise, you would have to determine the conversion factor or offset for each
time zone combination. Joomla’s standard behaviour ensures that the date stored in the database
is correct for the UTC time zone and that only the difference/offset to this time zone is needed for
the conversion. The default time zone can be configured in the file configuration.php. The
variable is called $offset. The default is public $offset = 'UTC';. When displaying the
time in the frontend of the website, one now only has to calculate the difference between UTC

Page 18 January 2023


2. Preface Joomla 4 - Developing Extensions

and the desired time zone.

2.5. The class Joomla

If you look at the class Joomla\CMS\Date in the directory /libraries/src/Date/Date.php, you


will notice that the constructor public function __construct($date = 'now', $tz = null
) allows two parameters: the date and the timezone.

1 ...
2 public function __construct($date = now , $tz = null)
3 {
4 // Create the base GMT and server time zone objects.
5 if (empty(self::$gmt) || empty(self::$stz)) {
6 // @TODO: This code block stays here only for B/C, can be
removed in 5.0
7 self::$gmt = new \DateTimeZone( GMT );
8 self::$stz = new \DateTimeZone(@date_default_timezone_get());
9 }
10
11 // If the time zone object is not set, attempt to build it.
12 if (!($tz instanceof \DateTimeZone)) {
13 if (\is_string($tz)) {
14 $tz = new \DateTimeZone($tz);
15 } else {
16 $tz = new \DateTimeZone( UTC );
17 }
18 }
19
20 // Backup active time zone
21 $activeTZ = date_default_timezone_get();
22
23 // Force UTC timezone for correct time handling
24 date_default_timezone_set( UTC );
25
26 // If the date is numeric assume a unix timestamp and convert it.
27 $date = is_numeric($date) ? date( c , $date) : $date;
28
29 // Call the DateTime constructor.
30 parent::__construct($date, $tz);
31
32 // Restore previously active timezone
33 date_default_timezone_set($activeTZ);
34
35 // Set the timezone object for access later.
36 $this->tz = $tz;
37 }
38 ...

January 2023 Page 19


Joomla 4 - Developing Extensions 2. Preface

The class Joomla\CMS\Date is used, among other things, in the function getDate of the file /
libraries/src/Factory.php, which helps Joomla extension programmers to always output the
date with the appropriate offset, i.e. in the correct time zone. The code of the function getDate() is
printed below for the sake of completeness:

1 ...
2
3 public static function getDate($time = now , $tzOffset = null)
4 {
5 static $classname;
6 static $mainLocale;
7
8 $language = self::getLanguage();
9 $locale = $language->getTag();
10
11 if (!isset($classname) || $locale != $mainLocale) {
12 // Store the locale for future reference
13 $mainLocale = $locale;
14
15 if ($mainLocale !== false) {
16 $classname = str_replace( - , _ , $mainLocale) .
Date ;
17
18 if (!class_exists($classname)) {
19 // The class does not exist, default to Date
20 $classname = Joomla \\CMS\\Date\\ D a t e ;
21 }
22 } else {
23 // No tag, so default to Date
24 $classname = Joomla \\CMS\\Date\\ D a t e ;
25 }
26 }
27
28 $key = $time . - . ($tzOffset instanceof \DateTimeZone ?
$tzOffset->getName() : (string) $tzOffset);
29
30 if (!isset(self::$dates[$classname][$key])) {
31 self::$dates[$classname][$key] = new $classname($time,
$tzOffset);
32 }
33
34 $date = clone self::$dates[$classname][$key];
35
36 return $date;
37 }
38 ...

How do you display the date in the correct time zone in your Joomla extension in the frontend? You
are on the safe side if you use the functions provided by Joomla. Let’s take a look at the interaction of

Page 20 January 2023


2. Preface Joomla 4 - Developing Extensions

Factory::getDate() and the class Joomla\CMS\Date in Joomla as an example below.

2.6. Display in the frontend

2.6.1. The value in the database

Let’s start very simply. The following code displays the string that is stored for the date in the
database.

1 echo $this->item->advent_publish_up;

The Output is:

1 2022-10-03 13:30:01

I have already explained why the time is displayed in the UTC time zone.

2.6.2. The time in the time zone of the logged-in user

If you want to display the date in the time zone that is stored with the user, the following code shows a
possibility.

1 ...
2 $date = Factory::getDate($this->item->advent_publish_up, 'UTC');
3 $user = Factory::getApplication()->getIdentity();
4 $date->setTimezone($user->getTimezone());
5 echo $this->value = $date->format('Y-m-d H:i:s', true, false);
6 echo "<br><pre>";
7 print_r($date);
8 echo "</pre>";

If a user is logged in with the time zone set to Australia/Adelaide, the following text appears in
the frontend:

1 2022-10-04 00:00:01
2
3 Joomla\CMS\Date\Date Object
4 (
5 [tz:protected] => DateTimeZone Object
6 (
7 [timezone_type] => 3
8 [timezone] => Australia/Adelaide
9 )
10
11 [date] => 2022-10-04 00:00:01.000000

January 2023 Page 21


Joomla 4 - Developing Extensions 2. Preface

12 [timezone_type] => 3
13 [timezone] => Australia/Adelaide
14 )

If no user is logged in, the time zone of the server is fallback. In our example, the following text
appears:

1 2022-10-03 15:30:01
2
3 Joomla\CMS\Date\Date Object
4 (
5 [tz:protected] => DateTimeZone Object
6 (
7 [timezone_type] => 3
8 [timezone] => Africa/Johannesburg
9 )
10
11 [date] => 2022-10-03 15:30:01.000000
12 [timezone_type] => 3
13 [timezone] => Africa/Johannesburg
14 )

The following code displays the date in the time zone stored for the web server in the global configura-
tion.

1 $date = Factory::getDate($this->item->advent_publish_up, 'UTC');


2 $date->setTimezone(new \DateTimeZone(Factory::getApplication()->get('
offset')));
3 echo $this->value = $date->format('Y-m-d H:i:s', true, false);
4 echo "<br><pre>";
5 print_r($date);
6 echo "</pre>";

The Output is:

1 2022-10-03 15:30:01
2
3 Joomla\CMS\Date\Date Object
4 (
5 [tz:protected] => DateTimeZone Object
6 (
7 [timezone_type] => 3
8 [timezone] => Africa/Johannesburg
9 )
10
11 [date] => 2022-10-03 15:30:01.000000
12 [timezone_type] => 3
13 [timezone] => Africa/Johannesburg
14 )

Page 22 January 2023


2. Preface Joomla 4 - Developing Extensions

The following code outputs the date immediately in the standard time zone UTC.

1 $date = Factory::getDate($this->item->publish_up, 'UTC');


2 echo $this->value = $date->format('Y-m-d H:i:s', true, false);
3 echo "<br><pre>";
4 print_r($date);
5 echo "</pre>";

The Output is:

1 2022-10-03 13:30:01
2
3 Joomla\CMS\Date\Date Object
4 (
5 [tz:protected] => DateTimeZone Object
6 (
7 [timezone_type] => 3
8 [timezone] => UTC
9 )
10
11 [date] => 2022-10-03 13:30:01.000000
12 [timezone_type] => 3
13 [timezone] => UTC
14 )

Conclusion: Depending on the use case, i.e. whether it is a competition where everything should
happen at the same time or an Advent calendar where the actual time is relevant, the date can be
programmed in the Joomla extension.

January 2023 Page 23


Joomla 4 - Developing Extensions

Part I.

Component

Page 25 January 2023


Joomla 4 - Developing Extensions 3. The first view in the backend

3. The first view in the backend

We’ll start with the basics. For this we create the View in the administration area rudimentary. At the
end of this text you know how to insert a menu item in the menu of the administration area. Via the
menu item you open the view to your component. Don’t be disappointed: This view contains nothing
more than a short text. You have a good basis for the next steps.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t0...t1

3.1. Step by step

3.1.1. New files

3.1.1.1. administrator/components/com_foos/foos.xml

foos.xml tells Joomla how to install our component. Just like modules and plugins, components
have an XML installation file that informs Joomla about the extension to be installed. This file is called
a manifest and contains details such as

• the version number,


• all files and folders used by the component,
• details of the database,
• and component parameters.

Create a new file and name it foos.xml. This is the name of the extension without the prefix com_.
We will then go through each line and see what it does.

The first line is not specific to Joomla. It tells us that this is an XML file

1 <?xml version="1.0" encoding="utf-8" ?>

Then we tell Joomla that this is a component. And we want the upgrade installation method to be
used. So it is possible to use this package not only for installation but also for an upgrade

Page 27 January 2023


Joomla 4 - Developing Extensions 3. The first view in the backend

1 <extension type="component" method="upgrade">

Sometimes you will find a parameter with a version number in the <extension> tag of
the manifest. This is not used anywhere, so it is unnecessary. For more information, see
github.com/joomla/joomla-cms/pull/25820.

Then we define the name of the component. In this case COM_FOOS.

1 <name>COM_FOOS</name>

Read more about the name at github.com/joomla/joomla-cms/issues/26221.

The next lines are self-explanatory. Add your data.

1 <creationDate>[DATE]</creationDate>
2 <author>[AUTHOR]</author>
3 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
4 <authorUrl>[AUTHOR_URL]</authorUrl>
5 <copyright>[COPYRIGHT]</copyright>
6 <license>GNU General Public License version 2 or later;</license>

This is the first version of the component. We will give it the version number 1.0.0: <version
>1.0.0</version>. If we fix a small bug, the next number would be 1.0.1. If we introduce a new
feature, we choose 1.1.0. If we made major changes that alter implementations in earlier versions,
the next version would be 2.0.0. It is important that you use the three-part version numbering, as
this makes it easier to create updates later using semantic versioning.

Joomla follows semantic versioninga . I recommend this as well.


a
developer.joomla.org/news/586-joomla-development-strategy.html#version_numbering

In the description field we use a language string.

1 <description>COM_FOOS_ XML_DESCRIPTION</description>

At the moment, this has no effect. Later, this text will change based on the language files we introduce
in one of the next chapters. The description of the component will be shown during installation and
when you click the menu System and open the submenu Manage | Extensions.

Next we set the HTML tag for the namespace. In the preface I explained why we use namespaces. Now
we create it practically. How do you name your namespace?

• The first element of the namespace is your CompanyName. For this tutorial I have used
FooNamespace. It is used to distinguish the code from the code in other extensions. This makes

Page 28 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

it possible to use identical class names without conflicts. The namespace is also used to register
a service provider. A service provider is a PHP class that provides services.

• The second element is the type of extension: component, module, plugin, language or template.

• The third element is the name of the extension without the preceding com_, mod_, plg_ or tpl_,
in our case Foos.

1 <namespace>FooNamespace\Component\Foos</namespace>

With the script file you call code when your component is installed, uninstalled or updated.

1 <scriptfile>script.php </scriptfile>

Like Joomla itself, components have a frontend and an administration area. The folder
administrator/components/ com_foos contains all files used by the backend. You add
individual files with the tag “filename”. For a complete directory it is better to use the tag folder.
The files for the administration area of your component are all inside the tag administration. Here
is also a menu tag. This is the menu item that is displayed in the sidebar in the backend. We use the
language string COM_FOOS, which we will replace later with text from a language file.

1 <administration>
2 <!-- Menu entries -->
3 <menu view="foos">COM_FOOS</menu>
4 <submenu>
5 <menu link="option=com_foos">COM_FOOS</menu>
6 </submenu>
7 <files folder="administrator/components/com_foos">
8 <filename>foos.xml</filename>
9 <folder>services</folder>
10 <folder>src</folder>
11 <folder>tmpl</folder>
12 </files>
13 </administration>

We will look at the changelogurl and updateservers tags in more detail in the next chapter.

1<changelogurl>https://codeberg.org/astrid/j4examplecode/raw/branch/
tutorial/changelog.xml</changelogurl>
2 <updateservers>
3 <server type="extension" name="Foo Updates">https://codeberg.org/
astrid/j4examplecode/raw/branch/tutorial/foo_update.xml</server>
4 </updateservers>

Let’s move on to the dlid-tag.

1 <dlid prefix="dlid=" suffix="" />

January 2023 Page 29


Joomla 4 - Developing Extensions 3. The first view in the backend

You need this if you use the Download Key Manager. In general, this is only the case for commercial
extensions. You can find more information on Github at github.com/joomla/joomla-cms/pull/25553.

Finally, we close the </extension> tag. Here is the complete code:

administrator/components/com_foos/foos.xml

1
2 <?xml version="1.0" encoding="utf-8" ?>
3 <extension type="component" method="upgrade">
4 <name>COM_FOOS</name>
5 <creationDate>[DATE]</creationDate>
6 <author>[AUTHOR]</author>
7 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
8 <authorUrl>[AUTHOR_URL]</authorUrl>
9 <copyright>[COPYRIGHT]</copyright>
10 <license>GNU General Public License version 2 or later;</license>
11 <version>__BUMP_VERSION__</version>
12 <description>COM_FOOS_XML_DESCRIPTION</description>
13 <namespace path="src">FooNamespace\Component\Foos</namespace>
14 <scriptfile>script.php</scriptfile>
15 <!-- Back-end files -->
16 <administration>
17 <!-- Menu entries -->
18 <menu view="foos">COM_FOOS</menu>
19 <submenu>
20 <menu link="option=com_foos">COM_FOOS</menu>
21 </submenu>
22 <files folder="administrator/components/com_foos">
23 <filename>foos.xml</filename>
24 <folder>services</folder>
25 <folder>src</folder>
26 <folder>tmpl</folder>
27 </files>
28 </administration>
29 <changelogurl>https://codeberg.org/astrid/j4examplecode/raw/branch/
tutorial/changelog.xml</changelogurl>
30 <updateservers>
31 <server type="extension" name="Foo Updates">https://codeberg.
org/astrid/j4examplecode/raw/branch/tutorial/foo_update.xml
</server>
32 </updateservers>
33 <dlid prefix="dlid=" suffix="" />
34 </extension>

3.1.1.2. administrator/components/com_foos/script.php

With the installation script file you call code

Page 30 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

• when your component is installed,


• before your component is installed,
• when your component is uninstalled,
• before your component is uninstalled,
• or when your component is updated.

Create the file script.php with the following content:

administrator/components/com_foos/script.php

1
2 <?php
3 \defined('_JEXEC') or die;
4 use Joomla\CMS\Installer\InstallerAdapter;
5 use Joomla\CMS\Language\Text;
6 use Joomla\CMS\Log\Log;
7
8 class Com_FoosInstallerScript
9 {
10 private $minimumJoomlaVersion = '4.0';
11
12 private $minimumPHPVersion = JOOMLA_MINIMUM_PHP;
13
14 public function install($parent): bool
15 {
16 echo Text::_('COM_FOOS_INSTALLERSCRIPT_INSTALL');
17
18 return true;
19 }
20
21 public function uninstall($parent): bool
22 {
23 echo Text::_('COM_FOOS_INSTALLERSCRIPT_UNINSTALL');
24
25 return true;
26 }
27
28 public function update($parent): bool
29 {
30 echo Text::_('COM_FOOS_INSTALLERSCRIPT_UPDATE');
31
32 return true;
33 }
34
35 public function preflight($type, $parent): bool
36 {
37 if ($type !== 'uninstall') {
38 // Check for the minimum PHP version before continuing
39 if (!empty($this->minimumPHPVersion) && version_compare(
PHP_VERSION, $this->minimumPHPVersion, '<')) {

January 2023 Page 31


Joomla 4 - Developing Extensions 3. The first view in the backend

40 Log::add(
41 Text::sprintf('JLIB_INSTALLER_MINIMUM_PHP', $this->
minimumPHPVersion),
42 Log::WARNING,
43 'jerror'
44 );
45
46 return false;
47 }
48
49 // Check for the minimum Joomla version before continuing
50 if (!empty($this->minimumJoomlaVersion) && version_compare(
JVERSION, $this->minimumJoomlaVersion, '<')) {
51 Log::add(
52 Text::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA',
$this->minimumJoomlaVersion),
53 Log::WARNING,
54 'jerror'
55 );
56
57 return false;
58 }
59 }
60
61 echo Text::_('COM_FOOS_INSTALLERSCRIPT_PREFLIGHT');
62
63 return true;
64 }
65
66 public function postflight($type, $parent)
67 {
68 echo Text::_('COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT');
69
70 return true;
71 }
72 }

The install function, as the name suggests, is called when the component is installed. At the moment,
text is output. It is possible to install sample data.

uninstall is called when someone uninstalls the component. At the moment, only text is displayed.

The update function update is called whenever you update the component. Have there been changes
to save locations in the extension? In that case, you might want to delete files? Then the update
method could look like this:

1 public function update($parent)


2 {
3 $this->deleteFiles[] = '/administrator/language/en-GB/?.ini';
4 $this->deleteFiles[] = '/administrator/language/en-GB/?.sys.ini

Page 32 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

';
5
6 $this->removeFiles();
7
8 return true;
9 }

The preflight function is called before the component is installed, discover_installed, updated or
uninstalled. You can add code here to check the prerequisites like the PHP version or to check if another
extension is installed or not.
The postflight function is called after the component has been installed, discover_installed, updated
or uninstalled. This function is used to set default values for component parameters.

Note: In Joomla 3, only plugins called the Preflight method during the uninstall process and
Postflight was never used during uninstall. As of version 4.0, these two hooks are available during
the uninstall for all extension types.

Do you want to know exactly when which method is called? Then have a look at the
file /libraries/src/Installer/InstallerAdapter.php. The commands $this->
triggerManifestScript(''); will start the execution of the related method. For example,
the postflight function is triggered via $this->triggerManifestScript('postflight'
);. See Potential backward compatibility issues in Joomla 4a .
a
docs.joomla.org/Potential_backward_compatibility_issues_in_Joomla_4#CMS_Libraries

3.1.1.3. administrator/components/com_foos/ services/provider.php

provider.php is used to implement the component services. Via an interface, the component class
defines which services it provides. A dependency injection container or DI container is used for this.
To register, ComponentDispatcherFactory and MVCFactory are mandatory for each component.
Registering CategoryFactory is at this place optional, we need CategoryFactory when we inte-
grate categories later. Using provider.php it is possible to introduce new services without breaking
backwards compatibility (BC). If you are not familiar with the concept of DI Container but would like to
learn more, you can find explanations and some examples in the following links:

• joomla-framework/di1 .
• docs/why-dependency-injection.md2 .

More information about the implementation can be found on Github3 .


1
github.com/joomla-framework/di
2
github.com/joomla-framework/di/blob/master/docs/why-dependency-injection.md
3
github.com/joomla/joomla-cms/pull/20217

January 2023 Page 33


Joomla 4 - Developing Extensions 3. The first view in the backend

You often see the word “factory” in Joomla. This is because Joomla uses the factory design pattern4 .
The factory method is a pattern where the interface to create an object is an abstract method of an
inheriting class. However, the concrete implementation of the creation of new objects does not take
place in the superclass, but in subclasses derived from it. The latter implement the said abstract
method. To program extensions for Joomla it is not mandatory that you know the design patterns.
However, it can be worthwhile to think outside the box. In software engineering, a design pattern5 is a
general, reusable solution to a common problem. Someone else had the same problem and found a
solution. We don’t have to solve the same problem, but can build on it.

administrator/components/com_foos/services/provider.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
7 use Joomla\CMS\Extension\ComponentInterface;
8 use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
9 use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
10 use Joomla\CMS\Extension\Service\Provider\MVCFactory;
11 use Joomla\CMS\HTML\Registry;
12 use Joomla\DI\Container;
13 use Joomla\DI\ServiceProviderInterface;
14 use FooNamespace\Component\Foos\Administrator\Extension\FoosComponent;
15
16 return new class implements ServiceProviderInterface
17 {
18 public function register(Container $container)
19 {
20 $container->registerServiceProvider(new CategoryFactory('\\
FooNamespace\\Component\\Foos'));
21 $container->registerServiceProvider(new MVCFactory('\\
FooNamespace\\Component\\Foos'));
22 $container->registerServiceProvider(new
ComponentDispatcherFactory('\\FooNamespace\\Component\\Foos'
));
23
24 $container->set(
25 ComponentInterface::class,
26 function (Container $container) {
27 $component = new FoosComponent($container->get(
ComponentDispatcherFactoryInterface::class));
28
29 $component->setRegistry($container->get(Registry::class
));

4
en.wikipedia.org/wiki/factory_method_pattern
5
en.wikipedia.org/wiki/software_design_pattern

Page 34 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

30
31 return $component;
32 }
33 );
34 }
35 };

3.1.1.4. administrator/components/com_foos/ src/Controller/DisplayController.php

The file DisplayController.php is the entry point for the Model View Controller part in the admin-
istration area of the Foo component. Name the class DisplayController. Joomla expects it like this.
Extend BaseController to use many things out-of-the-box.

The main task of this controller is to prepare the display. Therefore the default controller is called
DisplayController. It calls the method display() of the parent class BaseController in the names-
pace Joomla\CMS\MVC\Controller - exactly this is the file libraries/src/MVC/Controller/
BaseController.php. In the Model-View-Controller model, controllers are often used to set up the
start environment.

Let’s create the DisplayController. As always, we first create the DocBlock. Here is an example of a
typical document block.

Figure 3.1.: An example of a typical docblock in Joomla

What does __BUMP_VERSION__ or __DEPLOY_VERSION__ stand for? Sometimes you see strange
text like this in a DocBlock. For example in PR 27712a . In Joomla, we put __DEPLOY_VERSION__
in place of the version number of a new method we create. Since we don’t know in which version
this new code will be accepted in Joomla, we can’t use a real version number. When the new code
is added to the core, this strange string is automatically replaced with the current version number.
In other systems __BUMP_VERSION__ is common. I use __BUMP_VERSION__ here as well.
a
github.com/joomla/joomla-cms/pull/27712/files

How to create DocBlocks for Joomla is explained in the Joomla coding standards at devel-
oper.joomla.org/ coding-standards/docblocks.html and the pull request github.com/joomla/joomla-

January 2023 Page 35


Joomla 4 - Developing Extensions 3. The first view in the backend

cms/ pull/31504.

A DocBlock is displayed before each class and before each function. All code contains DocBlock
comments, which make it easier for automated tools to generate documentation. In addition
it helps some IDEs to provide code completion. And sometimes the comment is helpful for
programmers. I don’t print the documentary blocks further here. In the code examples on Github,
the DocBlocks are still present.

After the DocBlock you add the namespace.

1 namespace FooNamespace\Component\Foos\Administrator\Controller;

You declare this with the corresponding keyword. Namespaces were introduced in Joomla 4. If this
concept is new to you, read the overview of namespaces at php.net6 . It is important that it is in the file
before any other code.

After the namespace we add

1 \defined('_JEXEC') or die; `

so that this PHP file is not directly callable.

Next, we import the namespace of the parent class BaseController with the keyword use to be able
to use this class.

1 use Joomla\CMS\MVC\Controller\BaseController;

Then we create the class for the controller. I already wrote that you should call this DisplayController
and extend the class BaseController. Then define the variable $default_view in which you set
the default view with foos. You choose foos as the view because the name of the component is foos
and for this reason you will also created the directory /administrator/components/ com_foos
/src/View/Foos. If nothing is defined, the Foos view with the default layout is used by default.
Setting this variable is not necessary. But I think it is always better to insert this line.

If you take a closer look at the URL while using a component in the administration area, you may notice
the view and layout variables. For example, the URL index.php ?option=com_foos &view=foos
&layout=default loads the foos view with the default layout default. Thus, the file components/
+ com_foos/tmpl/foos/ + default.php is called when you are in the frontend. If you are working in
the backend, administrator/components/ + com_foos/tmpl/foos/ + default.php is used.

The visibility is defined in PHP with public, private or protected. When to use which is

6
php.net/manual/en/language.namespaces.php

Page 36 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

explained in the PHP manuala .


a
php.net/manual/en/language.oop5.visibility.php

Create everything as it is intended in Joomla. This will bring you advantages. For many frequently
used functions, you do not reinvent the wheel. You can see this in practice with the display method.
You do not implement any action in your code. All the work is done by parent::display().

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Controller;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\Controller\BaseController;
9
10 class DisplayController extends BaseController
11 {
12 protected $default_view = 'foos';
13
14 public function display($cachable = false, $urlparams = [])
15 {
16 return parent::display();
17 }
18 }

3.1.1.5. administrator/components/com_foos/ src/Extension/FoosComponent.php

FoosComponent.php is the code for booting the extension. It is the first file that is called when
Joomla loads the component. Boot’ is the function to set up the environment of the extension, such
as registering new classes. For more information, see the pull request github.com/joomla/joomla-
cms/pull/20217. In the following we will expand the file FoosComponent.php.

administrator/components/com_foos/Extension/FoosComponent.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Extension;
5
6 defined('JPATH_PLATFORM') or die;
7
8 use Joomla\CMS\Categories\CategoryServiceInterface;
9 use Joomla\CMS\Categories\CategoryServiceTrait;
10 use Joomla\CMS\Extension\BootableExtensionInterface;
11 use Joomla\CMS\Extension\MVCComponent;
12 use Joomla\CMS\HTML\HTMLRegistryAwareTrait;

January 2023 Page 37


Joomla 4 - Developing Extensions 3. The first view in the backend

13 use FooNamespace\Component\Foos\Administrator\Service\HTML\
AdministratorService;
14 use Psr\Container\ContainerInterface;
15
16 class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface
17 {
18 use CategoryServiceTrait;
19 use HTMLRegistryAwareTrait;
20
21 public function boot(ContainerInterface $container)
22 {
23 $this->getRegistry()->register('foosadministrator', new
AdministratorService);
24 }
25 }

3.1.1.6. administrator/components/com_foos/ src/Service/HTML/AdministratorService.php

Although we are developing the code for a minimal component, some administrator files are needed.
The file AdministratorService.php will be used later to add functions like multilingualism or
main entries/featured. At the moment we do not need these functions. But we are already preparing
everything here.

administrator/components/com_foos/service/HTML/AdministratorService.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
5
6 defined('JPATH_BASE') or die;
7
8 class AdministratorService
9 {
10 }

3.1.1.7. administrator/components/com_foos/ src/View/Foos/HtmlView.php

The view administrator/components/com_foos/src/View/Foos/HtmlView.php defines ob-


jects (toolbar, title) and calls the model (data). At the moment our component has a primitive view.
Only a static text is shown. This will change soon! There are several files that work together to generate
the view in the frontend. For example, the controller that calls the view is involved. We created the
controller earlier in the current chapter. Later, we will add the model, which prepares the data.

Page 38 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

In the file HtmlView.php all buttons and titles of the toolbar are defined. The model is called to
prepare the data for the view. At the moment we only call the function of the parent class to display the
default template: parent::display($tpl);. Why do it yourself when there are functions in Joomla
to do it?
When naming a view, it is best to use only a capital letter as the initial letter. I had a problem with
the name of an additional View. I used FOOPlaces. The view was not found under this name.
After I renamed the view folder and namespace to Fooplaces, everything works fine. I found an
explanation of naming conventions on Githuba . According to this page, the folder name for the
template should be written in lower case. It does not say that in addition the view is allowed to
use an uppercase letter only for initial letters. According to a discussionb this is nevertheless the
case.
a
docs.joomla.org/j4.x:file_structure_and_naming_conventions
b
github.com/joomla/joomla-cms/discussions/36679

administrator/components/com_foos/src/View/Foos/HtmlView.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\View\Foos;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
9
10 class HtmlView extends BaseHtmlView
11 {
12 public function display($tpl = null): void
13 {
14 parent::display($tpl);
15 }
16 }

3.1.1.8. administrator/components/com_foos/ tmpl/foos/default.php

The file default.php is the template for rendering the view. You can further identify them by the
directory name tmpl. In it is the text that we display. At the moment we are putting all the effort into
the output of the text “Hello Foos”.

administrator/components/com_foos/tmpl/foos/default.php

1
2 <?php
3 \defined('_JEXEC') or die;

January 2023 Page 39


Joomla 4 - Developing Extensions 3. The first view in the backend

4 ?>
5 Hello Foos

3.1.1.9. components/com_foos/index.html and api/com_foos/index.html

I wrote in the preface that the file index.html is not needed. That is correct! Here I only added
it because I am putting together an installation package, but Joomla reports an error during the
installation if there is no folder for the frontend or if an empty directory is passed in the installation
package. At the moment we have no content for the frontend. The temporary insertion of the file is
therefore only a help at this point to avoid error messages during the installation. I create the folder
api for the sake of completeness.

components/com_foos/index.html

1
2 <!DOCTYPE html><title></title>

3.1.2. Changed files

Everything is new. There are no changed files yet.

3.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: In the beginning, the easiest thing to
do is to copy the files manually in place. Copy the files in the administrator folder into the
administrator folder of your Joomla 4 installation. Copy the files in the components folder
into the components folder of your Joomla 4 installation.

2. open the menu System | Install | Discover. Here you will see an entry for the compo-
nent you just copied. Select it and click on the button ’Install.

Page 40 January 2023


3. The first view in the backend Joomla 4 - Developing Extensions

Figure 3.2.: View that allows you to find extensions that were not installed via the normal Joomla
installation

3. if everything works, you will see these displays in front of you after the installation.

Figure 3.3.: View after installation

4. next test if you get the view for your component without errors.

January 2023 Page 41


Joomla 4 - Developing Extensions 3. The first view in the backend

Figure 3.4.: The first view in the backend

In previous Joomla versions, the text was output in the backend at the end of the installation,
which is inserted into the installation script with the command echo Text::_('...'). Since
Joomla 4, this no longer happens without further ado. More information is available on Githuba .
a
github.com/joomla/joomla-cms/issues/36343

Up to this point, it wasn’t rocket science. We have a solid basis for the next steps.

Page 42 January 2023


Joomla 4 - Developing Extensions 4. The First View in Frontend

4. The First View in Frontend

After you have a working backend for your component, you implement the frontend. Currently, with
the extension it is possible to display a static text. We don’t have dynamic data yet. This will change
soon. First, however, we build the rough structure.

For impatient people: Look at the changed program code in the diff viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t1...t2

4.1. Step by step

4.1.1. New files

The administration area of our component is located in the folder com_foos under /administrator
/component. Now we work on the frontend. The data of the frontend view are stored in the folder
com_foos directly under /components.

4.1.1.1. components/com_foos/src/Controller/DisplayController.php

The DisplayController in the directory components/com_foos/src/Controller/ is the entry point


for the Model-View-Controller part in the frontend of the Foo component. Name the class DisplayCon-
troller. Joomla expects this. Extend BaseController to use many things out-of-the-box. Everything I
wrote in the chapter on the First View in the Backend applies here analogously.

components/com_foos/src/Controller/DisplayController.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Controller;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\Controller\BaseController;

Page 43 January 2023


Joomla 4 - Developing Extensions 4. The First View in Frontend

9 use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
10
11 class DisplayController extends BaseController
12 {
13 public function __construct($config = [], MVCFactoryInterface
$factory = null, $app = null, $input = null)
14 {
15 parent::__construct($config, $factory, $app, $input);
16 }
17
18 public function display($cachable = false, $urlparams = [])
19 {
20 parent::display($cachable);
21
22 return $this;
23 }
24 }

4.1.1.2. components/com_foos/src/View/Foo/HtmlView.php

At the moment, the view of our component is simple. Only a static text is displayed. This will change!

There are several files that work together to generate the view in the frontend. For example, the
controller that calls it. We created this earlier in the current chapter. Later on, we will add a special
cell model that prepares the data. At the moment we use the model of the parent classes, because we
build on Joomla standard. The file HtmlView.php calls the inherited model to prepare the data for
the view.

components/com_foos/src/View/Foo/HtmlView.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\View\Foo;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
9
10 class HtmlView extends BaseHtmlView
11 {
12 public function display($tpl = null)
13 {
14 return parent::display($tpl);
15 }
16 }

Page 44 January 2023


4. The First View in Frontend Joomla 4 - Developing Extensions

4.1.1.2.1. Logging and debugging Joomla logging offers the possibility to log messages in a file
and on the screen. In the case of the screen, you will find this within the Joomla debug console at the
bottom of the web page when debugging is active. This function may be helpful when developing,
so I mention it here. The entry Log::add('Log me.', Log::DEBUG); causes a line to be added
to the log file. It is important that the necessary functions are loaded in the head of the file via use
Joomla\CMS\Log\Log;. The following images show where the logging and the debugging are set in
the Joomla backend.

Figure 4.1.: Settings for logging in the Joomla backend

January 2023 Page 45


Joomla 4 - Developing Extensions 4. The First View in Frontend

Figure 4.2.: Activate debugging in the Joomla backend

We do not use the file here, just because it fits a note: The file libraries/src/Log/
DelegatingPsrLogger.php becomes final in Joomla 5 and cannot be overwritten further. See
PR 39134 a .
a
github.com/joomla/joomla-cms/pull/39134

4.1.1.3. components/com_foos/ tmpl/foo/default.php

The file components/com_foos/ tmpl/foo/default.php contains the text we display. Everything


I wrote in the chapter about the first view in the backend also applies here.
components/com_foos/tmpl/foo/default.php

1
2 <?php
3 \defined('_JEXEC') or die;
4 ?>
5 Hello Foos

4.1.2. Modified files

4.1.2.1. administrator/components/com_foos/foos.xml

Administrator/components/com_foos/foos.xml’ is the file that tells Joomla how to install our compo-
nent. Therefore, we add the two newly added files here. This way, when installing or updating, Joomla

Page 46 January 2023


4. The First View in Frontend Joomla 4 - Developing Extensions

knows that the directories src and tmpl exist and where to copy them to. The copy destination is the
directory components/com_foos because of folder="components/com_foos".

administrator/components/com_foos/foos.xml

1 <description>COM_FOOS_XML_DESCRIPTION</description>
2 <namespace path="src">FooNamespace\Component\Foos</namespace>
3 <scriptfile>script.php</scriptfile>
4 + <!-- Frond-end files -->
5 + <files folder="components/com_foos">
6 + <folder>src</folder>
7 + <folder>tmpl</folder>
8 + </files>
9 <!-- Back-end files -->
10 <administration>
11 <!-- Menu entries -->

4.1.2.2. The file - components/com_foos/index.html

This file was only a workaround and it can now be deleted.

4.2. Test your Joomla component

1. at the end, install your component in Joomla version 4 to test it: Perform a new installation. This
is necessary, otherwise the new files will not be recognised in the frontend. To do this, uninstall
your previous installation and copy all files again. Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation. Install your component as
described in part one, after you have copied all the files. Joomla will set up namespaces for you
during installation.

2. then open the address JOOMLA4/index.php?option=com_foos&view=foo in a browser. You


will see the frontend view you just created.

Do you care about Search Engine Friendly (SEF) URLsa . Please do not enable this feature yet. This
sample extension does not support SEF yet. We will add the Joomla conform routing later.
a
docs.joomla.org/enabling_search_engine_friendly_(sef)_urls

January 2023 Page 47


Joomla 4 - Developing Extensions 4. The First View in Frontend

Figure 4.3.: Joomla View in Frontend

Page 48 January 2023


Joomla 4 - Developing Extensions 5. A Menu Item

5. A Menu Item

In this article you will learn how to create a menu item for the frontend view of your component. So
it is not necessary that you know the exact URL. Later a modification to search engine friendly (SEF)
URLs1 is possible. As a note, please do not enable this feature yet. This sample extension does not
support SEF yet. We will add the Joomla conform routing later.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t2...t3

5.1. Step by step

5.1.1. New files

The menu item in the frontend works differently than the one in the administration area. We create a
separate XML file. Later we will use parameters. But for now we keep it straightforward. We add some
language strings for text. Later on, we will see how to translate them.

5.1.1.1. components/com_foos/ tmpl/foo/default.xml

Create the file default.xml under components/com_foos/tmpl/foo and add the following
code:

components/com_foos/tmpl/foo/default.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <metadata>
4 <layout title="COM_FOOS_FOO_VIEW_DEFAULT_TITLE">
5 <message>
6 <![CDATA[COM_FOOS_FOO_VIEW_DEFAULT_DESC]]>
7 </message>
1
docs.joomla.org/enabling_search_engine_friendly_(sef)_urls/

Page 49 January 2023


Joomla 4 - Developing Extensions 5. A Menu Item

8 </layout>
9 </metadata>

I already mentioned it in the chapter on the update server: The term CDATAa is used in the XML
markup language for various purposes. It indicates that a given part of the document is general
characters rather than program code with a more specific, limited structure. The CDATA section
may contain markup characters (<, > and &). These are not interpreted further by the parser. The
use of entities such as &lt; and &amp; is not necessary.
a
en.wikipedia.org/wiki/cdata

The title attribute in the layout tag here is used when we create a new menu item for this component
in the administration area. The text in the message tag is displayed as a description. The language
string does not stay as it is. It will be translated into different languages. We will work on this later.
Here we prepare everything.

5.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the components folder
to the components folder of your Joomla 4 installation. A new installation is not necessary.
Continue using the ones from the previous part.
2. open the menu manager to create a menu item. To do this, click on Menu in the left sidebar and
then on All Menu Items.

Figure 5.1.: Joomla - Create a menu item in the backend

Page 50 January 2023


5. A Menu Item Joomla 4 - Developing Extensions

Then click on the New button and fill in all the necessary fields.

Figure 5.2.: Joomla - Select the type of menu item in the backend

3. find the appropriate Menu Item Type with the Select button.

Figure 5.3.: Joomla - Save a menu item in the backend

4. save the menu item.

5. switch to the frontend and make sure that the menu item is created correctly and works.

January 2023 Page 51


Joomla 4 - Developing Extensions 5. A Menu Item

Figure 5.4.: Joomla - The view of the menu item in the frontend

Page 52 January 2023


Joomla 4 - Developing Extensions 6. The M in MVC: Model

6. The M in MVC: Model

No new functionality is added in this part. We improve the previous structure. A web application
usually consists of

• Logic,
• data and
• the presentation.

It is problematic to combine these three elements in one class. Especially for larger projects. Joomla
uses the Model-View-Controller-Concept (MVC)1 . In this tutorial part, we add a Model to the frontend.
The Model object is responsible for the data and its processing.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t3...t4

6.1. Step by step

6.1.1. New files

6.1.1.1. components/com_foos/src/Model/FooModel.php

With the model it is also so that you do not reinvent the wheel. You extend the Joomla class
BaseDatabaseModel. Then implement only what you specifically use. In our case it is the output
$this->message = 'Hello Foo!'; for which we create the method getMsg().

The model classes that are available as parent class of Joomla can be found in the directory
libraries/src/MVC/Model/. BaseDatabaseModel is implemented in the file libraries/
src/MVC/Model/BaseDatabaseModel.php.

components/com_foos/src/Model/FooModel.php
1
en.wikipedia.org/wiki/model_view_controller

Page 53 January 2023


Joomla 4 - Developing Extensions 6. The M in MVC: Model

1
2 <?php
3
4
5 namespace FooNamespace\Component\Foos\Site\Model;
6
7 \defined('_JEXEC') or die;
8
9 use Joomla\CMS\MVC\Model\BaseDatabaseModel;
10
11 class FooModel extends BaseDatabaseModel
12 {
13 protected $message;
14
15 public function getMsg()
16 {
17 if (!isset($this->message)) {
18 $this->message = 'Hello Foo!';
19 }
20
21 return $this->message;
22 }
23 }

6.1.2. Modified files

6.1.2.1. components/com_foos/src/View/Foo/HtmlView.php

We get the data of the model in the view with $this->msg = $this->get('Msg');. This seems
complicated in this simple example. In complex applications, this procedure has proven itself. The
data calculation is done in the model. The view handles the design of the data.

components/com_foos/src/View/Foo/HtmlView.php

1 public function display($tpl = null)


2 {
3 + $this->msg = $this->get('Msg');
4 +
5 return parent::display($tpl);
6 }
7 }

You may be confused by the call $this->get('Msg'); as I was when I first started using Joomla.
The method in the model is called getMsg(), but we call it here via get('Msg'). Somehow that
doesn’t fit. If you have dealt with object oriented programming before, you are tempted to call it

Page 54 January 2023


6. The M in MVC: Model Joomla 4 - Developing Extensions

via getMsg(). If you are using Joomla, you will have an easier time using things the way they are
prepared. You call Gettera in the model via the method get() with the appropriate parameter.
a
en.wikipedia.org/wiki/mutator_method

6.1.2.2. components/com_foos/ tmpl/foo/default.php

We output the data via the template. Here, everything will be packed into HTML tags later.

components/com_foos/tmpl/foo/default.php

1 \defined('_JEXEC') or die;
2 ?>
3 -Hello Foos
4 +
5 +Hello Foos: <?php echo $this->msg;

6.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the files from the previous part.

2. look at the frontend view of your component. Make sure that the data for the output is generated
by the model. The text output now is Hello Foos: Hello Foo! instead of Hello Foos if
you followed my example.

January 2023 Page 55


Joomla 4 - Developing Extensions 6. The M in MVC: Model

Figure 6.1.: Joomla - Usind the Model - Frontend view

Page 56 January 2023


Joomla 4 - Developing Extensions 7. Extend the Menu Item with a Variable

7. Extend the Menu Item with a Variable

Sometimes you need to customize the frontend output for a menu item. For this you need a variable.
In this part of the tutorial we will add a text variable to the menu item and use it for the display in the
frontend.
For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t4...t5

7.1. Step by step

7.1.1. New files

No new file is added in this chapter. We only change files.

7.1.2. Modified files

7.1.2.1. components/com_foos/src/Model/FooModel.php

In the model, change the method in which text is calculated for output. Delete the following entry:
components/com_foos/src/Model/FooModel.php

1 ...
2 if (!isset($this->message))
3 {
4 $this->message = 'Hello Foo!';
5 }
6 ...

Add the following lines in its place:

1 ...
2 $app = Factory::getApplication();
3 $this->message = $app->input->get('show_text', "Hi");
4 ...

Page 57 January 2023


Joomla 4 - Developing Extensions 7. Extend the Menu Item with a Variable

You can do without the check if (!isset($this->message)) in the new variant because
the statement get('show_text', "Hi"); catches the error that occurs when the parameter
show_text is not set. Whenever the value show_text is not set, the second parameter "Hi" is
used as default.

The complete file looks like this in the Diff view:

components/com_foos/src/Model/FooModel.php

1 \defined('_JEXEC') or die;
2
3 +use Joomla\CMS\Factory;
4 use Joomla\CMS\MVC\Model\BaseDatabaseModel;
5
6
7 public function getMsg()
8 {
9 - if (!isset($this->message))
10 - {
11 - $this->message = 'Hello Foo!';
12 - }
13 + $app = Factory::getApplication();
14 + $this->message = $app->input->get('show_text', "Hi");
15
16 return $this->message;
17 }

7.1.2.1.1. Side note: How to handle request variables in Joomla The function $app->input
->get('show_text', "Hi") is a help. It is impemented via the Input class in the libraries
/vendor/joomla/input/src/Input.php file and works together with InputFilter in the
libraries/vendor/joomla/filter/src/InputFilter.php. file.

Extension development is about processing user input. The parameter added here is entered by a user
through a form and then stored in the database table. To ensure that the value of the parameter is
correct, i.e. does not contain malicious code or syntactical errors, it is necessary to filter the value. This
is where the Input class comes into play. Those already familiar with PHP may work directly with raw
request variables such as $_POST and $_GET. These work fine in Joomla. However, it is easier and
possibly safer to let the Input class do the work.

If you browse the code of Joomla, you will find many examples that show the basic uses of the Input
class. For example, $app->input->get('show_text', "Hi") is checked for a string, because it is
a string. To return the parameter without filtering, $app->input->get('show_text', "Hi", '
RAW') would be the appropriate command.

Page 58 January 2023


7. Extend the Menu Item with a Variable Joomla 4 - Developing Extensions

Possible data types for filtering are: - INT: An integer - UINT: An unsigned integer - FLOAT: A floating point
number - BOOLEAN: A boolean value - WORD: A string containing A-Z or underscores only (not case
sensitive) - ALNUM: A string containing A-Z or 0-9 only (not case sensitive) - CMD: A string containing A-Z,
0-9, underscores, periods or hyphens (not case sensitive) - BASE64: A string containing A-Z, 0-9, forward
slashes, plus or equals (not case sensitive) - STRING: A fully decoded and sanitised string (default) -
HTML: A sanitised string - ARRAY: An array - PATH: A sanitised file path - TRIM: A string trimmed from
normal, non-breaking and multibyte spaces - USERNAME: Do not use (use an application specific filter)
- RAW: The raw string is returned with no filtering - unknown: An unknown filter will act like STRING. If
the input is an array it will return an array of fully decoded and sanitised strings.

So far, so good. We are still missing the possibility to configure the value for show_text at the menu
item in the backend. We implement this next in the file default.xml.

7.1.2.2. components/com_foos/ tmpl/foo/default.xml

In your extension you offer the possibility to save a value at the menu item by extending the XML file
with an input element. The following code shows you how to add a text input field.

components/com_foos/tmpl/foo/default.xml

1 <![CDATA[COM_FOOS_FOO_VIEW_DEFAULT_DESC]]>
2 </message>
3 </layout>
4 + <!-- Add fields to the request variables for the layout. -->
5 + <fields name="request">
6 + <fieldset name="request">
7 + <field
8 + name="show_text"
9 + type="text"
10 + label="COM_FOOS_FIELD_TEXT_SHOW_LABEL"
11 + default="Hi"
12 + />
13 + </fieldset>
14 + </fields>
15 </metadata>

The XML entry

1 <field
2 name="show_text"
3 type="text"
4 label="COM_FOOS_FIELD_TEXT_SHOW_LABEL"
5 default="Hi"
6 />

turns Joomla into the following HTML code for the output in the backend form:

January 2023 Page 59


Joomla 4 - Developing Extensions 7. Extend the Menu Item with a Variable

1 <div class="control-label">
2 <label id="jform_request_show_text-lbl" for="jform_request_show_text"
>
3 COM_FOOS_FIELD_TEXT_SHOW_LABEL</label
4 >
5 </div>
6 <div class="controls has-success">
7 <input
8 type="text"
9 name="jform[request][show_text]"
10 id="jform_request_show_text"
11 value="Hi"
12 class="form-control valid form-control-success"
13 aria-invalid="false"
14 />
15 </div>

With type="text" we use one of the simplest form fields, that is the one of type text. Various
types of form fields are built into Joomla. The Joomla documentation lists these standard types.
Take a look at the table on the website docs.joomla.org/Form_field/en. Often you can implement
a special requirement with a special field.

7.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the components folder
to the components folder of your Joomla 4 installation. A new installation is not necessary.
Continue using the ones from the previous part.

2. switch to the Menu Manager and open a created menu item or create a new one. Here you will
now see a text field where you can insert any text.

Page 60 January 2023


7. Extend the Menu Item with a Variable Joomla 4 - Developing Extensions

Figure 7.1.: Joomla Request Variable at Joomla Menu Item - Backend

3. now switch to the frontend view. Make sure that the text you entered for the menu item is
displayed in the correct variant in the frontend.

Figure 7.2.: Joomla Request Variable at Joomla Menu Item - Frontend

I’m sure you can think of funnier or more useful examples. But the sense and function of the variables
will be clear.

4. Create multiple menu items, each containing different text. Don’t just output the text on the

January 2023 Page 61


Joomla 4 - Developing Extensions 7. Extend the Menu Item with a Variable

frontend, style the output using conditional statements1 . A popular use case is to change the
design of the output using variables. For example, you use the variable to query whether the
content is to be output in a list or in a table.

1
developer.mozilla.org/en/docs/web/javascript/reference/statements/if...else

Page 62 January 2023


Joomla 4 - Developing Extensions 8. A Joomla database table for your extension

8. A Joomla database table for your extension

Your view in the administration area usually does not contain only static text. You display data here
that is dynamic. At least that’s how most extensions work. That’s why in this part we create a database
for your component. In the database, we store three records during setup and display them in the
administration area. A static list is displayed. The single entries are not changeable via the backend.
We will work on that in the next part.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t5...t6

8.1. Step by step

8.1.1. New files

8.1.1.1. administrator/components/com_foos/ sql/install.mysql.utf8.sql

We create a file that contains SQL statements for creating the database table. So that these statements
are called, we add the name of the file later in the manifest.
Besides the id, which we use to make the element uniquely findable and the name name, which is
optional and names the item in our extension, there is the alias alias. The latter prepares the data
for routing, among other things. Imagine a system URL like http://www.example.com/index.php
?option=com_foos&view=foo&id=1. This is not very readable for humans. Machines like search
engines also process such a URL poorly. A textual description is mandatory. In Joomla this is done
with the help of the alias. This can be defined by the user. So that the alias text for the URL contains
only valid characters, there are automatic processes in the background processing of Joomla.
With CREATE TABLE IF NOT EXISTS ... we create the database table if it does not already exist.
With INSERT INTO ... we store sample contents in the database table. In a real extension, I would
not add sample data via the SQL file during installation. In Joomla 4, a plugin of the type sampledata
is a good choice. For inspiration you can find plugins in Joomla in the directory plugins/sampledata
.

Page 63 January 2023


Joomla 4 - Developing Extensions 8. A Joomla database table for your extension

administrator/components/com_foos/sql/install.mysql.utf8.sql

1
2 CREATE TABLE IF NOT EXISTS `#__foos_details ` (
3 `id ` int(11) NOT NULL AUTO_INCREMENT,
4 `alias ` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT
NULL DEFAULT '',
5 `name ` varchar(255) NOT NULL DEFAULT '',
6 PRIMARY KEY ( `id`)
7 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=
utf8mb4_unicode_ci;
8
9 INSERT INTO `#__foos_details ` ( `name`) VALUES
10 ('Nina'),
11 ('Astrid'),
12 ('Elmar');

Read in the preface of this tutorial what exactly the prefix #__ means, if you are unfamiliar with it.

8.1.1.2. administrator/components/com_foos/ sql/uninstall.mysql.utf8.sql

So that Joomla does not contain unnecessary data in case of uninstallation, we simultaneously create
a file that contains the SQL command to delete the database table. This automatically executed when
uninstalling.

administrator/components/com_foos/sql/uninstall.mysql.utf8.sql

1
2 DROP TABLE IF EXISTS `#__foos_details`;

You might think ahead and ask yourself already how to handle potential future database changes.
What is needed to store the first name in addition to the name in a future version. SQL updates are
name-based in Joomla. This means exactly: For each version of the component you have to create
a file whose name consists of the version number and the file extension .sql in case database
contents change. Practically you will experience this in the further course of this tutorial.

8.1.1.3. administrator/components/com_foos/ src/Model/FoosModel.php

Next, we create a Model for the administration area. Since we are extending the ListModel class, we
do not need to take care of the connection to the database ourselves. We create the getListQuery()
method and specify our specific requirements here. Specific are for example the name of the database
table and the column.

Page 64 January 2023


8. A Joomla database table for your extension Joomla 4 - Developing Extensions

If not done so far, you will see here why the separation of model and view makes sense. Have a
look at the method getListQuery() in Joomla components, for example in com_content. The
SQL statement is usually extensive. Therefore it is clearer to encapsulate this from other parts.

The following code shows you the model, which in our case is still quite clear.

administrator/components/com_foos/src/Model/FoosModel.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Model;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\Model\ListModel;
9
10 class FoosModel extends ListModel
11 {
12 public function __construct($config = [])
13 {
14 parent::__construct($config);
15 }
16 protected function getListQuery()
17 {
18 // Create a new query object.
19 $db = $this->getDbo();
20 $query = $db->getQuery(true);
21
22 // Select the required fields from the table.
23 $query->select(
24 $db->quoteName(['id', 'name', 'alias'])
25 );
26 $query->from($db->quoteName('#__foos_details'));
27
28 return $query;
29 }
30 }

8.1.2. Modified files

8.1.2.1. administrator/components/com_foos/foos.xml

The entry in the installation manifest marked with a plus sign causes the SQL statements in the named
files to be called at the right moment, either during an installation or during an uninstallation..

administrator/components/com_foos/foos.xml

January 2023 Page 65


Joomla 4 - Developing Extensions 8. A Joomla database table for your extension

1 <description>COM_FOOS_XML_DESCRIPTION</description>
2 <namespace path="src">FooNamespace\Component\Foos</namespace>
3 <scriptfile>script.php</scriptfile>
4 + <install> <!-- Runs on install -->
5 + <sql>
6 + <file driver="mysql" charset="utf8">sql/install.mysql.utf8.
sql</file>
7 + </sql>
8 + </install>
9 + <uninstall> <!-- Runs on uninstall -->
10 + <sql>
11 + <file driver="mysql" charset="utf8">sql/uninstall.mysql.
utf8.sql</file>
12 + </sql>
13 + </uninstall>
14 <!-- Frond-end files -->
15 <files folder="components/com_foos">
16 <folder>src</folder>
17
18 <files folder="administrator/components/com_foos">
19 <filename>foos.xml</filename>
20 <folder>services</folder>
21 + <folder>sql</folder>
22 <folder>src</folder>
23 <folder>tmpl</folder>
24 </files>

In this example, I only support a MySQL database. Joomla supportsa as well as MySQL (from 5.6)
and PostgreSQL (from 11). If you also support both databases, you can find an implementation to
check out in the Weblinks componentb . How you name the drivers is flexible. postgresql and
mysql are correct. mysqli, pdomysql and pgsql are changed by Joomla in the file /libraries
/src/ Installer/Installer.php if you use this.
a
downloads.joomla.org/technical-requirements
b
github.com/joomla-extensions/weblinks

8.1.2.1.1. Updates For the sake of completeness, I anticipate here changes of a following chapter
concerning updating. If something changes, it is sufficient to include only the changes in the database.
You should take care that existing data are not affected. You save the changes in a separate file for
each version. The directory, where the files for the future updates are to be stored, you write in the
<update> tag. This is logical, right?

1 ...
2 <update> <!-- Runs on update -->
3 <schemas>
4 <schemapath type="mysql">sql/updates/mysql</schemapath>

Page 66 January 2023


8. A Joomla database table for your extension Joomla 4 - Developing Extensions

5 </schemas>
6 </update>
7 ...

Below you can see the update file src/administrator/components/com_foos/sql/updates/


mysql/10.0.0.sql as an example. This file will be added later in this example.

1 ALTER TABLE `#__foos_details ` ADD COLUMN `access ` int(10) unsigned NOT


NULL DEFAULT 0 AFTER `alias`;
2 ALTER TABLE `#__foos_details ` ADD KEY `idx_access ` ( `access`);

8.1.2.2. administrator/components/com_foos/ services/provider.php

Previously it was not necessary to set the MVC factory in provider.php, now it is required. Other-
wise you will see the following error message or you will be forced to program the connection to the
database yourself: MVC factory not set in Joomla\CMS\Extension\MVCComponent.

administrator/components/com_foos/services/provider.php

1 use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
2 use Joomla\CMS\Extension\Service\Provider\MVCFactory;
3 use Joomla\CMS\HTML\Registry;
4 +use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
5 use Joomla\DI\Container;
6 use Joomla\DI\ServiceProviderInterface;
7 use FooNamespace\Component\Foos\Administrator\Extension\FoosComponent;
8
9 $component = new FoosComponent($container->get(
ComponentDispatcherFactoryInterface::class));
10
11 $component->setRegistry($container->get(Registry::class
));
12 + $component->setMVCFactory($container->get(
MVCFactoryInterface::class));
13
14 return $component;
15 }

8.1.2.3. administrator/components/com_foos/ src/View/Foos/HtmlView.php

In the view we get all the items at the end. For this we call the method $this->get('Items') in the
model:

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 class HtmlView extends BaseHtmlView

January 2023 Page 67


Joomla 4 - Developing Extensions 8. A Joomla database table for your extension

2 {
3 + protected $items;
4 +
5
6 public function display($tpl = null): void
7 {
8 + $this->items = $this->get('Items');
9 parent::display($tpl);
10 }
11 }

8.1.2.4. administrator/components/com_foos/ tmpl/foos/default.php

Last but not least, we display everything using the template file. Instead of the static text Hello Foos
there is now a loop that goes through all elements.

administrator/components/com_foos/tmpl/foos/default.php

1 \defined('_JEXEC') or die;
2 ?>
3 -Hello Foos
4 +<?php foreach ($this->items as $i => $item) : ?>
5 +<?php echo $item->name; ?>
6 +</br>
7 +<?php endforeach; ?>

Are you wondering about the syntax? In the preface I had explained why I choose the alternative
syntax for PHP in a template file and enclose the individual lines in PHP tags.

8.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder to the administrator folder of your Joomla 4 installation. Install your component as
described in part one, after copying all files. Joomla creates the database during the installation.

2. Next, test if the view of your component in the administration area is correct. Do you see three
entries? We had entered these as sample data in the SQL file when setting up the database.

Page 68 January 2023


8. A Joomla database table for your extension Joomla 4 - Developing Extensions

Figure 8.1.: Joomla Component with Database

3. make sure that the elements are stored in the database. I use locally phpmyadmin.net for
database administration.

Figure 8.2.: Joomla Datenbankansicht in phpMyAdmin

January 2023 Page 69


Joomla 4 - Developing Extensions 9. Using the Database

9. Using the Database

In the previous part we set up a database for the Joomla components. In this part you will learn how to
change or add data using a form in the administration area. At the end, the view of your component in
the administration area contains a button to add new items. You change an existing item by clicking on
the title in the list view.
For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t6...t6b

9.1. Step by step

9.1.1. New files

9.1.1.1. administrator/components/com_foos/ forms/foo.xml

Joomla creates the form for you if you give it the requirements in an XML file. Below you can see this
for our example.

administrator/components/com_foos/forms/foo.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <form>
4 <fieldset>
5 <field
6 name="id"
7 type="number"
8 label="JGLOBAL_FIELD_ID_LABEL"
9 default="0"
10 class="readonly"
11 readonly="true"
12 />
13
14 <field
15 name="name"

Page 71 January 2023


Joomla 4 - Developing Extensions 9. Using the Database

16 type="text"
17 label="COM_FOOS_FIELD_NAME_LABEL"
18 size="40"
19 required="true"
20 />
21
22 <field
23 name="alias"
24 type="text"
25 label="JFIELD_ALIAS_LABEL"
26 size="45"
27 hint="JFIELD_ALIAS_PLACEHOLDER"
28 />
29 </fieldset>
30 </form>

You want an overview of all possible form elements? In the Joomla documentationa all standard
form fields are listed.
a
docs.joomla.org/form_field

Further tip: We have a simple form so far. Later, more specific requirements will surely be added.
For example: What is the best way to place JavaScript in a Joomla form? A quick and simple but
messy solution is this: You create a field type=note in the XML definition and then write the
JavaScript code into the language constant of the description. I found a more elegant solution
in Allrounder template by Bakual a . First he creates a new field of type loadjscssb . He then
includes this in the file templateDetails.xmlc . Don’t worry if you don’t see through the last
variant right away. We will create more fields as we go along.
a
github.com/Bakual/Allrounder
b
github.com/Bakual/Allrounder/blob/master/fields/loadjscss.php
c
github.com/Bakual/Allrounder/blob/master/templateDetails.xml#L51

9.1.1.2. administrator/components/com_foos/ src/Controller/FooController.php

We create more or less an empty class with FooController. Although it contains no logic of its
own, we need it because it inherits from FormController. Joomla expects FooController as the
controller of the extension in this place under this name.

administrator/components/com_foos/src/Controller/FooController.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Controller;
5

Page 72 January 2023


9. Using the Database Joomla 4 - Developing Extensions

6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\Controller\FormController;
9
10 class FooController extends FormController
11 {
12 }

When should you add something to the file administrator/components/com_foos/src/


Controller/FooController.php? Did you rename your component and now you have the
problem that your Views are not found correctly by Joomla? For example, did you create a new View
called Katze and your list view is called Katzen. Now you are sometimes redirected by Joomla to the
View Katzes. Especially when you create a new record or cancel an editing. You wonder where the S
in the end of Katzes comes from? You can find the solution in the file libraries/src/MVC/Controller/
FormController.php1 . In the constructor of this file, the view_item variable is automatically made
plural. However, the English grammar is used. In the case of the English word cat, it fits. Cats is
plural of cat. Your controller inherits these values because of use Joomla\CMS\MVC\Controller
\FormController and extends FormController. This does not always fit. You have just
experienced an example with the name cat. Set in your FormController the variables view_item
and view_list yourself to use custom values.

1 class KatzeController extends FormController


2 {
3 protected $view_item = 'katze';
4 protected $view_list = 'katzen';
5 }

9.1.1.3. administrator/components/com_foos/ src/Model/FooModel.php

Now we create the model to fetch the data for an element from the database. This we call FooModel.
It inherits the main implementations from AdminModel. We add our own special requirements. With
$typeAlias we set the typalias for the content type. This way Joomla knows for all inherited functions
to which element it has to apply them exactly. For example, the alias in loadFormData() is used
to convert the matching XML file into a form. Remember, you created the file in the current chapter.
And for the correct mapping of the table, the alias is essential when you reuse Joomla functions. The
typalias plays a big role in the background without you noticing it.

administrator/components/com_foos/src/Model/FooModel.php

1
2 <?php
1
libraries/src/MVC/Controller/FormController.php

January 2023 Page 73


Joomla 4 - Developing Extensions 9. Using the Database

3
4 namespace FooNamespace\Component\Foos\Administrator\Model;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\MVC\Model\AdminModel;
10
11 class FooModel extends AdminModel
12 {
13 public $typeAlias = 'com_foos.foo';
14
15 public function getForm($data = [], $loadData = true)
16 {
17 // Get the form.
18 $form = $this->loadForm($this->typeAlias, 'foo', ['control' =>
'jform', 'load_data' => $loadData]);
19
20 if (empty($form)) {
21 return false;
22 }
23
24 return $form;
25 }
26
27 protected function loadFormData()
28 {
29 $app = Factory::getApplication();
30
31 $data = $this->getItem();
32
33 $this->preprocessData($this->typeAlias, $data);
34
35 return $data;
36 }
37
38 protected function prepareTable($table)
39 {
40 $table->generateAlias();
41 }
42 }

9.1.1.4. administrator/components/com_foos/ src/Table/FooTable.php

We implement the access to the database table. It is important to set $this->typeAlias and to
specify the name of the table #__foos_details.

administrator/components/com_foos/src/Table/FooTable.php

Page 74 January 2023


9. Using the Database Joomla 4 - Developing Extensions

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Table;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Application\ApplicationHelper;
9 use Joomla\CMS\Table\Table;
10 use Joomla\Database\DatabaseDriver;
11
12 class FooTable extends Table
13 {
14 public function __construct(DatabaseDriver $db)
15 {
16 $this->typeAlias = 'com_foos.foo';
17
18 parent::__construct('#__foos_details', 'id', $db);
19 }
20
21 public function generateAlias()
22 {
23 if (empty($this->alias)) {
24 $this->alias = $this->name;
25 }
26
27 $this->alias = ApplicationHelper::stringURLSafe($this->alias,
$this->language);
28
29 if (trim(str_replace('-', '', $this->alias)) == '') {
30 $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
31 }
32
33 return $this->alias;
34 }
35 }

9.1.1.5. administrator/components/com_foos/ src/View/Foo/HtmlView.php

The file administrator/components/com_foos/src/View/Foo/HtmlView.php organises the


view of an element. Be careful not to mix this up this with the file administrator/components/
com_foos/src/View/Foo s /HtmlView.php, which displays all elements in an overview list. To
edit an element, we need a toolbar just like in the list view. The display itself is done as usual via the
method display of the class BaseHtmlView. Only our special features are given via $this->form
= $this->get('Form'); and $this->item = $this->get('Item');.

administrator/components/com_foos/src/View/Foo/HtmlView.php

January 2023 Page 75


Joomla 4 - Developing Extensions 9. Using the Database

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\View\Foo;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\Language\Text;
10 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
11 use Joomla\CMS\Toolbar\ToolbarHelper;
12
13 class HtmlView extends BaseHtmlView
14 {
15 protected $form;
16
17 protected $item;
18
19 public function display($tpl = null)
20 {
21 $this->form = $this->get('Form');
22 $this->item = $this->get('Item');
23
24 $this->addToolbar();
25
26 return parent::display($tpl);
27 }
28
29 protected function addToolbar()
30 {
31 Factory::getApplication()->input->set('hidemainmenu', true);
32
33 $isNew = ($this->item->id == 0);
34
35 ToolbarHelper::title($isNew ? Text::_('COM_FOOS_MANAGER_FOO_NEW
') : Text::_('COM_FOOS_MANAGER_FOO_EDIT'), 'address foo');
36
37 ToolbarHelper::apply('foo.apply');
38 ToolbarHelper::cancel('foo.cancel', 'JTOOLBAR_CLOSE');
39 }
40 }

9.1.1.6. administrator/components/com_foos/ tmpl/foo/edit.php

In the file edit.php is the view implemented, which is called for editing. It is important for me to
address the Webassetmanager2 $wa = $this->document->getWebAssetManager(); here. This
2
docs.joomla.org/j4.x:web_assets

Page 76 January 2023


9. Using the Database Joomla 4 - Developing Extensions

is new in Joomla 4. You load two JavaScript files via Webassetmanager. useScript('keepalive')
loads media/system/js/keepalive.js and keeps your session alive while you edit or create an
article. useScript('form.validate') loads a lot of helpful functions with media/system/js/
core.js. For example, validation, which we’ll look at in more detail later.

If you do not include webassets correctly, you will get the following error in the console of
your browser when you save the form: joomla document.formvalidator is undefined.
Joomla validates the forms by default and expects the file media/system/js/core.js to be
loaded.

administrator/components/com_foos/tmpl/foo/edit.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Factory;
7 use Joomla\CMS\HTML\HTMLHelper;
8 use Joomla\CMS\Router\Route;
9
10 $app = Factory::getApplication();
11 $input = $app->input;
12
13 $wa = $this->document->getWebAssetManager();
14 $wa->useScript('keepalive')
15 ->useScript('form.validate');
16
17 $layout = 'edit';
18 $tmpl = $input->get('tmpl', '', 'cmd') === 'component' ? '&tmpl=
component' : '';
19 ?>
20
21 <form action="<?php echo Route::_('index.php?option=com_foos&layout=' .
$layout . $tmpl . '&id=' . (int) $this->item->id); ?>" method="post
" name="adminForm" id="foo-form" class="form-validate">
22 <?php echo $this->getForm()->renderField('name'); ?>
23 <?php echo $this->getForm()->renderField('alias'); ?>
24 <input type="hidden" name="task" value="">
25 <?php echo HTMLHelper::_('form.token'); ?>
26 </form>

Are you interested in the content of the files Core.jsa or Keepalive.jsb ? In this case, look at
them directly in Joomla. In the development version, they are located in the directory build/
media_source/system/js/ and are prepared for installation with the help of scripts, Node.jsc
and Composerd in the directory media/system/js. For further information, please refer to the

January 2023 Page 77


Joomla 4 - Developing Extensions 9. Using the Database

Joomla Documentatione .
a
build/media_source/system/js/core.es6.js
b
build/media_source/system/js/keepalive.es6.js
c
nodejs.org
d
getcomposer.org/
e
docs.joomla.org/j4.x:setting_up_your_local_environment

9.1.1.7. administrator/components/com_foos/tmpl/foos/emptystate.php

With the file administrator/components/com_foos/tmpl/foos/emptystate.php we create a


special layout for the case that the component contains no element and is therefore empty.

administrator/components/com_foos/tmpl/foos/emptystate.php

1
2 <?php
3 \defined('_JEXEC') or die;
4
5 use Joomla\CMS\Factory;
6 use Joomla\CMS\Layout\LayoutHelper;
7
8 $displayData = [
9 'textPrefix' => 'COM_FOOS',
10 'formURL' => 'index.php?option=com_foos',
11 'helpURL' => 'https://codeberg.org/astrid/j4examplecode/src/branch/
main/README.md',
12 'icon' => 'icon-copy',
13 ];
14
15 $user = Factory::getApplication()->getIdentity();
16
17 if ($user->authorise('core.create', 'com_foos') || count($user->
getAuthorisedCategories('com_foos', 'core.create')) > 0) {
18 $displayData['createURL'] = 'index.php?option=com_foos&task=foo.add
';
19 }
20
21 echo LayoutHelper::render('joomla.content.emptystate', $displayData);

'icon'=> 'icon-copy' only works with icons that are included by name in the file build/
media_source/system/scss/_icomoon.scssa . I explained in the preface why this is like it
is. Adjust the layout for the icon if you want to display a different symbol.
a
build/media_source/system/scss/_icomoon.scss

The Empty State layout has been integrated into Joomla in PR 332643 . The implementation
3
github.com/joomla/joomla-cms/pull/33264

Page 78 January 2023


9. Using the Database Joomla 4 - Developing Extensions

of the Empty-State-Layout here in the tutorial takes the hint from Issue 35712 into account
and inserts the code if (count($errors = $this->get('Errors'))){ throw new
GenericDataException(implode("\n", $errors), 500);} before the code if (!count(
$this->items)&& $this->get('IsEmptyState')){ $this->setLayout('emptystate')
;} in the file administrator/components/com_foos/src/View/Foos/HtmlView.php. This is
done in a later chapter.

Good design is already a challenge when there is data to display. It is even more difficult to
implement an empty page in a user-friendly way. Have a look at emptystat.es if you want to get
inspired about your Empty State implementation.

9.1.2. Modified files

9.1.2.1. administrator/components/com_foos/foos.xml

To ensure that the “forms” directory is passed to Joomla during a new installation, enter it in the
installation manifest.

administrator/components/com_foos/foos.xml

1 </submenu>
2 <files folder="administrator/components/com_foos">
3 <filename>foos.xml</filename>
4 + <folder>forms</folder>
5 <folder>services</folder>
6 <folder>sql</folder>
7 <folder>src</folder>

9.1.2.2. administrator/components/com_foos/ src/View/Foos/HtmlView.php

In the view that displays the overview list, we add the toolbar. Here we insert a button that creates a new
element. We also query with if (!count($this->items)&& $this->get('IsEmptyState'))
whether there are items to display. If the view is empty, we display the user-friendly Empty State layout
$this->setLayout('emptystate');.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1
2 \defined('_JEXEC') or die;
3
4 +use Joomla\CMS\Language\Text;
5 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
6 +use Joomla\CMS\Toolbar\Toolbar;

January 2023 Page 79


Joomla 4 - Developing Extensions 9. Using the Database

7 +use Joomla\CMS\Toolbar\ToolbarHelper;
8
9 class HtmlView extends BaseHtmlView
10 public function display($tpl = null): void
11 {
12 $this->items = $this->get('Items');
13 +
14 + if (!count($this->items) && $this->get('IsEmptyState')) {
15 + $this->setLayout('emptystate');
16 + }
17 +
18 + $this->addToolbar();
19 +
20 parent::display($tpl);
21 }
22 +
23 + protected function addToolbar()
24 + {
25 + // Get the toolbar object instance
26 + $toolbar = Toolbar::getInstance('toolbar');
27 +
28 + ToolbarHelper::title(Text::_('COM_FOOS_MANAGER_FOOS'), 'address
foo');
29 +
30 + $toolbar->addNew('foo.add');
31 + }
32 }

9.1.2.3. administrator/components/com_foos/ tmpl/foos/default.php

In the template of the overview list, we replace the simple text with a form. The form contains a form
field for each column in the database table and makes it possible to create or change data.

administrator/components/com_foos/tmpl/foos/default.php

1 \defined('_JEXEC') or die;
2 +
3 +use Joomla\CMS\HTML\HTMLHelper;
4 +use Joomla\CMS\Language\Text;
5 +use Joomla\CMS\Router\Route;
6 ?>
7 -<?php foreach ($this->items as $i => $item) : ?>
8 - <?php echo $item->name; ?>
9 -</br>
10 -<?php endforeach; ?>
11 +<form action="<?php echo Route::_('index.php?option=com_foos'); ?>"
method="post" name="adminForm" id="adminForm">
12 + <div class="row">
13 + <div class="col-md-12">

Page 80 January 2023


9. Using the Database Joomla 4 - Developing Extensions

14 + <div id="j-main-container" class="j-main-container">


15 + <?php if (empty($this->items)) : ?>
16 + <div class="alert alert-warning">
17 + <?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS
'); ?>
18 + </div>
19 + <?php else : ?>
20 + <table class="table" id="fooList">
21 + <thead>
22 + <tr>
23 + <th scope="col" style="width:1%" class=
"text-center d-none d-md-table-cell">
24 + <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_NAME'); ?>
25 + </th>
26 + <th scope="col">
27 + <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_ID'); ?>
28 + </th>
29 + </tr>
30 + </thead>
31 + <tbody>
32 + <?php
33 + $n = count($this->items);
34 + foreach ($this->items as $i => $item) :
35 + ?>
36 + <tr class="row<?php echo $i % 2; ?>">
37 + <th scope="row" class="has-context">
38 + <div>
39 + <?php echo $this->escape($item
->name); ?>
40 + </div>
41 + <?php $editIcon = '<span class="fa
fa-pencil-square mr-2" aria-hidden="true"></span>'; ?>
42 + <a class="hasTooltip" href="<?php
echo Route::_('index.php?option=com_foos&task=foo.edit&id=' . (int)
$item->id); ?>" title="<?php echo Text::_('JACTION_EDIT'); ?> <?php
echo $this->escape(addslashes($item->name)); ?>">
43 + <?php echo $editIcon; ?><?php
echo $this->escape($item->name); ?></a>
44 +
45 + </th>
46 + <td class="d-none d-md-table-cell">
47 + <?php echo $item->id; ?>
48 + </td>
49 + </tr>
50 + <?php endforeach; ?>
51 + </tbody>
52 + </table>
53 +
54 + <?php endif; ?>

January 2023 Page 81


Joomla 4 - Developing Extensions 9. Using the Database

55 + <input type="hidden" name="task" value="">


56 + <input type="hidden" name="boxchecked" value="0">
57 + <?php echo HTMLHelper::_('form.token'); ?>
58 + </div>
59 + </div>
60 + </div>
61 +</form>

9.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the files from the previous part.

2. next, open the list view of your component in the administration area. Are the three items
provided with links? Do you see a button to create a new item?

Figure 9.1.: Edit Joomla Component in Backend - List View

3. then click on the button New or on the title of an item. You will see the form for creating or editing
items. Add a new item.

Page 82 January 2023


9. Using the Database Joomla 4 - Developing Extensions

Figure 9.2.: Edit Joomla Component in Backend - Open View of an Item

4. change existing items by clicking on the name.

Figure 9.3.: Edit Joomla Component in the Backend - Edit an Element

5. delete all Foo-Items via the database and make sure that the Empty-State layout is displayed.
Have you not yet edited the database yourself? In the previous section I suggested phpmyad-
min.net as a tool. In the following you will see the standard view followed by our user-friendly
Empty State version for comparison. In the next but one section we will take care of the language

January 2023 Page 83


Joomla 4 - Developing Extensions 9. Using the Database

files, then the layout will be more friendly. Later, the button for deleting items is also added.

Figure 9.4.: Edit Joomla Component in Backend - Empty View without Empty State Layout

Figure 9.5.: Edit Joomla Component in Backend - Empty View with Empty State Layout

Page 84 January 2023


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

10. Using the Database Data in the Frontend

We have a database where the data about the component is stored. The next step is to display the
dynamic content in the frontend. In this part, I’ll show you how to output the content for an element
via menu item. For this we will create our own form field.
For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t6b...t7

10.1. Step by step

10.1.1. New files

10.1.1.1. administrator/components/com_foos/ src/Field/Modal/FooField.php

First, we create the form field through which it is possible to select or deselect a Foo element. In this
case, we cannot access a ready-made field. Basically, we implement the methods getInput and
getLabel and we set the type to Modal_Foo. It is not mandatory that the name of the class starts
with the word “Field” and that the class is stored in the directory “Field”. However, it can be helpful
because it is standard in Joomla’s own extension.
It is possible to extend the field so that a Foo element is created via a button. I have left this out so
far for the sake of simplicity. Sample code is provided by the component com_contact in the file
administrator/components/com_contact/ src/Field/Modal/ContactField.php.

administrator/components/com_foos/src/Field/Modal/FooField.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Field\Modal;
5
6 \defined('JPATH_BASE') or die;
7

Page 85 January 2023


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

8 use Joomla\CMS\Factory;
9 use Joomla\CMS\Form\FormField;
10 use Joomla\CMS\HTML\HTMLHelper;
11 use Joomla\CMS\Language\Text;
12 use Joomla\CMS\Session\Session;
13
14 class FooField extends FormField
15 {
16 protected $type = 'Modal_Foo';
17
18 protected function getInput()
19 {
20 $allowClear = ((string) $this->element['clear'] != 'false');
21 $allowSelect = ((string) $this->element['select'] != 'false');
22
23 // The active foo id field.
24 $value = (int) $this->value > 0 ? (int) $this->value : '';
25
26 // Create the modal id.
27 $modalId = 'Foo_' . $this->id;
28
29 // Add the modal field script to the document head.
30 $wa = Factory::getApplication()->getDocument()->
getWebAssetManager();
31
32 // Add the modal field script to the document head.
33 $wa->useScript('field.modal-fields');
34
35 // Script to proxy the select modal function to the modal-
fields.js file.
36 if ($allowSelect) {
37 static $scriptSelect = null;
38
39 if (is_null($scriptSelect)) {
40 $scriptSelect = [];
41 }
42
43 if (!isset($scriptSelect[$this->id])) {
44 $wa->addInlineScript("
45 window.jSelectFoo_" . $this->id . " = function (id,
title, object) {
46 window.processModalSelect('Foo', '" . $this->id . "
', id, title, '', object);
47 }",
48 [],
49 ['type' => 'module']
50 );
51
52 $scriptSelect[$this->id] = true;
53 }
54 }

Page 86 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

55
56 // Setup variables for display.
57 $linkFoos = 'index.php?option=com_foos&amp;view=foos&amp;layout
=modal&amp;tmpl=component&amp;'
58 . Session::getFormToken() . '=1';
59 $linkFoo = 'index.php?option=com_foos&amp;view=foo&amp;layout=
modal&amp;tmpl=component&amp;'
60 . Session::getFormToken() . '=1';
61 $modalTitle = Text::_('COM_FOOS_CHANGE_FOO');
62
63 $urlSelect = $linkFoos . '&amp;function=jSelectFoo_' . $this->
id;
64
65 if ($value) {
66 $db = Factory::getDbo();
67 $query = $db->getQuery(true)
68 ->select($db->quoteName('name'))
69 ->from($db->quoteName('#__foos_details'))
70 ->where($db->quoteName('id') . ' = ' . (int) $value);
71 $db->setQuery($query);
72
73 try {
74 $title = $db->loadResult();
75 } catch (\RuntimeException $e) {
76 Factory::getApplication()->enqueueMessage($e->
getMessage(), 'error');
77 }
78 }
79
80 $title = empty($title) ? Text::_('COM_FOOS_SELECT_A_FOO') :
htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
81
82 // The current foo display field.
83 $html = '';
84
85 if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
86 $html .= '<span class="input-group">';
87 }
88
89 $html .= '<input class="form-control" id="' . $this->id . '
_name" type="text" value="' . $title . '" readonly size
="35">';
90
91 // Select foo button
92 if ($allowSelect) {
93 $html .= '<button'
94 . ' class="btn btn-primary hasTooltip' . ($value ? '
hidden' : '') . '"'
95 . ' id="' . $this->id . '_select"'
96 . ' data-bs-toggle="modal"'
97 . ' type="button"'

January 2023 Page 87


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

98 . ' data-bs-target="#ModalSelect' . $modalId . '"'


99 . ' title="' . HTMLHelper::tooltipText('
COM_FOOS_CHANGE_FOO') . '">'
100 . '<span class="icon-file" aria-hidden="true"></span> '
. Text::_('JSELECT')
101 . '</button>';
102 }
103
104 // Clear foo button
105 if ($allowClear) {
106 $html .= '<button'
107 . ' class="btn btn-secondary' . ($value ? '' : ' hidden
') . '"'
108 . ' id="' . $this->id . '_clear"'
109 . ' type="button"'
110 . ' onclick="window.processModalParent(\'' . $this->id
. '\'); return false;">'
111 . '<span class="icon-remove" aria-hidden="true"></span>
' . Text::_('JCLEAR')
112 . '</button>';
113 }
114
115 if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
116 $html .= '</span>';
117 }
118
119 // Select foo modal
120 if ($allowSelect) {
121 $html .= HTMLHelper::_(
122 'bootstrap.renderModal',
123 'ModalSelect' . $modalId,
124 [
125 'title' => $modalTitle,
126 'url' => $urlSelect,
127 'height' => '400px',
128 'width' => '800px',
129 'bodyHeight' => 70,
130 'modalWidth' => 80,
131 'footer' => '<button type="button" class="btn
btn-secondary" data-bs-dismiss="modal">'
132 . Text::_('
JLIB_HTML_BEHAVIOR_CLOSE') .
'</button>',
133 ]
134 );
135 }
136
137 // Note: class='required' for client side validation.
138 $class = $this->required ? ' class="required modal-value"' : ''
;
139

Page 88 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

140 $html .= '<input type="hidden" id="'


141 . $this->id . '_id"'
142 . $class . ' data-required="' . (int) $this->required
143 . '" name="' . $this->name
144 . '" data-text="'
145 . htmlspecialchars(Text::_('COM_FOOS_SELECT_A_FOO', true),
ENT_COMPAT, 'UTF-8')
146 . '" value="' . $value . '">';
147
148 return $html;
149 }
150
151 protected function getLabel()
152 {
153 return str_replace($this->id, $this->id . '_name', parent::
getLabel());
154 }
155 }

The programme code for the form field is adapted to Bootstrap 5a . This framework was integrated
into Joomla in the pull request 32037b .
a
getbootstrap.com
b
github.com/joomla/joomla-cms/pull/32037

Make sure that you use the correct names. If nothing happens later during testing when you select a
single Foo, it is usually due to a typing error. Background: JavaScript is being executed. You add this
script in two places. First, you create the function jSelectFoo_... using variables.

1 ...
2 if (!isset($scriptSelect[$this->id])) {
3 $wa->addInlineScript("
4 window.jSelectFoo_" . $this->id . " = function (id, title, object)
{
5 window.processModalSelect('Foo', '" . $this->id . "', id, title
, '', object);
6 }",
7 [],
8 ['type' => 'module']
9 );
10
11 $scriptSelect[$this->id] = true;
12 }
13
14 ...

In the source code of the Joomla administration area, the following code is added:

1 <script nonce="sampleId=">

January 2023 Page 89


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

2 function jSelectFoos_jform_request_id(id, title, object) { window.


processModalSelect('Foo', 'jform_request_id', id, title, '', object)
;}
3 </script>

A little later, you call the function.

1 ...
2 $urlSelect = $linkFoos . '&amp;function=jSelectFoo_' . $this->id;
3 ...

The name of the function must be the same in both places!

In an early sample code version for the field “FooField” we do not use the Webasset Manager. The
necessary changes can be found herea .
a
github.com/joomla/joomla-cms/commit/04f844ad4a6d0432ec4b770bbb2a33243ded16d9

10.1.1.2. administrator/components/com_foos/ tmpl/foos/modal.php

We open the selection in a modal window via the FooField. As address we have inserted in the
field $linkFoos = 'index.php?option=com_foos&amp;view=foos&amp;layout=modal
&amp;tmpl=component&amp;'. The following code shows you the template for this modal
window.

administrator/components/com_foos/tmpl/foos/modal.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Factory;
7 use Joomla\CMS\HTML\HTMLHelper;
8 use Joomla\CMS\Language\Text;
9 use Joomla\CMS\Router\Route;
10 use Joomla\CMS\Session\Session;
11
12 $app = Factory::getApplication();
13
14 $wa = $this->document->getWebAssetManager();
15 $wa->useScript('com_foos.admin-foos-modal');
16
17 $function = $app->input->getCmd('function', 'jSelectFoos');
18 $onclick = $this->escape($function);
19 ?>
20 <div class="container-popup">
21

Page 90 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

22 <form action="<?php echo Route::_('index.php?option=com_foos&view=


foos&layout=modal&tmpl=component&function=' . $function . '&' .
Session::getFormToken() . '=1'); ?>" method="post" name="
adminForm" id="adminForm" class="form-inline">
23
24 <?php if (empty($this->items)) : ?>
25 <div class="alert alert-warning">
26 <?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
27 </div>
28 <?php else : ?>
29 <table class="table table-sm">
30 <thead>
31 <tr>
32 <th scope="col" style="width:10%" class="d-none
d-md-table-cell">
33 </th>
34 <th scope="col" style="width:1%">
35 </th>
36 </tr>
37 </thead>
38 <tbody>
39 <?php
40 $iconStates = [
41 -2 => 'icon-trash',
42 0 => 'icon-unpublish',
43 1 => 'icon-publish',
44 2 => 'icon-archive',
45 ];
46 ?>
47 <?php foreach ($this->items as $i => $item) : ?>
48 <?php $lang = ''; ?>
49 <tr class="row<?php echo $i % 2; ?>">
50 <th scope="row">
51 <a class="select-link" href="javascript:
void(0)" data-function="<?php echo $this
->escape($onclick); ?>" data-id="<?php
echo $item->id; ?>" data-title="<?php
echo $this->escape($item->name); ?>">
52 <?php echo $this->escape($item->name);
?>
53 </a>
54 </th>
55 <td>
56 <?php echo (int) $item->id; ?>
57 </td>
58 </tr>
59 <?php endforeach; ?>
60 </tbody>
61 </table>
62
63 <?php endif; ?>

January 2023 Page 91


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

64
65 <input type="hidden" name="task" value="">
66 <input type="hidden" name="forcedLanguage" value="<?php echo
$app->input->get('forcedLanguage', '', 'CMD'); ?>">
67 <?php echo HTMLHelper::_('form.token'); ?>
68
69 </form>
70 </div>

A Modala is an area that opens in the foreground of a web page and changes its state. It is required
to actively close it. Modal dialogs lock the rest of the application as long as the dialog is displayed.
A modal is also called a dialog or lightbox.
a
en.wikipedia.org/wiki/dialog_box

10.1.1.3. media/com_foos/joomla.asset.json

We use the WebAssetManager. This time we add our own webasset using the file joomla.asset
.json. If you don’t include it correctly, you will get the following error when you select a foo item
for the menu item: There is no "com_foos.admin-foos-modal"asset of a "script"type
in the registry.. Reason: In the modal, the line $wa->useScript('com_foos.admin-foos
-modal'); calls the script com_foos.admin-foos-modal, which, however, was not registered cor-
rectly before. Therefore it is not found.

Because of the newly added file joomla.asset.json it is necessary that we reinstall the ex-
tension. We have used other files so far without a new installation in Joomla. This does not
work with the file joomla.asset.json. This file has to be registered once during an installation.
Furthermore, changes can be made in it. These are recognised without a new installation.

It is not mandatory to create the file joomla.asset.json if you want to use the WebAsset-
Managera . In the documentation you will find possibilities to register webassets in the code
afterwards.
a
docs.joomla.org/j4.x:web_assets

media/com_foos/joomla.asset.json

1 /* https://codeberg.org/astrid/j4examplecode/raw/branch/t7/src/media/
com_foos/joomla.asset.json */
2
3 {
4 "$schema": "https://developer.joomla.org/schemas/json-schema/
web_assets.json",
5 "name": "com_foos",

Page 92 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

6 "version": "1.0.0",
7 "description": "Joomla CMS",
8 "license": "GPL-2.0-or-later",
9 "assets": [
10 {
11 "name": "com_foos.admin-foos-modal",
12 "type": "script",
13 "uri": "com_foos/admin-foos-modal.js",
14 "dependencies": ["core"],
15 "attributes": {
16 "defer": true
17 }
18 }
19 ]
20 }

Are you wondering that instead of com_foos/js/admin-foos-modal.js I write com_foos/


admin-foos-modal.js as uri? In my opinion this is a hidden secret in Joomla. js and css file,
if the path is not absolutea , are automatically searched in the subdirectory js, respectively css.
This was already the case in Joomla 3.xb . In the call JHtml::_('script', 'com_example/
example.js', array('relative'=> true)); Joomla expects the file example.js to be
located at media/com_example/example.js. You should not include js in the path in the
statement This behavior is implemented by Web Asset Manager for scripts and styles by default.
Want to take a closer look? You can find the code for this in the file libraries/src/WebAsset/
WebAssetItem.php.
a
github.com/joomla/joomla-cms/blob/ddb844b450ec989f08f6a54c051ca52d57fa0789/ libraries/src/webasset/we-
bassetitem.php#l349
b
docs.joomla.org/adding_javascript#external_javascript

For the media version the Web Asset Manager sets the default auto. This means that JHtml::_
('script', 'com_example/example.js', array('version'=> 'auto')); is called by de-
fault. What does this mean exactly? The media version is used to control the new loading of CSS and
JavaScript files. Specifically, the media version is reset during an update, installation, or uninstall. The
reason for this is that browsers cache CSS and JS files, so the following situation can occur: 1. A user
accesses a Joomla website, and the CSS and JS files are stored in the user’s browser. 2. Joomla is
updated, and in the update process, the contents of several CSS and JS files change. The file names
remain the same. 3. The user accesses the newly updated site, but the new CSS and JS files are not
reloaded because the user’s browser uses the cached versions instead. 4. if version'=> 'auto is
set, the src attribute of the <script> tag is different after the update, and the browser loads the new
file. For normal work with a Joomla website this setting is useful. When developing it might happen
that you want to reload web asset files more often. I use debug mode when developing, because this
way a new media version is forced on every HTTP request.

January 2023 Page 93


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

What does the attribute "defer": true mean? Scripts are loaded with async asynchronous or
parallel to other resources. defer promises the browser that the web page will not be changed
by instructions. More information at Mozilla.org.

The Joomla Web Assets Manager manages all assets in a Joomla installation. It is not mandatory to
include script files or stylesheets via this manager. All calls to HTMLHelper::_('stylesheet
or script ...) work, but these assets are appended after the Web Asset Manager assets. This
results in overriding styles that are set in the template. Thus, a user does not have the possibility
to manipulate by means of a user.css. However, it does have more advantages: If dependencies
are set correctly, no conflicts occur and necessary files are loaded by Joomla. For example, we
have set a dependency in the line "dependencies": ["core"],.

10.1.1.4. media/com_foos/js/admin-foos-modal.js

The following is the JavaScript code that causes a foo element to be selectable when a menu item is cre-
ated. We will assign the class select-link, which is the main element in the file, to the corresponding
button in the field later.

media/com_foos/js/admin-foos-modal.js

1 /* https://codeberg.org/astrid/j4examplecode/raw/branch/t7/src/media/
com_foos/js/admin-foos-modal.js */
2
3 ;(function () {
4 'use strict'
5
6 document.addEventListener('DOMContentLoaded', function () {
7 var elements = document.querySelectorAll('.select-link')
8
9 for (var i = 0, l = elements.length; l > i; i += 1) {
10 elements[i].addEventListener('click', function (event) {
11 event.preventDefault()
12 var functionName = event.target.getAttribute('data-function')
13
14 window.parent[functionName](
15 event.target.getAttribute('data-id'),
16 event.target.getAttribute('data-title'),
17 null,
18 null,
19 event.target.getAttribute('data-uri'),
20 event.target.getAttribute('data-language'),
21 null
22 )
23
24 if (window.parent.Joomla.Modal) {

Page 94 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

25 window.parent.Joomla.Modal.getCurrent().close()
26 }
27 })
28 }
29 })
30 })()

10.1.2. Modified files

10.1.2.1. administrator/components/com_foos/foos.xml

We have created a new JavaScript file. We place it in the media\js directory. So that it is copied when
the component is installed, we add the js folder in the section media of the installation manifest.

administrator/components/com_foos/foos.xml

1 <folder>src</folder>
2 <folder>tmpl</folder>
3 </files>
4 + <media folder="media/com_foos" destination="com_foos">
5 + <folder>js</folder>
6 + <filename>joomla.asset.json</filename>
7 + </media>
8 <!-- Back-end files -->
9 <administration>
10 <!-- Menu entries -->

Read in the preface why you choose the media directory ideally for assets like JavaScript files or
stylesheets.

10.1.2.2. components/com_foos/src/Model/FooModel.php

We no longer output static text. An item from the database is displayed. Therefore we rename the
getMsg method to getItem. We adjust the variable names and create a database query.

Make sure you update the DocBlock here. This sounds nit-picky and unimportant at the beginning.
In small extensions, it may still be minor. But later you may want to create documentation
automatically based on this information. Then you will be happy if they are correct.

components/com_foos/src/Model/FooModel.php

1 class FooModel extends BaseDatabaseModel


2 {
3 - * @var string message

January 2023 Page 95


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

4 + * @var string item


5 - protected $message;
6 + protected $_item = null;
7
8 - * Get the message
9 + * Gets a foo
10 - * @return string The message to be displayed to the user
11 + * @param integer $pk Id for the foo
12 + *
13 + * @return mixed Object or null
14 + *
15 + * @since __BUMP_VERSION__
16 - public function getMsg()
17 + public function getItem($pk = null)
18 {
19 $app = Factory::getApplication();
20 - $this->message = $app->input->get('show_text', "Hi");
21 + $pk = $app->input->getInt('id');
22 +
23 + if ($this->_item === null)
24 + {
25 + $this->_item = array();
26 + }
27 +
28 + if (!isset($this->_item[$pk]))
29 + {
30 + try
31 + {
32 + $db = $this->getDbo();
33 + $query = $db->getQuery(true);
34 +
35 + $query->select('*')
36 + ->from($db->quoteName('#__foos_details', 'a'))
37 + ->where('a.id = ' . (int) $pk);
38 +
39 + $db->setQuery($query);
40 + $data = $db->loadObject();
41 +
42 + if (empty($data))
43 + {
44 + throw new \Exception(Text::_('
COM_FOOS_ERROR_FOO_NOT_FOUND'), 404);
45 + }
46 +
47 + $this->_item[$pk] = $data;
48 + }
49 + catch (\Exception $e)
50 + {
51 + $this->setError($e);
52 + $this->_item[$pk] = false;
53 + }

Page 96 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

54 + }
55
56 - return $this->message;
57 + return $this->_item[$pk];
58 }
59 }

Joomla supports you in creating the database queries. If you use the available statementsa ,
Joomla will take care of security or different syntax in PostgreSQL and MySQL.
a
docs.joomla.org/accessing_the_database_using_jdatabase

10.1.2.3. components/com_foos/src/View/Foo/HtmlView.php

In the view we consequently replace $this->msg = $this->get('Msg'); with $this->item =


$this->get('Item');.

components/com_foos/src/View/Foo/HtmlView.php

1 class HtmlView extends BaseHtmlView


2 {
3 + /**
4 + * The item object details
5 + *
6 + * @var \JObject
7 + * @since __BUMP_VERSION__
8 + */
9 + protected $item;
10 +
11
12 public function display($tpl = null)
13 {
14 - $this->msg = $this->get('Msg');
15 + $this->item = $this->get('Item');
16
17 return parent::display($tpl);
18 }

10.1.2.4. components/com_foos/ tmpl/foo/default.php

We will customize the display of the name in the template. Here we access the item element and its
name property. In this way we can flexibly and easily add new properties in the future.

components/com_foos/tmpl/foo/default.php

1 \defined('_JEXEC') or die;
2 ?>

January 2023 Page 97


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

3
4 -Hello Foos: <?php echo $this->msg;
5 +<?php
6 +echo $this->item->name;

10.1.2.5. components/com_foos/ tmpl/foo/default.xml

We create an entry in the default.xml file for the new form field. This way we enable the selection of a
Foo element at the menu item. Worth mentioning are the entries addfieldprefix="FooNamespace
\Component\Foos\Administrator\Field" and type="modal_foo":

components/com_foos/tmpl/foo/default.xml

1 </layout>
2 <!-- Add fields to the request variables for the layout. -->
3 <fields name="request">
4 - <fieldset name="request">
5 + <fieldset name="request"
6 + addfieldprefix="FooNamespace\Component\Foos\Administrator\
Field"
7 + >
8 <field
9 - name="show_text"
10 - type="text"
11 - label="COM_FOOS_FIELD_TEXT_SHOW_LABEL"
12 - default="Hi"
13 + name="id"
14 + type="modal_foo"
15 + label="COM_FOOS_SELECT_FOO_LABEL"
16 + required="true"
17 + select="true"
18 + new="true"
19 + edit="true"
20 + clear="true"
21 />
22 </fieldset>
23 </fields>

10.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation. Copy the files in the media

Page 98 January 2023


10. Using the Database Data in the Frontend Joomla 4 - Developing Extensions

folder into the media folder of your Joomla 4 installation. A new installation is required to register
the file joomla.asset.json.

2. open the menu manager to create a menu item. Click on Menu and then on All Menu Items.

Then click on the New button and fill in all the necessary fields. You can find the appropriate Menu
Item Type by clicking the Select button. Make sure that you see a selection field instead of the text
field for entering static text. The selection field also contains a Select button.

Figure 10.1.: Joomla Create a menu item

3. click on the second Select and select an item. Make sure that a selected item can be cleared
using Clear.

January 2023 Page 99


Joomla 4 - Developing Extensions 10. Using the Database Data in the Frontend

Figure 10.2.: Joomla Create a menu item

4. save the menu item.

5. switch to the frontend and make sure that the menu item is created correctly. You will see the
title of the element you selected in the administration area.

Figure 10.3.: Joomla Create a menu item

Page 100 January 2023


Joomla 4 - Developing Extensions 11. Using Language Files

11. Using Language Files

Your goal was that descriptive text in the views are not mixed with the program code. So they are
uncomplicatedly changeable via the Joomla backend. This is possible for every user. Even the one
who is not familiar with the program code. By the way, this is the prerequisite for your extension to be
multilingual! For this reason you did not enter the real texts directly into the program code, but used
language strings instead. Specifically, by descriptive texts I mean the texts that are displayed in the
browser. You had prepared everything so that you use special files. These are easily changeable. So far
you have seen cryptic texts in the browser views. In this part we translate the unattractive language
strings into human readable words.

Even if your target audience speaks English and you only support this language it is important
to use a language file for texts you display in the front-end or back-end of the component. This
way it is possible for users to overwrite texts via language overridea without editing the source
code. Under some circumstances a user prefers to write first name instead of name in the column
header.
a
docs.joomla.org/j3.x:language_overrides_in_joomla

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t7...t8

11.1. Step by step

The frontend view and the administration area each use their own language files. Unlike the frontend,
where there is only one, the backend needs two - *.sys.ini and *.ini. Briefly explained: The file
with the extension sys.ini is used to translate the XML installation file as well as the menu elements.
The ini is responsible for the rest. This has the advantage that during the installation and for the
construction of the menu only the loading of small text files is necessary. A disadvantage is that
some language strings have to be entered twice. You can find out more in the article International
Enhancements which has a section on the file *.sys.ini1 .
1
docs.joomla.org/international_enhancements_for_version_1.6#the_new_.sys.ini

Page 101 January 2023


Joomla 4 - Developing Extensions 11. Using Language Files

The addition of the English language files is mandatory. All other languages are optional. The
reason for this is that if a file is missing, the English version is used by default. If a Frenchman
installs the extension - which contains German and English language files - on his Joomla with the
default language French, the texts will be displayed in English. Note: This only applies to missing
language files. A missing language key in a non-English language file will not be replaced with the
key from the English file. This means that it is mandatory that each file is complete. Joomla does
not search in different language version for a language string missing in a file.

11.1.1. Side Note: Special features

Would you like to see exactly how the ini file is parsed? At php.neta you will find the description of
the function that does this work.
a
php.net/manual/en/function.parse-ini-file.php

11.1.1.1. Commenting

You can mark a line as a comment using a semicolon ;.

1 ; Joomla Project
2 ; (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
3 ; License GNU General Public License version 2 or later; see LICENSE.
txt
4 Note : All ini files need to be saved as UTF-8
5 ....

11.1.1.2. Escaping

There are characters that have a special meaning - for example, the inverted commas " that mark the
beginning and end of the translation. This meaning can be cancelled with a backslash \.

1 ...
2 COM_CONTACT_CONTACT_REQUIRED="<strong class=\"red\">*</strong> Required
field"
3
4 ...

Language strings in Joomla have in the past used the string “QQ” to avoid double quotes within
the language INI files. This was a short-term solution. Older PHP versions were not able to handle
double quotes. For more information, see the PR 19024.a
a
github.com/joomla/joomla-cms/issues/19024

Page 102 January 2023


11. Using Language Files Joomla 4 - Developing Extensions

11.1.1.3. Variables

Sometimes the output of the language string depends on a variable. The function Text::sprintf
ensures that you do not have to compose the text in a complicated way in the programme code. Instead
of the variable in the language file, enter a character with the prefix %. For example, you can use %s.

Are you interested in the exact structure of the function Text::sprintf? You can find it in
Joomla in the file libraries/src/Language/Text.php.

1 ...
2 COM_CONTACT_CHECKED_OUT_BY="Checked out by %s"
3 ...

In the PHP code, the call then looks like this.

1 ...
2 Text::sprintf('COM_CONTACT_CHECKED_OUT_BY', $checkoutUser->name)
3 ...

The value of $checkoutUser->name is inserted instead of the first variable in the language string.
Here in the example instead of %s.

Is there more than one variable? You can specify which variable belongs to which language
string. For example, use %1$s, %2$s as in JLIB_DATABASE_ERROR_STORE_FAILED="%1$s:
:store failed<br>%2$s". The values are assigned in the order indicated in the number after
the “%” character.

11.1.1.4. singular/singular

There is singular or singular and plural or plural and the Joomla language strings support this.

Let us take the call

1 ...
2 $message = Text::plural('COM_FOOS_N_ITEMS_FEATURED', count($ids));
3 ...

as an example.

Depending on whether count($ids) has the value 1 or 2 the language string COM_FOOS_N_
ITEMS_FEATURED_1 or COM_FOOS_N_ ITEMS_FEATURED_2 is used. If count($ids) has neither 1
nor 2, COM_FOOS_N_ ITEMS_FEATURED is used.

1 ...
2 COM_FOOS_N_ITEMS_FEATURED="%d foos featured."

January 2023 Page 103


Joomla 4 - Developing Extensions 11. Using Language Files

3 COM_FOOS_N_ITEMS_FEATURED_1="Foo featured."
4 COM_FOOS_N_ITEMS_FEATURED_2="Two foos featured."
5 ...

11.1.2. New files

Create six files to support the German language in addition to English. Each file is structured as follows:
One language string is inserted per line. The left side of the equal sign in the language string, for exam-
ple COM_FOOS_ CONFIGURATION" in COM_FOOS_ CONFIGURATION="Foo Options", is always in
upper case. Normally the extension name is at the beginning, in our case it is COM_FOOS. After that you
ideally add a short description. Here you describe briefly what this string is used for. Make sure that you
do not use spaces. Only letters and underscores are allowed. The right side of the language string, for
example Foo Options" in COM_FOOS_ CONFIGURATION = "Foo Options", is the actual text that
will be displayed on the site. When your extension is translated into another language, the translator
only changes this right side of the language string in his language file. The right side is enclosed in
quotation marks.

11.1.2.1. administrator/components/com_foos/ language/de-DE/com_foos.ini

We add the German language version for the administration area with the files “administrator/components/com_foos/lan
DE/com_foos.ini” and “administrator/components/com_foos/language/en-DE/com_foos.sys.ini”.

Don’t be confused if you see a lot of texts in the sample data. These are not all used at the moment.
I’m already inserting the text for the future chapters here.

administrator/components/com_foos/language/de-DE/com_foos.ini

1
2 COM_FOOS="[PROJECT_NAME]"
3 COM_FOOS_CONFIGURATION="Foo Optionen"
4 COM_FOOS_FOOS="Foos"
5 COM_FOOS_CATEGORIES="Kategorien"
6 ...

Naming conventions: Each language file is marked with an abbreviation, which is defined in
ISO-639a and ISO-3166b : The first two lower case letters name the language. For German this is
de and en for English. After the hyphen, the two capital letters indicate the country. For example,
Swiss German can be distinguished from DE by CH or Austrian by AT. A folder named de-CH
contains the translation for Switzerland and de-AT the Austrian variant.
a
en.wikipedia.org/wiki/iso_639

Page 104 January 2023


11. Using Language Files Joomla 4 - Developing Extensions

b
en.wikipedia.org/wiki/iso_3166

11.1.2.2. administrator/components/com_foos/ language/de-DE/com_foos.sys.ini

As mentioned before, you need two language files for the backend: one ending with .ini and one
ending with sys.ini. The sys.ini is primarily used during installation and for displaying the menu
items and the ini for everything else.

administrator/components/com_foos/language/de-DE/com_foos.sys.ini

1
2 COM_FOOS="[PROJECT_NAME]"
3 COM_FOOS_XML_DESCRIPTION="Foo Komponente"
4 ...
5 COM_FOOS_INSTALLERSCRIPT_PREFLIGHT="<p>Alles hier passiert vor der
Installation / Aktualisierung / Deinstallation der Komponente</p>"
6 COM_FOOS_INSTALLERSCRIPT_UPDATE="<p>TDie Komponente wurde aktualisiert
</p>"
7 COM_FOOS_INSTALLERSCRIPT_UNINSTALL="<p>Die Komponente wurde
deinstalliert</p>"
8 COM_FOOS_INSTALLERSCRIPT_INSTALL="<p>Die Komponente wurde installiert</
p>"
9 COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT="<p>Alles hier passiert nach der
Installation / Aktualisierung / Deinstallation der Komponente</p>"
10 ...

11.1.2.3. administrator/components/com_foos/ language/en-GB/com_foos.ini

I had already written it: The English versions of the language files should always be available as a
fallback.

administrator/components/com_foos/language/en-GB/com_foos.ini

1
2 COM_FOOS="[PROJECT_NAME]"
3 COM_FOOS_CONFIGURATION="Foo Options"
4 COM_FOOS_FOOS="Foos"
5 COM_FOOS_CATEGORIES="Categories"
6 ...

11.1.2.4. administrator/components/com_foos/ language/en-GB/com_foos.sys.ini

We also add the file administrator/components/com_foos/language/en-GB/com_foos.sys


.ini as a fallback for all non-German or English Joomla installations.

January 2023 Page 105


Joomla 4 - Developing Extensions 11. Using Language Files

administrator/components/com_foos/language/en-GB/com_foos.sys.ini

1
2 COM_FOOS="[PROJECT_NAME]"
3 COM_FOOS_CONFIGURATION="Foo Options"
4 ...
5 COM_FOOS_INSTALLERSCRIPT_PREFLIGHT="<p>Anything here happens before the
installation/update/uninstallation of the component</p>"
6 COM_FOOS_INSTALLERSCRIPT_UPDATE="<p>The component has been updated</p>"
7 COM_FOOS_INSTALLERSCRIPT_UNINSTALL="<p>The component has been
uninstalled</p>"
8 COM_FOOS_INSTALLERSCRIPT_INSTALL="<p>The component has been installed</
p>"
9 COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT="<p>Anything here happens after the
installation/update/uninstallation of the component</p>"
10 ...

11.1.2.5. components/com_foos/ language/de-DE/com_foos.ini

In the frontend there is only the .ini - so no sys.ini. The file components/com_foos/language
/en-DE/com_foos.ini implements the German language.

components/com_foos/language/de-DE/com_foos.ini

1
2 COM_FOOS_NAME="Vorame: "
3 ...

11.1.2.6. components/com_foos/ language/en-GB/com_foos.ini

We add the English version to the file components/com_foos/language/en-GB/com_foos.ini


so that it is used as a fallback in all languages other as German.

components/com_foos/language/en-GB/com_foos.ini

1
2 COM_FOOS_NAME="Surname: "
3 ...

In the next chapters more language strings will be added. I will not mention them separately there.
I have already integrated most of them into the sample files in this lesson. This way I avoid that
the files appear in the diff view and blow it up unnecessarily. Specifically, I mean the diff view of
the program code of the different chapters on Github, which I refer to here in the text.

Page 106 January 2023


11. Using Language Files Joomla 4 - Developing Extensions

11.1.3. Modified files

11.1.3.1. administrator/components/com_foos/foos.xml

To make sure that the language files are copied to Joomla Core when the extension is installed, we add
the <folder>language</folder> entry for the frontend and the backend to the manifest.

administrator/components/com_foos/foos.xml

1
2 </uninstall>
3 <!-- Frond-end files -->
4 <files folder="components/com_foos">
5 + <folder>language</folder>
6 <folder>src</folder>
7 <folder>tmpl</folder>
8 </files>
9
10 <files folder="administrator/components/com_foos">
11 <filename>foos.xml</filename>
12 <folder>forms</folder>
13 + <folder>language</folder>
14 <folder>services</folder>
15 <folder>sql</folder>
16 <folder>src</folder>

11.1.3.1.1. Where are the language files ideally stored? Joomla’s own components store the files
for the administration area in the folder /administrator/language/en-GB/ and those for the
site in the folder /language/en-GB/. This is the first place Joomla looks for the language files.
For this reason, it was common for extension developers to put their files here. Sometimes it is
more straightforward to put them in your own component folder. In our example, this is the folder
administrator/components/com_foos/language/en-GB/ for the backend and components/
com_foos/language/en-GB/ for the frontend. This is the place where Joomla looks for the language
file if it doesn’t find anything suitable in the Joomla Core directories /administrator/language/
en-GB / and / language/en-GB respectively.

You want to store your language files in the same directory as the Joomla core extensions? To place
your files together with Joomla’s own language files, you add the <language> tag to the installation
file. Here is an example from com_contact

1 ...
2 <files folder="components/com_contact">
3 ...
4 </files>
5

January 2023 Page 107


Joomla 4 - Developing Extensions 11. Using Language Files

6 <languages folder="site">
7 <language tag="en-GB">language/en-GB.com_contact.ini</language>
8 </languages>
9
10 <administration>
11 ...
12 <languages folder="admin">
13 <language tag="en-GB">language/en-GB.com_contact.ini</
language>
14 <language tag="en-GB">language/en-GB.com_contact.sys.ini</
language>
15 </languages>
16 </administration>
17 ...

where you need to adjust the value of the folder parameter to your structure:

1 ...
2 <files folder="components/com_foos">
3 ...
4 </files>
5
6 <languages folder="language">
7 <language tag="en-GB">language/en-GB.com_foos.ini</language>
8 </languages>
9
10 <administration>
11 ...
12 <languages folder="administrator/language">
13 <language tag="en-GB">language/en-GB.com_foos.ini</language
>
14 <language tag="en-GB">language/en-GB.com_foos.sys.ini</
language>
15 </languages>
16 </administration>
17 ...

11.1.3.2. components/com_foos/ tmpl/foo/default.php

One last step is still missing. The own use of the language strings. So far we have printed the name
without a label in the frontend via echo $this->item->name;. Now we add a label that takes differ-
ent languages into account. The following code causes the string that is entered in the corresponding
language file to be printed in the frontend. This is done by the command Text::_('COM_FOOS_NAME
'). If there is a Spanish language file with the entry COM_FOOS_FIELD_NAME_LABEL="Nombre" and
the Spanish language is active in the frontend, then Nombre is printed. If the German language is
set and there is a German language file with the entry COM_FOOS_FIELD_NAME_LABEL="Name", the

Page 108 January 2023


11. Using Language Files Joomla 4 - Developing Extensions

word Name is displayed. If the Spanish language is active without a Spanish language file, the English
language file is used.
components/com_foos/tmpl/foo/default.php

1 \defined('_JEXEC') or die;
2 -?>
3
4 -<?php
5 -echo $this->item->name;
6 +use Joomla\CMS\Language\Text;
7 +
8 +echo Text::_('COM_FOOS_NAME') . $this->item->name;

11.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the files from the previous part. If you do a new installation, you will
notice that the hints in the installation script are now translated.

In case of discovery, the texts may be hiddena .


a
github.com/joomla/joomla-cms/issues/36343

Figure 11.1.: Joomla language files are used

January 2023 Page 109


Joomla 4 - Developing Extensions 11. Using Language Files

2. open the view of your component in the administration area and frontend and make sure that
the texts are readable and not cryptic anymore.

Figure 11.2.: Joomla language files are used

3. try out the new feature. Create language files for different languages and change the default
language in Joomla. Make sure that Joomla translates correctly.

4. create a language override and make sure that it is used.

Figure 11.3.: Joomla language files - language override creation

Page 110 January 2023


11. Using Language Files Joomla 4 - Developing Extensions

Figure 11.4.: Joomla language files - language override usage

January 2023 Page 111


Joomla 4 - Developing Extensions 12. Configuration

12. Configuration

Are there things you plan to offer configurable? Then this part is important for you. Here I show you how
to add a configuration to your component in the Joomla typical way. We create the global configuration
for our component!

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t8...t9

12.1. Step by step

12.1.1. New files

12.1.1.1. administrator/components/com_foos/ config.xml

We add the config.xml file. This implements the configuration parameters. In this XML file you can
use all standard form field types1 as usual or implement your own types analogous to the already
created modal field FieldFoo.

We use a selection field of type type="list". We minimise the translation work by using the global
language strings JNO and JYES. All texts that Joomla translates in the file language/en-GB/joomla
.ini can be used globally.

administrator/components/com_foos/config.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <config>
4 <fieldset
5 name="foo"
6 label="COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DISPLAY"
7 description="COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DESC"
8 >
9 <field
1
docs.joomla.org/form_field

Page 113 January 2023


Joomla 4 - Developing Extensions 12. Configuration

10 name="show_foo_name_label"
11 type="list"
12 label="COM_FOOS_FIELD_FOO_SHOW_CATEGORY_LABEL"
13 default="1"
14 >
15 <option value="0">JNO</option>
16 <option value="1">JYES</option>
17 </field>
18 </fieldset>
19 </config>

12.1.2. Modified files

12.1.2.1. administrator/components/com_foos/foos.xml

The addition in the foos.xml file ensures that the config.xml file is copied during installation and
Joomla can thus access it later.

administrator/components/com_foos/foos.xml

1 </submenu>
2 <files folder="administrator/components/com_foos">
3 <filename>foos.xml</filename>
4 + <filename>config.xml</filename>
5 <folder>forms</folder>
6 <folder>language</folder>
7 <folder>services</folder>

12.1.2.2. administrator/components/com_foos/ src/View/Foos/HtmlView.php

The line $toolbar->preferences('com_foos'); ensures that the button ‘Options’ is inserted


at the top of the administration area. This way, the configuration is easily accessible later in the
backend.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 ToolbarHelper::title(Text::_('COM_FOOS_MANAGER_FOOS'), 'address
foo');
2
3 $toolbar->addNew('foo.add');
4 +
5 + $toolbar->preferences('com_foos');
6 }
7
8 }

Page 114 January 2023


12. Configuration Joomla 4 - Developing Extensions

12.1.2.3. components/com_foos/src/Model/FooModel.php

The populateState method ensures that the State object is correctly filled and available to all code.
We add the new parameter here for the site part.

populateState() is called automatically when we use getState() for the first time. If we need
something special in the method, we override it in our own model - as in the following code example.

You may wonder which populateState() method is called when nothing is implemented in
our own extension. Quite simple: FooModel (components/com_foos/src/Model/FooModel
.php) extends BaseDatabaseModel (libraries/src/MVC/Model/BaseDatabaseModel
.php), which in turn extends BaseModel (libraries/src/MVC/Model/BaseModel.
php). The latter implements StateBehaviorTrait (libraries/src/MVC/Model/
StateBehaviorTrait.php) in which you find the method protected function
populateState(){}. This method is empty and does nothing. But: It is callable. It is
extremely helpful to follow up on such questions. This is how you get to know Joomla.

components/com_foos/src/Model/FooModel.php

1 return $this->_item[$pk];
2 }
3 +
4 + protected function populateState()
5 + {
6 + $app = Factory::getApplication();
7 +
8 + $this->setState('foo.id', $app->input->getInt('id'));
9 + $this->setState('params', $app->getParams());
10 + }
11 }

12.1.2.4. components/com_foos/ tmpl/foo/default.php

Finally, we replace echo Text::_('COM_FOOS_NAME'). $this->item->name;. We only show


the label if in the status the parameter is set to true or 1.

components/com_foos/tmpl/foo/default.php

1 use Joomla\CMS\Language\Text;
2
3 -echo Text::_('COM_FOOS_NAME') . $this->item->name;
4 +if ($this->get('State')->get('params')->get('show_foo_name_label'))
5 +{
6 + echo Text::_('COM_FOOS_NAME');
7 +}

January 2023 Page 115


Joomla 4 - Developing Extensions 12. Configuration

8 +
9 +echo $this->item->name;

12.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the files from the previous part.

2. open the view of your component in the administration area and make sure that you see the
button Options in the upper right corner.

Figure 12.1.: Joomla Configuration - Button in Backend

3. click on Options and adjust the display of the label according to your wishes.

Page 116 January 2023


12. Configuration Joomla 4 - Developing Extensions

Figure 12.2.: Joomla Configuration - Global configuration

4. open as last, the view in the frontend. Does the display of the label behave as you have set it in
the administration area?

Figure 12.3.: Joomla Configuration - Frontend

January 2023 Page 117


Joomla 4 - Developing Extensions 13. Access Control List (ACL)

13. Access Control List (ACL)

Not everyone has the right to edit all content. For this purpose Joomla offers an access control list, the
ACL. With this you manage user rights in your component.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t9...t10

13.1. Step by step

13.1.1. New files

13.1.1.1. administrator/components/com_foos/ access.xml

First, we set all possible permissions in an XML file. Each component can define individual permissions.
I orientate myself here on the usual actions in Joomla. core.admin thereby determines which groups
are allowed to configure the permissions at component level via the options button in the toolbar.
core.manage determines which groups are allowed to access the backend of the component.

The Access Control List Tutoriala is a helpful read.


a
docs.joomla.org/j3.x:access_control_list_tutorial

administrator/components/com_foos/access.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <access component="com_foos">
4 <section name="component">
5 <action name="core.admin" title="JACTION_ADMIN" />
6 <action name="core.options" title="JACTION_OPTIONS" />
7 <action name="core.manage" title="JACTION_MANAGE" />
8 <action name="core.create" title="JACTION_CREATE" />
9 <action name="core.delete" title="JACTION_DELETE" />
10 <action name="core.edit" title="JACTION_EDIT" />
11 <action name="core.edit.state" title="JACTION_EDITSTATE" />

Page 119 January 2023


Joomla 4 - Developing Extensions 13. Access Control List (ACL)

12 <action name="core.edit.own" title="JACTION_EDITOWN" />


13 </section>
14 </access>

13.1.1.2. administrator/components/com_foos/ sql/updates/mysql/10.0.0.sql

Joomla stores the permissions in the database. Regarding the database, only changes are relevant
during a Joomla update. We enter these in the file administrator/components/com_foos/sql
/updates/mysql/VERSIONSNUMMER.sql, here this is specifically administrator/components/
com_foos/sql/updates/mysql/10.0.0.sql. This file is only called during an update. In case of a
new installation the database will be set up correctly via the main file administrator/components
/com_foos/sql/install.mysql.utf8.sql.

administrator/components/com_foos/sql/updates/mysql/10.0.0.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `access ` int(10) unsigned NOT
NULL DEFAULT 0 AFTER `alias`;
3
4 ALTER TABLE `#__foos_details ` ADD KEY `idx_access ` ( `access`);

13.1.2. Modified files

13.1.2.1. administrator/components/com_foos/ config.xml

We set the permissions for the entire component in the configuration. For this we integrate a special
form field. Joomla offers the type rules for this.

administrator/components/com_foos/config.xml

1 <option value="1">JYES</option>
2 </field>
3 </fieldset>
4 + <fieldset
5 + name="permissions"
6 + label="JCONFIG_PERMISSIONS_LABEL"
7 + description="JCONFIG_PERMISSIONS_DESC"
8 + >
9 + <field
10 + name="rules"
11 + type="rules"
12 + label="JCONFIG_PERMISSIONS_LABEL"
13 + validate="rules"
14 + filter="rules"
15 + component="com_foos"

Page 120 January 2023


13. Access Control List (ACL) Joomla 4 - Developing Extensions

16 + section="component"
17 + />
18 + </fieldset>
19 </config>

13.1.2.2. administrator/components/com_foos/foos.xml

To make sure that everything runs smoothly during the installation, we add the new file and folder
sql/updates/mysql and access.xml here.

administrator/components/com_foos/foos.xml

1 <file driver="mysql" charset="utf8">sql/uninstall.mysql.


utf8.sql</file>
2 </sql>
3 </uninstall>
4 + <update> <!-- Runs on update -->
5 + <schemas>
6 + <schemapath type="mysql">sql/updates/mysql</schemapath>
7 + </schemas>
8 + </update>
9 <!-- Frond-end files -->
10 <files folder="components/com_foos">
11 <folder>language</folder>
12
13 <menu link="option=com_foos">COM_FOOS</menu>
14 </submenu>
15 <files folder="administrator/components/com_foos">
16 + <filename>access.xml</filename>
17 <filename>foos.xml</filename>
18 <filename>config.xml</filename>
19 <folder>forms</folder>

13.1.2.3. administrator/components/com_foos/ forms/foo.xml

We extend the form for creating a new Foo item with the possibility to set permissions for a single item.
We add the field name="access".

administrator/components/com_foos/forms/foo.xml

1 size="45"
2 hint="JFIELD_ALIAS_PLACEHOLDER"
3 />
4 +
5 + <field
6 + name="access"
7 + type="accesslevel"

January 2023 Page 121


Joomla 4 - Developing Extensions 13. Access Control List (ACL)

8 + label="JFIELD_ACCESS_LABEL"
9 + size="1"
10 + />
11 </fieldset>
12 </form>

13.1.2.4. administrator/components/com_foos/ sql/install.mysql.utf8.sql

The SQL script for a new installation of the component is also extended with the necessary fields. In
this way we ensure that the database is also completely set up for a new installation.

administrator/components/com_foos/sql/install.mysql.utf8.sql

1 ('Nina'),
2 ('Astrid'),
3 ('Elmar');
4 +
5 +ALTER TABLE `#__foos_details ` ADD COLUMN `access ` int(10) unsigned
NOT NULL DEFAULT 0 AFTER `alias`;
6 +
7 +ALTER TABLE `#__foos_details ` ADD KEY `idx_access ` ( `access`);

13.1.2.5. administrator/components/com_foos/ src/Model/FoosModel.php

If you are not familiar with SQL, the database query in the model will now seem complex. It is now
necessary to combine data from two database tables. One table is #__viewlevels which manages
the permissions of com_user. The other table is that of our example component which is named
#__foos_details. Don’t feel discouraged by this. Joomla supports you in creating the queries.

administrator/components/com_foos/src/Model/FoosModel.php

1
2 // Select the required fields from the table.
3 $query->select(
4 - $db->quoteName(array('id', 'name', 'alias'))
5 + $db->quoteName(array('a.id', 'a.name', 'a.alias', 'a.access
'))
6 );
7 - $query->from($db->quoteName('#__foos_details'));
8 +
9 + $query->from($db->quoteName('#__foos_details', 'a'));
10 +
11 + // Join over the asset groups.
12 + $query->select($db->quoteName('ag.title', 'access_level'))
13 + ->join(
14 + 'LEFT',

Page 122 January 2023


13. Access Control List (ACL) Joomla 4 - Developing Extensions

15 + $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->


quoteName('ag.id') . ' = ' . $db->quoteName('a.access')
16 + );
17
18 return $query;
19 }

As a reminder, Joomla supports you in creating the database queries. If you use the available
statementsa , Joomla will take care of security or different syntax in PostgreSQL and MySQL for
you.
a
docs.joomla.org/accessing_the_database_using_jdatabase

13.1.2.6. administrator/components/com_foos/ src/View/Foos/HtmlView.php

A button to create an element is only useful if this is allowed. Therefore we change the view - $canDo
is added. The call $canDo = ContentHelper::getActions('com_foos'); gets the actions you
defined in the file administrator/components/com_foos/access.xml at the beginning of this
chapter.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1
2 \defined('_JEXEC') or die;
3
4 +use Joomla\CMS\Helper\ContentHelper;
5 use Joomla\CMS\Language\Text;
6 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
7 use Joomla\CMS\Toolbar\Toolbar;
8
9 protected function addToolbar()
10 {
11 + $canDo = ContentHelper::getActions('com_foos');
12 +
13 // Get the toolbar object instance
14 $toolbar = Toolbar::getInstance('toolbar');
15
16 ToolbarHelper::title(Text::_('COM_FOOS_MANAGER_FOOS'), 'address
foo');
17
18 - $toolbar->addNew('foo.add');
19 + if ($canDo->get('core.create'))
20 + {
21 + $toolbar->addNew('foo.add');
22 + }
23
24 - $toolbar->preferences('com_foos');
25 + if ($canDo->get('core.options'))

January 2023 Page 123


Joomla 4 - Developing Extensions 13. Access Control List (ACL)

26 + {
27 + $toolbar->preferences('com_foos');
28 + }
29 }
30 -
31 }

13.1.2.7. administrator/components/com_foos/ tmpl/foo/edit.php

The entry <?php echo $this->getForm()->renderField(access); is necessary to include the


field in the form, which we have already configured in the XML file. Only this way it is possible to change
the permissions per element.

administrator/components/com_foos/tmpl/foo/edit.php

1 <form action="<?php echo Route::_('index.php?option=com_foos&layout='


. $layout . $tmpl . '&id=' . (int) $this->item->id); ?>" method="
post" name="adminForm" id="foo-form" class="form-validate">
2 <?php echo $this->getForm()->renderField('name'); ?>
3 <?php echo $this->getForm()->renderField('alias'); ?>
4 + <?php echo $this->getForm()->renderField('access'); ?>
5 <input type="hidden" name="task" value="">
6 <?php echo HTMLHelper::_('form.token'); ?>
7 </form>

13.1.2.8. administrator/components/com_foos/ tmpl/foos/default.php

Last but not least, we include a column in the overview for the authorization display.

administrator/components/com_foos/tmpl/foos/default.php

1 <th scope="col" style="width:1%" class=


"text-center d-none d-md-table-cell"
>
2 <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_NAME');
?>
3 </th>
4 + <th scope="col" style="width:10%" class
="d-none d-md-table-cell">
5 + <?php echo TEXT::_('
JGRID_HEADING_ACCESS') ?>
6 + </th>
7 <th scope="col">
8 <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_ID');
?>

Page 124 January 2023


13. Access Control List (ACL) Joomla 4 - Developing Extensions

9 </th>
10
11 <?php echo $editIcon; ?><?php
echo $this->escape($item->
name); ?></a>
12
13 </th>
14 + <td class="small d-none d-md-table-cell
">
15 + <?php echo $item->access_level; ?>
16 + </td>
17 <td class="d-none d-md-table-cell">
18 <?php echo $item->id; ?>
19 </td>

Note that I have not covered all cases here where permissions need to be handled. This description
is intended as a best practice.

13.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder to the administrator folder of your Joomla 4 installation. Install your component as
described in part one, after copying all files. Joomla will update the database for you during the
installation.

2. create a new item in your component. Make sure that you are offered a checkbox for saving a
permission. The value you enter here will be saved with the item and can be queried when it is
displayed in a list.

January 2023 Page 125


Joomla 4 - Developing Extensions 13. Access Control List (ACL)

Figure 13.1.: Joomla Configuration - Set permissions in an element

3. consider how you use the permission set in point 2 for your purposes? So far, the value is only
stored, we do not use it.

4. for an overview, the value is additionally displayed in the main view.

Figure 13.2.: Joomla Configuration - Display permissions in the overview list

5. Open the options of the global configuration. Here you have the possibility to set the permissions
for the use of the component globally.

Page 126 January 2023


13. Access Control List (ACL) Joomla 4 - Developing Extensions

Figure 13.3.: Joomla Configuration - Permissions in the Global Configuration

6. Play with the settings. Allow the administrator to use the component in the backend. But remove
his right to create new elements in the extension. After that, log in as a simple administrator and
make sure that the “New” button has disappeared.

13.3. Links

Access Control List Tutorial1

1
docs.joomla.org/j3.x:access_control_list_tutorial

January 2023 Page 127


Joomla 4 - Developing Extensions 14. Server Side Validierung

14. Server Side Validierung

Your component is user-friendly. User experience (UX) or user experience is on everyone’s lips. If a
user enters incorrect data, it’s important to you that they get an explanation. This is where we use
validation.

In server-side validation, the input submitted by the user is sent to the server and validated using
the scripting language. In the case of Joomla, this is PHP. After the validation process on the server
side, the feedback is sent back to the client from a new dynamically generated web page. It is safe
to validate user input from the server. Malicious attackers have no easy game this way. Client-side
scripting languages are easier to trick. Intruders bypass them to send malicious input to the server.

Since both validation methods (server and client) have their own importance, it is recommended
to use them simultaneously. Server-side validation is more secure. The client-side one is more
user-friendly!

This part covers the server-side validation in Joomla 4.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t10...t11a

14.1. Step by step

14.1.1. New files

14.1.1.1. administrator/components/com_foos/ src/Rule/LetterRule.php

The main goal here is not to learn meaningful validation. Rather, I’m showing you how to integrate your
rules into Joomla. That’s why you see here only a rudimentary example: In the name it is forbidden to
insert a number from now on. In concrete terms, this means: Astrid is allowed. Astrid9 is not allowed.
For this we create the file LetterRule.php.

Page 129 January 2023


Joomla 4 - Developing Extensions 14. Server Side Validierung

Here in the example I only use the regular expression to be checked in the class LetterRule.php.
Of course it is possible to integrate complex checks using functions.

administrator/components/com_foos/src/Rule/LetterRule.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Rule;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Form\FormRule;
9
10 class LetterRule extends FormRule
11 {
12 protected $regex = '^([a-z]+)$';
13
14 protected $modifiers = 'i';
15 }

It is not necessary to implement the test method in your file. You inherit it from the class FormRule
, which is implemented in the file /libraries/src/Form/FormRule.php. In it you will find the
code

1 ...
2 protected $regex;
3 ...
4 public function test(\SimpleXMLElement $element, $value, $group = null,
Registry $input = null, Form $form = null)
5 {
6 ...
7 // Test the value against the regular expression.
8 if (preg_match(\chr(1) . $this->regex . \chr(1) . $this->modifiers,
$value)) {
9 return true;
10 }
11
12 return false;
13 }

14.1.2. Modified files

To make Joomla apply the rule in the LetterRule.php file to the text field for entering the name, we
modify the form file.

Page 130 January 2023


14. Server Side Validierung Joomla 4 - Developing Extensions

14.1.2.1. administrator/components/com_foos/ forms/foo.xml

What has changed is <fieldset addruleprefix="FooNamespace\Component\Foos\


Administrator\Rule"> and validate="Letter". The parameter addruleprefix="
FooNamespace\Component\Foos\Administrator\Rule" ensures that the form searches
for rules in the namespace FooNamespace\Component\Foos\Administrator\Rule and
validate="Letter" indicates that the rule Letter, i.e. according to Joomla standard the class
LetterRule, is applied.

administrator/components/com_foos/forms/foo.xml

1 <?xml version="1.0" encoding="utf-8"?>


2 <form>
3 - <fieldset>
4 + <fieldset addruleprefix="FooNamespace\Component\Foos\Administrator\
Rule">
5 <field
6 name="id"
7 type="number"
8
9 <field
10 name="name"
11 type="text"
12 + validate="Letter"
13 label="COM_FOOS_FIELD_NAME_LABEL"
14 size="40"
15 required="true"

14.2. Example taken from Joomla Core

To show you what is possible I add below two examples from Joomla Core as inspiration.

14.2.1. Username

For the username, the Joomla database is used to check if the name already exists. In this case false
is returned. Otherwise the test is successful.

1 <?php
2
3 namespace Joomla\CMS\Form\Rule;
4
5 use Joomla\CMS\Form\Form;
6 use Joomla\CMS\Form\FormRule;
7 use Joomla\Database\DatabaseAwareInterface;

January 2023 Page 131


Joomla 4 - Developing Extensions 14. Server Side Validierung

8 use Joomla\Database\DatabaseAwareTrait;
9 use Joomla\Database\ParameterType;
10 use Joomla\Registry\Registry;
11
12 class UsernameRule extends FormRule implements DatabaseAwareInterface
13 {
14 use DatabaseAwareTrait;
15
16 public function test(\SimpleXMLElement $element, $value, $group =
null, Registry $input = null, Form $form = null)
17 {
18 // Get the database object and a new query object.
19 $db = $this->getDatabase();
20 $query = $db->getQuery(true);
21
22 // Get the extra field check attribute.
23 $userId = ($form instanceof Form) ? (int) $form->getValue('id')
: 0;
24
25 // Build the query.
26 $query->select('COUNT(*)')
27 ->from($db->quoteName('#__users'))
28 ->where(
29 [
30 $db->quoteName('username') . ' = :username',
31 $db->quoteName('id') . ' <> :userId',
32 ]
33 )
34 ->bind(':username', $value)
35 ->bind(':userId', $userId, ParameterType::INTEGER);
36
37 // Set and query the database.
38 $db->setQuery($query);
39 $duplicate = (bool) $db->loadResult();
40
41 if ($duplicate) {
42 return false;
43 }
44
45 return true;
46 }
47 }

14.2.2. URL

The URL field does not require a regular expression. Various requirements are queried successively. If a
requirement is not given, false is returned. Otherwise, the test is successful.

1 <?php

Page 132 January 2023


14. Server Side Validierung Joomla 4 - Developing Extensions

2
3 namespace Joomla\CMS\Form\Rule;
4
5 use Joomla\CMS\Form\Form;
6 use Joomla\CMS\Form\FormRule;
7 use Joomla\CMS\Language\Text;
8 use Joomla\Registry\Registry;
9 use Joomla\String\StringHelper;
10 use Joomla\Uri\UriHelper;
11
12 class UrlRule extends FormRule
13 {
14 public function test(\SimpleXMLElement $element, $value, $group =
null, Registry $input = null, Form $form = null)
15 {
16 // If the field is empty and not required, the field is valid.
17 $required = ((string) $element['required'] === 'true' || (
string) $element['required'] === 'required');
18
19 if (!$required && empty($value)) {
20 return true;
21 }
22
23 $urlParts = UriHelper::parse_url($value);
24
25 // See https://www.w3.org/Addressing/URL/url-spec.txt
26 // Use the full list or optionally specify a list of permitted
schemes.
27 if ($element['schemes'] == '') {
28 $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', '
mailto', 'news', 'prospero', 'telnet', 'rlogin', 'sftp',
'tn3270', 'wais',
29 'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', '
fax', 'modem', 'git');
30 } else {
31 $scheme = explode(',', $element['schemes']);
32 }
33
34 /*
35 if ($urlParts === false || !\array_key_exists('scheme',
$urlParts)) {
36 /*
37 if ($urlParts === false || !$element['relative']) {
38 $element->addAttribute('message', Text::sprintf('
JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value
, implode(', ', $scheme)));
39
40 return false;
41 }
42
43 // The best we can do for the rest is make sure that the

January 2023 Page 133


Joomla 4 - Developing Extensions 14. Server Side Validierung

path exists and is valid UTF-8.


44 if (!\array_key_exists('path', $urlParts) || !StringHelper
::valid((string) $urlParts['path'])) {
45 return false;
46 }
47
48 // The internal URL seems to be good.
49 return true;
50 }
51
52 // Scheme found, check all parts found.
53 $urlScheme = (string) $urlParts['scheme'];
54 $urlScheme = strtolower($urlScheme);
55
56 if (\in_array($urlScheme, $scheme) == false) {
57 return false;
58 }
59
60 // For some schemes here must be two slashes.
61 $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'wais
', 'prospero', 'sftp', 'telnet', 'git');
62
63 if (\in_array($urlScheme, $scheme) && substr($value, \strlen(
$urlScheme), 3) !== '://') {
64 return false;
65 }
66
67 // The best we can do for the rest is make sure that the
strings are valid UTF-8
68 // and the port is an integer.
69 if (\array_key_exists('host', $urlParts) && !StringHelper::
valid((string) $urlParts['host'])) {
70 return false;
71 }
72
73 if (\array_key_exists('port', $urlParts) && !\is_int((int)
$urlParts['port'])) {
74 return false;
75 }
76
77 if (\array_key_exists('path', $urlParts) && !StringHelper::
valid((string) $urlParts['path'])) {
78 return false;
79 }
80
81 return true;
82 }
83 }

Page 134 January 2023


14. Server Side Validierung Joomla 4 - Developing Extensions

14.3. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder into the administrator folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the ones from the previous part.

2. Open the view of your component in the administration area and create a new item or edit an
existing one. Enter a number in the text field for the name.

3. Then edit another field, for example set the access to Registered.

4. make sure that you don’t get any warning at this time.

5. try to save your input at the end. This is not possible. You will see a warning.

Figure 14.1.: Joomla Validation - Server Side Validation

Did you notice it? You may see the warning only after you have made a lot of changes in the form
and want to save all changes. In this small extension it does not matter. In large forms, the hint at
the end can be frustrating. A user may want to see it immediately after the incorrect input. So it is
possible to act immediately and avoid unnecessary work. It is also frustrating to have to do all
the input again. This is where client-side validation comes into play. We will look at this in the
next part.

January 2023 Page 135


Joomla 4 - Developing Extensions 15. Client Side Validierung

15. Client Side Validierung

Our goal in this part: when we enter a number in the name field, an error message is displayed
immediately after we leave the field. In server-side validation, the message was not issued until after
the form was sent to the server via the Save button.

In client-side validation, we provide a better user experience by responding quickly at the browser level.
Here, all inputs in the user’s browser are validated immediately. Client-side validation does not require
a query to the server, thus reducing the load on the server and the network. This type of validation
works on the browser side using scripting languages such as JavaScript or with HTML5 attributes.

For example, if the user enters an invalid email format, we issue an error message immediately after
the user moves to the next field. This allows a correction to be made in a timely manner.

Most of the time, client-side validation depends on JavaScript being enabled in the browser. If
JavaScript is disabled, user input is sent to the server unchecked. It is possible that this is mali-
cious data! Therefore, client-side validation does not protect your component’s users from malicious
attacks.
Again: Since both validation methods (server and client) have their own importance, it is recom-
mended to use them side by side. Server-side validation is more secure - client-side validation is
more user-friendly!

This part covers the client-side validation in Joomla 4.

For impatient people: Look at the changed program code in the Diff Viewa and apply these changes
to your development version.
a
codeberg.org/astrid/j4examplecode/compare/t11a...t11b

15.1. Step by step

15.1.1. New files

Client-side validation is done via a JavaScript file.

Page 137 January 2023


Joomla 4 - Developing Extensions 15. Client Side Validierung

15.1.1.1. media/com_foos/js/admin-foos-letter.js

Again, it is about the principle, just like in the previous chapter. The quality of the validation is in this
tutorial secondary and I choose a simple example. Numbers are forbidden in the text field for the name.
Astrid is allowed. Astrid9 is not allowed.

In the example I use a regular expressiona . regex.test(value) returns true if regex is equal
to /^([a-z]+)$/i and value does not contain a number. For more information on the test
method, see developer.mozilla.orgb . It is not mandatory to use a regular expression. It is only
important that true is returned for a pass and false for a fail.
a
en.wikipedia.org/wiki/regular_expression
b
developer.mozilla.org/en/docs/web/javascript/reference/global_objects/regexp/test

media/com_foos/js/admin-foos-letter.js

1
2 document.addEventListener('DOMContentLoaded', function(){
3 "use strict";
4 setTimeout(function() {
5 if (document.formvalidator) {
6 document.formvalidator.setHandler('letter', function (value
) {
7
8 var returnedValue = false;
9
10 var regex = /^([a-z]+)$/i;
11
12 if (regex.test(value))
13 returnedValue = true;
14
15 return returnedValue;
16 });
17 //console.log(document.formvalidator);
18 }
19 }, (1000));
20 });

Note: The variable name returnedValue is only meant as an example. The name of a variable
should explain in real code why it exists, what it does and how it is used.

Page 138 January 2023


15. Client Side Validierung Joomla 4 - Developing Extensions

15.1.2. Modified files

15.1.2.1. administrator/components/com_foos/foos.xml

In the installation manifest we add <filename>joomla.asset.json</filename> so that Joomla


knows that the file joomla.asset.json belongs to the extension and is copied to the media/
com_foos directory. We create this file later in this part.

administrator/components/com_foos/foos.xml

1 <folder>tmpl</folder>
2 </files>
3 <media folder="media/com_foos" destination="com_foos">
4 + <filename>joomla.asset.json</filename>
5 <folder>js</folder>
6 </media>
7 <!-- Back-end files -->

15.1.2.2. administrator/components/com_foos/ tmpl/foo/edit.php

The entry ->useScript('com_foos.admin-foos-letter'); ensures that the JavaScript file


media/com_foos/js/admin-foos-letter.js, which is responsible for checking, is applicable
via the webasset manager. For this purpose, we will register it later in this chapter via the file
joomla.asset.json.

1
2 $wa = $this->document->getWebAssetManager();
3 $wa->useScript('keepalive')
4 - ->useScript('form.validate');
5 + ->useScript('form.validate')
6 + ->useScript('com_foos.admin-foos-letter');
7
8 $layout = 'edit';
9 $tmpl = $input->get('tmpl', '', 'cmd') === 'component' ? '&tmpl=
component' : '';

15.1.2.3. administrator/components/com_foos/ forms/foo.xml

We add class="validate-letter", so Joomla knows which CSS class should be checked. Joomla
sets this class when creating the field. See for yourself by opening the form in the backend and checking
out the source code.

administrator/components/com_foos/forms/foo.xml

January 2023 Page 139


Joomla 4 - Developing Extensions 15. Client Side Validierung

1 name="name"
2 type="text"
3 validate="Letter"
4 + class="validate-letter"
5 label="COM_FOOS_FIELD_NAME_LABEL"
6 size="40"
7 required="true"

15.1.2.4. media/com_foos/joomla.asset.json

Last but not least, we register the new file under the name com_foos.admin-foos-letter in the
webasset manager.

media/com_foos/joomla.asset.json

1
2 "description": "Joomla CMS",
3 "license": "GPL-2.0-or-later",
4 "assets": [
5 + {
6 + "name": "com_foos.admin-foos-letter",
7 + "type": "script",
8 + "uri": "com_foos/admin-foos-letter.js",
9 + "dependencies": [
10 + "core"
11 + ],
12 + "attributes": {
13 + "defer": true
14 + }
15 + },
16 {
17 "name": "com_foos.admin-foos-modal",
18 "type": "script",

15.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the media folder
into the media folder of your Joomla 4 installation. A new installation is not necessary. Continue
using the files from the previous part. 2.

2. Open the view of your component in the administration area and create a new item or edit an
existing one. Enter a number in the text field for the title. 3.

Page 140 January 2023


15. Client Side Validierung Joomla 4 - Developing Extensions

3. Then edit another field, for example set the access to Registered.

4. Make sure that at this point a warning is displayed.

Figure 15.1.: Joomla Validation

January 2023 Page 141


Joomla 4 - Developing Extensions 16. Set Up Categories in Backend

16. Set Up Categories in Backend

Almost every website divides its content into categories. Joomla offers this useful feature as well. The
current part of the tutorial shows you how to ideally integrate categories into a Joomla component.
Don’t reinvent the wheel yourself. Use what Joomla offers you.

Categories are a way of organising content in Joomla A category contains posts and other cate-
gories. A post can only be in one category. If a category is contained in another, it is a subcategory
of the category.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t11b...t12

16.1. Step by step

16.1.1. New files

16.1.1.1. administrator/components/com_foos/ sql/updates/mysql/12.0.0.sql

We store the data in the database that is necessary to classify an element into a category. Therefore,
in case of an update, it is important to add a column to the database. To do this, we create the
file administrator/components/com_foos/sql/updates/mysql/12.0.0.sql and enter the
necessary SQL statement in it. We choose the name because we are currently working on version 12 of
our extension.

administrator/components/com_foos/sql/updates/mysql/12.0.0.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `catid ` int(11) NOT NULL
DEFAULT 0 AFTER `alias`;
3
4 ALTER TABLE `#__foos_details ` ADD KEY `idx_catid ` ( `catid`);

Page 143 January 2023


Joomla 4 - Developing Extensions 16. Set Up Categories in Backend

16.1.2. Modified files

16.1.2.1. administrator/components/com_foos/ access.xml

The entries in the file access.xml marked below with a plus sign are necessary to set permissions for
the categories. The new code causes the display of a tab for setting user permissions per category in
the administration area.
administrator/components/com_foos/access.xml

1
2 <action name="core.edit" title="JACTION_EDIT" />
3 <action name="core.edit.state" title="JACTION_EDITSTATE" />
4 <action name="core.edit.own" title="JACTION_EDITOWN" />
5 + <action name="core.edit.value" title="JACTION_EDITVALUE" />
6 + </section>
7 + <section name="category">
8 + <action name="core.create" title="JACTION_CREATE" />
9 + <action name="core.delete" title="JACTION_DELETE" />
10 + <action name="core.edit" title="JACTION_EDIT" />
11 + <action name="core.edit.state" title="JACTION_EDITSTATE" />
12 + <action name="core.edit.own" title="JACTION_EDITOWN" />
13 </section>
14 </access>

16.1.2.2. administrator/components/com_foos/foos.xml

The <menu link="option=com_categories&amp;extension=com_foos" entry causes a menu


item to be added in the administration area menu for editing the category.
administrator/components/com_foos/foos.xml

1
2 <menu view="foos">COM_FOOS</menu>
3 <submenu>
4 <menu link="option=com_foos">COM_FOOS</menu>
5 + <menu link="option=com_categories&amp;extension=com_foos">
JCATEGORY</menu>
6 </submenu>
7 <files folder="administrator/components/com_foos">
8 <filename>access.xml</filename>

16.1.2.3. administrator/components/com_foos/ forms/foo.xml

We add a selection field with matching categories to the form used to create a Foo item. We use the
Joomla own field categoryedit for this. Note the line extension="com_foos". This ensures that

Page 144 January 2023


16. Set Up Categories in Backend Joomla 4 - Developing Extensions

only categories belonging to the component com_foos are displayed.

administrator/components/com_foos/forms/foo.xml

1 hint="JFIELD_ALIAS_PLACEHOLDER"
2 />
3
4 + <field
5 + name="catid"
6 + type="categoryedit"
7 + label="JCATEGORY"
8 + extension="com_foos"
9 + addfieldprefix="Joomla\Component\Categories\Administrator\
Field"
10 + required="true"
11 + default=""
12 + />
13 +
14 <field
15 name="access"
16 type="accesslevel"

16.1.2.4. administrator/components/com_foos/script.php

To ensure that a category already exists at the beginning, we add the script that is called during the
installation. Using the install method, we create a category with the title Uncategorised for the
component during a new installation. We store these directly in the database. To be able to specify
a user as the creator of the category, we request the ID of the administrator in the getAdminId()
method.

administrator/components/com_foos/script.php

1
2 \defined('_JEXEC') or die;
3 +
4 +use Joomla\CMS\Application\ApplicationHelper;
5 +use Joomla\CMS\Factory;
6 use Joomla\CMS\Installer\InstallerAdapter;
7 use Joomla\CMS\Language\Text;
8 use Joomla\CMS\Log\Log;
9 +use Joomla\CMS\Table\Table;
10
11
12 {
13 echo Text::_('COM_FOOS_INSTALLERSCRIPT_INSTALL');
14
15 + $db = Factory::getDbo();

January 2023 Page 145


Joomla 4 - Developing Extensions 16. Set Up Categories in Backend

16 + $alias = ApplicationHelper::stringURLSafe('FooUncategorised')
;
17 +
18 + // Initialize a new category.
19 + $category = Table::getInstance('Category');
20 +
21 + $data = array(
22 + 'extension' => 'com_foos',
23 + 'title' => 'FooUncategorised',
24 + 'alias' => $alias . '(en-GB)',
25 + 'description' => '',
26 + 'published' => 1,
27 + 'access' => 1,
28 + 'params' => '{"target":"","image":""}',
29 + 'metadesc' => '',
30 + 'metakey' => '',
31 + 'metadata' => '{"page_title":"","author":"","robots":""}',
32 + 'created_time' => Factory::getDate()->toSql(),
33 + 'created_user_id' => (int) $this->getAdminId(),
34 + 'language' => 'en-GB',
35 + 'rules' => array(),
36 + 'parent_id' => 1,
37 + );
38 +
39 + $category->setLocation(1, 'last-child');
40 +
41 + // Bind the data to the table
42 + if (!$category->bind($data))
43 + {
44 + return false;
45 + }
46 +
47 + // Check to make sure our data is valid.
48 + if (!$category->check())
49 + {
50 + return false;
51 + }
52 +
53 + // Store the category.
54 + if (!$category->store(true))
55 + {
56 + return false;
57 + }
58 +
59 return true;
60 }
61
62
63
64 return true;
65 }

Page 146 January 2023


16. Set Up Categories in Backend Joomla 4 - Developing Extensions

66 +
67 + private function getAdminId()
68 + {
69 + $db = Factory::getDbo();
70 + $query = $db->getQuery(true);
71 +
72 + // Select the admin user ID
73 + $query
74 + ->clear()
75 + ->select($db->quoteName('u') . '.' . $db->quoteName('id'))
76 + ->from($db->quoteName('#__users', 'u'))
77 + ->join(
78 + 'LEFT',
79 + $db->quoteName('#__user_usergroup_map', 'map')
80 + . ' ON ' . $db->quoteName('map') . '.' . $db->quoteName
('user_id')
81 + . ' = ' . $db->quoteName('u') . '.' . $db->quoteName('
id')
82 + )
83 + ->join(
84 + 'LEFT',
85 + $db->quoteName('#__usergroups', 'g')
86 + . ' ON ' . $db->quoteName('map') . '.' . $db->quoteName
('group_id')
87 + . ' = ' . $db->quoteName('g') . '.' . $db->quoteName('
id')
88 + )
89 + ->where(
90 + $db->quoteName('g') . '.' . $db->quoteName('title')
91 + . ' = ' . $db->quote('Super Users')
92 + );
93 +
94 + $db->setQuery($query);
95 + $id = $db->loadResult();
96 +
97 + if (!$id || $id instanceof \Exception)
98 + {
99 + return false;
100 + }
101 +
102 + return $id;
103 + }
104 }

16.1.2.5. administrator/components/com_foos/ services/provider.php

In the service provider we register the interface CategoryFactoryInterface. It is not necessary to


create CategoryFactory Interface by yourself. We use the Joomla own functions.

January 2023 Page 147


Joomla 4 - Developing Extensions 16. Set Up Categories in Backend

administrator/components/com_foos/services/provider.php

1
2 \defined('_JEXEC') or die;
3
4 +use Joomla\CMS\Categories\CategoryFactoryInterface;
5 use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
6 use Joomla\CMS\Extension\ComponentInterface;
7 use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
8
9
10 $component->setRegistry($container->get(Registry::class
));
11 $component->setMVCFactory($container->get(
MVCFactoryInterface::class));
12 + $component->setCategoryFactory($container->get(
CategoryFactoryInterface::class));
13
14 return $component;
15 }

16.1.2.6. administrator/components/com_foos/ sql/install.mysql.utf8.sql

In order to create the table column in which the category of a Foo element is stored during a new
installation, we add the necessary SQL command in the SQL file that is called during the installation.
administrator/components/com_foos/sql/install.mysql.utf8.sql

1 ALTER TABLE `#__foos_details ` ADD COLUMN `access ` int(10) unsigned


NOT NULL DEFAULT 0 AFTER `alias`;
2
3 ALTER TABLE `#__foos_details ` ADD KEY `idx_access ` ( `access`);
4 +
5 +ALTER TABLE `#__foos_details ` ADD COLUMN `catid ` int(11) NOT NULL
DEFAULT 0 AFTER `alias`;

16.1.2.7. administrator/components/com_foos/ src/Extension/FoosComponent.php

Additionally, implementations are required in the component class to use Joomla’s own functions. The
method countItems is necessary so that an overview of assigned items appears in the category view.
The method getTableNameForSection ensures that the correct database table is always queried.
administrator/components/com_foos/src/Extension/FoosComponent.php

1 use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
2 use FooNamespace\Component\Foos\Administrator\Service\HTML\
AdministratorService;

Page 148 January 2023


16. Set Up Categories in Backend Joomla 4 - Developing Extensions

3 use Psr\Container\ContainerInterface;
4 +use Joomla\CMS\Helper\ContentHelper;
5
6
7 {
8 $this->getRegistry()->register('foosadministrator', new
AdministratorService);
9 }
10 +
11 + public function countItems(array $items, string $section)
12 + {
13 + try
14 + {
15 + $config = (object) array(
16 + 'related_tbl' => $this->getTableNameForSection(
$section),
17 + 'state_col' => 'published',
18 + 'group_col' => 'catid',
19 + 'relation_type' => 'category_or_group',
20 + );
21 +
22 + ContentHelper::countRelations($items, $config);
23 + }
24 + catch (\Exception $e)
25 + {
26 + // Ignore it
27 + }
28 + }
29 +
30 + protected function getTableNameForSection(string $section = null)
31 + {
32 + return ($section === 'category' ? 'categories' : 'foos_details'
);
33 +
34 + }
35 }

16.1.2.8. administrator/components/com_foos/ src/Model/Foos-


Model.php](https://codeberg.org/astrid/j4examplecode/compare/t11b. . . t12#diff-
2daf62ad6c51630353e31eaa3cc28626)

In the model we add to the database query the table where Joomla stores categories. Thus, in the
administration area, when a category is selected, only the elements belonging to it are displayed.

administrator/components/com_foos/src/Model/FoosModel.php

1
2 // Select the required fields from the table.

January 2023 Page 149


Joomla 4 - Developing Extensions 16. Set Up Categories in Backend

3 $query->select(
4 - $db->quoteName(array('a.id', 'a.name', 'a.alias', 'a.access
'))
5 + $db->quoteName(array('a.id', 'a.name', 'a.alias', 'a.access
', 'a.catid'))
6 );
7
8 $query->from($db->quoteName('#__foos_details', 'a'));
9
10 $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->
quoteName('ag.id') . ' = ' . $db->quoteName('a.
access')
11 );
12
13 + // Join over the categories.
14 + $query->select($db->quoteName('c.title', 'category_title'))
15 + ->join(
16 + 'LEFT',
17 + $db->quoteName('#__categories', 'c') . ' ON ' . $db->
quoteName('c.id') . ' = ' . $db->quoteName('a.catid')
18 + );
19 +
20 return $query;
21 }
22 }

16.1.2.9. administrator/components/com_foos/ tmpl/foo/edit.php

We add the category field to the form for editing an element. It is rendered using the information in the
XML form administrator/components/com_foos/forms/foo.xml, which we worked on earlier
in this chapter.

administrator/components/com_foos/tmpl/foo/edit.php

1 <?php echo $this->getForm()->renderField('name'); ?>


2 <?php echo $this->getForm()->renderField('alias'); ?>
3 <?php echo $this->getForm()->renderField('access'); ?>
4 + <?php echo $this->getForm()->renderField('catid'); ?>
5 <input type="hidden" name="task" value="">
6 <?php echo HTMLHelper::_('form.token'); ?>
7 </form>

16.1.2.10. administrator/components/com_foos/ tmpl/foos/default.php

In the overview table of the view in the backend, we add a column for displaying the category.

administrator/components/com_foos/tmpl/foos/default.php

Page 150 January 2023


16. Set Up Categories in Backend Joomla 4 - Developing Extensions

1
2 <a class="hasTooltip" href="<?php echo Route::_('index.php?
option=com_foos&task=foo.edit&id=' . (int) $item->id); ?>"
title="<?php echo Text::_('JACTION_EDIT'); ?> <?php echo
$this->escape(addslashes($item->name)); ?>">
3 <?php echo $editIcon; ?><?php echo $this->escape($item->
name); ?></a>
4
5 + <div class="small">
6 + <?php echo Text::_('JCATEGORY') . ': ' . $this->escape(
$item->category_title); ?>
7 + </div>
8 </th>
9 <td class="small d-none d-md-table-cell">
10 <?php echo $item->access_level; ?>

The categories help you to display your data in a structured way in the frontend. We will create
the frontend views in the further course of this tutorial.

16.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder to the administrator folder of your Joomla 4 installation. Install your component as
described in part one, after copying all files. Joomla will update the database for you during the
installation. On the right-hand side of the table is an overview that lists how many elements are
published or unpublished. This does not work yet. We will work on publishing and unpublishing
in the next part.

2. Open the view of your component in the administration area.

3. in the sidebar you will see a new menu item. This offers you everything you need to create and
edit the categories of your component.

January 2023 Page 151


Joomla 4 - Developing Extensions 16. Set Up Categories in Backend

Figure 16.1.: Joomla Category Menu Item

4. next open an element. Make sure that it is possible to assign a category to it.

Figure 16.2.: Joomla Assign Category

5. make sure that the foo specific categories do not appear in other components, for example in
com_contact.

Page 152 January 2023


Joomla 4 - Developing Extensions 17. Publish and Unpublish / Hide

17. Publish and Unpublish / Hide

If you worked with Joomla, you know it from other components: Items have a status that can be
changed. This section shows you how to

• unpublish,
• publish,
• schedule,
• archive and
• trash items.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t13...t12

17.1. Step by step

17.1.1. New files

17.1.1.1. administrator/components/com_foos/ sql/updates/mysql/13.0.0.sql

In case of an update, the database is updated to the latest version for version 13 using the file
administrator/components/com_foos/sql/updates/mysql/13.0.0.sql. Specifically,
columns are added for saving the data for publication.

administrator/components/com_foos/sql/updates/mysql/13.0.0.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `published ` tinyint(1) NOT
NULL DEFAULT 0 AFTER `alias`;
3
4 ALTER TABLE `#__foos_details ` ADD COLUMN `publish_up ` datetime AFTER `
alias`;
5
6 ALTER TABLE `#__foos_details ` ADD COLUMN `publish_down ` datetime AFTER
`alias`;

Page 153 January 2023


Joomla 4 - Developing Extensions 17. Publish and Unpublish / Hide

7
8 ALTER TABLE `#__foos_details ` ADD KEY `idx_state ` ( `published`);

17.1.1.2. administrator/components/com_foos/ src/Controller/FoosController.php

Now Joomla needs the class AdminController. Therefore, we create the class FoosController,
which inherits from AdminController. At the moment, FoosController does not contain any
implementations of its own. The controller only calls methods of the parent class.

administrator/components/com_foos/src/Controller/FoosController.php

1 <?php
2
3 <?php
4
5 namespace FooNamespace\Component\Foos\Administrator\Controller;
6
7 \defined('_JEXEC') or die;
8
9 use Joomla\CMS\Application\CMSApplication;
10 use Joomla\CMS\MVC\Controller\AdminController;
11 use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
12 use Joomla\Input\Input;
13
14 class FoosController extends AdminController
15 {
16 public function __construct($config = [], MVCFactoryInterface
$factory = null, $app = null, $input = null)
17 {
18 parent::__construct($config, $factory, $app, $input);
19 }
20
21 public function getModel($name = 'Foo', $prefix = 'Administrator',
$config = ['ignore_request' => true])
22 {
23 return parent::getModel($name, $prefix, $config);
24 }
25 }

17.1.2. Modified files

17.1.2.1. administrator/components/com_foos/ forms/foo.xml

Three fields are added to the form. One, in which the status is set and two, through which a scheduled
publication is achieved with the help of a calendar.

Page 154 January 2023


17. Publish and Unpublish / Hide Joomla 4 - Developing Extensions

administrator/components/com_foos/forms/foo.xml

1
2 hint="JFIELD_ALIAS_PLACEHOLDER"
3 />
4
5 + <field
6 + name="published"
7 + type="list"
8 + label="JSTATUS"
9 + default="1"
10 + id="published"
11 + class="custom-select-color-state"
12 + size="1"
13 + >
14 + <option value="1">JPUBLISHED</option>
15 + <option value="0">JUNPUBLISHED</option>
16 + <option value="2">JARCHIVED</option>
17 + <option value="-2">JTRASHED</option>
18 + </field>
19 +
20 + <field
21 + name="publish_up"
22 + type="calendar"
23 + label="COM_FOOS_FIELD_PUBLISH_UP_LABEL"
24 + translateformat="true"
25 + showtime="true"
26 + size="22"
27 + filter="user_utc"
28 + />
29 +
30 + <field
31 + name="publish_down"
32 + type="calendar"
33 + label="COM_FOOS_FIELD_PUBLISH_DOWN_LABEL"
34 + translateformat="true"
35 + showtime="true"
36 + size="22"
37 + filter="user_utc"
38 + />
39 +
40 <field
41 name="catid"
42 type="categoryedit"

17.1.2.2. administrator/components/com_foos/ sql/install.mysql.utf8.sql

administrator/components/com_foos/sql/install.mysql.utf8.sql is used in the case of


a new installation to create the database. Therefore, we add the necessary information here. We

January 2023 Page 155


Joomla 4 - Developing Extensions 17. Publish and Unpublish / Hide

had already added this in the file administrator/components/com_foos/sql/updates/mysql


/13.0.0.sql. This file is only used during an update.

administrator/components/com_foos/sql/install.mysql.utf8.sql

1 ALTER TABLE `#__foos_details ` ADD COLUMN `access ` int(10) unsigned


NOT NULL DEF
2 ALTER TABLE `#__foos_details ` ADD KEY `idx_access ` ( `access`);
3
4 ALTER TABLE `#__foos_details ` ADD COLUMN `catid ` int(11) NOT NULL
DEFAULT 0 AFTER `alias`;
5 +
6 +ALTER TABLE `#__foos_details ` ADD COLUMN `state ` tinyint(3) NOT NULL
DEFAULT 0 AFTER `alias`;
7 +
8 +ALTER TABLE `#__foos_details ` ADD KEY `idx_catid ` ( `catid`);
9 +
10 +ALTER TABLE `#__foos_details ` ADD COLUMN `published ` tinyint(1) NOT
NULL DEFAULT 0 AFTER `alias`;
11 +
12 +ALTER TABLE `#__foos_details ` ADD COLUMN `publish_up ` datetime AFTER
`alias`;
13 +
14 +ALTER TABLE `#__foos_details ` ADD COLUMN `publish_down ` datetime
AFTER `alias`;
15 +
16 +ALTER TABLE `#__foos_details ` ADD KEY `idx_state ` ( `published`);

17.1.2.3. administrator/components/com_foos/ src/Extension/FoosComponent.php

The component class receives the new function “getStateColumnForSection”. This is used to show in
the category view how many items are published or hidden. Remember. We introduced categories in
the previous part. Then this part did not work in the category view. Now it is counted correctly. See for
yourself after you have added this function to the component in Joomla.

administrator/components/com_foos/src/Extension/FoosComponent.php

1 protected function getTableNameForSection(string $section = null)


2 {
3 return ($section === 'category' ? 'categories' : 'foos_details'
);
4 }
5 +
6 + protected function getStateColumnForSection(string $section = null)
7 + {
8 + return 'published';
9 + }
10 }

Page 156 January 2023


17. Publish and Unpublish / Hide Joomla 4 - Developing Extensions

17.1.2.4. administrator/components/com_foos/ src/Model/FoosModel.php

We extend the model so that the information about the status is retrieved from the database when the
list view is created for the backend.

administrator/components/com_foos/src/Model/FoosModel.php

1 protected function getListQuery()


2
3 // Select the required fields from the table.
4 $query->select(
5 - $db->quoteName(['a.id', 'a.name', 'a.alias', 'a.access', 'a
.catid'])
6 + $db->quoteName(['a.id', 'a.name', 'a.alias', 'a.access', 'a
.catid', 'a.published', 'a.publish_up', 'a.publish_down'])
7 );
8
9 $query->from($db->quoteName('#__foos_details', 'a'));

17.1.2.5. administrator/components/com_foos/ src/Table/FooTable.php

In the file administrator/components/com_foos/src/Table/FooTable.php, which manages


the database table, we add checks. This way we make sure that no impossible data is stored.

We need store($updateNulls = true) because the parent class Table sets the variable
$updateNulls to false. This causes form fields that hold the value null not to be changed in the
database. Most of the time this is correct. The most common case is probably that a value is not set
from the beginning and has not been changed in the form when editing the element. Because an
empty date field is stored in the database with null, it is necessary in our case to force the storage of
null values. This is done by setting the variable $updateNulls to true.

administrator/components/com_foos/src/Table/FooTable.php

1 public function generateAlias()


2
3 return $this->alias;
4 }
5 +
6 + public function check()
7 + {
8 + try {
9 + parent::check();
10 + } catch (\Exception $e) {
11 + $this->setError($e->getMessage());
12 +
13 + return false;
14 + }

January 2023 Page 157


Joomla 4 - Developing Extensions 17. Publish and Unpublish / Hide

15 +
16 + // Check the publish down date is not earlier than publish up.
17 + if ($this->publish_down > $this->_db->getNullDate() && $this->
publish_down < $this->publish_up) {
18 + $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH
'));
19 +
20 + return false;
21 + }
22 +
23 + // Set publish_up, publish_down to null if not set
24 + if (!$this->publish_up) {
25 + $this->publish_up = null;
26 + }
27 +
28 + if (!$this->publish_down) {
29 + $this->publish_down = null;
30 + }
31 +
32 + return true;
33 + }
34 +
35 + public function store($updateNulls = true)
36 + {
37 + return parent::store($updateNulls);
38 + }
39 }

17.1.2.6. administrator/components/com_foos/ tmpl/foo/edit.php

In the form for editing an element, we make sure that the new fields are rendered.

administrator/components/com_foos/tmpl/foo/edit.php

1 <?php echo $this->getForm()->renderField('alias'); ?>


2 <?php echo $this->getForm()->renderField('access'); ?>
3 <?php echo $this->getForm()->renderField('catid'); ?>
4 + <?php echo $this->getForm()->renderField('published'); ?>
5 + <?php echo $this->getForm()->renderField('publish_up'); ?>
6 + <?php echo $this->getForm()->renderField('publish_down'); ?>
7 <input type="hidden" name="task" value="">
8 <?php echo HTMLHelper::_('form.token'); ?>
9 </form>

Page 158 January 2023


17. Publish and Unpublish / Hide Joomla 4 - Developing Extensions

17.1.2.7. administrator/components/com_foos/ tmpl/foos/default.php

Finally, we add to the overview list in the backend. We create a column for displaying the publication
status.
Are you wondering about the the tags <td> and <th>. This seems to be a mistake at first sight.
But it is correct. You can find more information in the Github-Issue 24546 a .
a
github.com/joomla/joomla-cms/pull/24546

administrator/components/com_foos/tmpl/foos/default.php

1 <table class="table" id="fooList">


2 <thead>
3 <tr>
4 + <td style="width:1%" class="text-center
">
5 + <?php echo HTMLHelper::_('grid.
checkall'); ?>
6 + </td>
7 <th scope="col" style="width:1%" class=
"text-center d-none d-md-table-cell"
>
8 <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_NAME');
?>
9 </th>
10 + <th scope="col" style="width:1%; min-
width:85px" class="text-center">
11 + <?php echo TEXT::_('JSTATUS'); ?>
12 + </th>
13 <th scope="col" style="width:10%" class
="d-none d-md-table-cell">
14 <?php echo TEXT::_('
JGRID_HEADING_ACCESS') ?>
15 </th>
16
17 foreach ($this->items as $i => $item) :
18 ?>
19 <tr class="row<?php echo $i % 2; ?>">
20 + <td class="text-center">
21 + <?php echo HTMLHelper::_('grid.id',
$i, $item->id); ?>
22 + </td>
23 <th scope="row" class="has-context">
24 <div>
25 <?php echo $this->escape($item
->name); ?>
26
27 <?php echo Text::_('JCATEGORY')

January 2023 Page 159


Joomla 4 - Developing Extensions 17. Publish and Unpublish / Hide

. ': ' . $this->escape(


$item->category_title); ?>
28 </div>
29 </th>
30 + <td class="text-center">
31 + <?php
32 + echo HTMLHelper::_('jgrid.published
', $item->published, $i, 'foos.', true, 'cb', $item->publish_up,
$item->publish_down);
33 + ?>
34 + </td>
35 <td class="small d-none d-md-table-cell
">
36 <?php echo $item->access_level; ?>
37 </td>

17.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
to the administrator folder of your Joomla 4 installation.

2. the database has been changed again, so it is necessary to update it. Uninstalling and reinstalling
is time-consuming. That’s why I’ll tell you an easier method.

3. open the section “System | Information | Database”.

Figure 17.1.: Joomla Published

Page 160 January 2023


17. Publish and Unpublish / Hide Joomla 4 - Developing Extensions

4. select your component and click on “Update Structure”. That was it! Now you have updated the
database.

5. open the view of your component in the administration area and make sure that you see a
column here that is overwritten with status. Click on an icon in it and change the status of the
corresponding element from “published” to “hidden” and vice versa.

Figure 17.2.: Joomla Published

6. open an element and check that the status is also editable in this view. It is also possible to
specify a date, so that items are hidden or published according to the date.

January 2023 Page 161


Joomla 4 - Developing Extensions 17. Publish and Unpublish / Hide

Figure 17.3.: Joomla Published

Page 162 January 2023


Joomla 4 - Developing Extensions 18. Integrate Custom Fields in Backend

18. Integrate Custom Fields in Backend

Custom Fields (Own Fields) are very popular since Joomla version 3.7. They offer many additional
possibilities. Therefore, there is no question that we integrate them into our component.

This part shows you how to program the support in the administration area. In the next chapter we will
integrate Custom Fields in the frontend.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t13...t14a

18.1. Step by step

18.1.1. New files

We have not created a new file in this part, we have only changed files.

18.1.2. Modified files

18.1.2.1. administrator/components/com_foos/access.xml

In the file administrator/components/com_foos/access.xml we prepare everything to give


permissions to the user-defined fields. So it is possible that only specific users are allowed to change
or view a field.

administrator/components/com_foos/access.xml

1 <action name="core.edit.state" title="JACTION_EDITSTATE" />


2 <action name="core.edit.own" title="JACTION_EDITOWN" />
3 </section>
4 + <section name="fieldgroup">
5 + <action name="core.create" title="JACTION_CREATE" />
6 + <action name="core.delete" title="JACTION_DELETE" />
7 + <action name="core.edit" title="JACTION_EDIT" />

Page 163 January 2023


Joomla 4 - Developing Extensions 18. Integrate Custom Fields in Backend

8 + <action name="core.edit.state" title="JACTION_EDITSTATE" />


9 + <action name="core.edit.own" title="JACTION_EDITOWN" />
10 + <action name="core.edit.value" title="JACTION_EDITVALUE" />
11 + </section>
12 + <section name="field">
13 + <action name="core.delete" title="JACTION_DELETE" />
14 + <action name="core.edit" title="JACTION_EDIT" />
15 + <action name="core.edit.state" title="JACTION_EDITSTATE" />
16 + <action name="core.edit.value" title="JACTION_EDITVALUE" />
17 + </section>
18 </access>

18.1.2.2. administrator/components/com_foos/config.xml

The configuration config.xml uses a paramter to define whether the extension uses custom fields.

Do you wonder why this parameter exists? It is not mandatorya .


a
joomla.stackexchange.com/questions/28672/reason-for-parameter-for-using-custom-fields-in-configuration

administrator/components/com_foos/config.xml

1 <option value="0">JNO</option>
2 <option value="1">JYES</option>
3 </field>
4 +
5 + <field
6 + name="custom_fields_enable"
7 + type="radio"
8 + label="JGLOBAL_CUSTOM_FIELDS_ENABLE_LABEL"
9 + layout="joomla.form.field.radio.switcher"
10 + default="1"
11 + >
12 + <option value="0">JNO</option>
13 + <option value="1">JYES</option>
14 + </field>
15 </fieldset>
16 <fieldset
17 name="permissions"

A tip for the type radio with the layout joomla.form.field.radio.switcher. Do you want to
determine yourself how the colours are set in the layout? Is it important to you that when you set the
option “yes”, the field is coloured green and when you set the option “no”, a grey background appears?
By default, Joomla colours the options based on the order of the options. Example: Your field looks
like the next picture with the following code.

Page 164 January 2023


18. Integrate Custom Fields in Backend Joomla 4 - Developing Extensions

Figure 18.1.: Type radio with the layout joomla.form.field.radio.switcher

1 <field name="eins" type="radio" label="eins" layout="joomla.form.field.


radio.switcher" default="1">
2 <option value="0">JNO</option>
3 <option value="1">JYES</option>
4 </field>
5
6 <field name="zwei" type="radio" label="zwei" layout="joomla.form.field.
radio.switcher" default="0">
7 <option value="0">JNO</option>
8 <option value="1">JYES</option>
9 </field>
10 <field name="drei" type="radio" label="drei" layout="joomla.form.field.
radio.switcher" default="1">
11 <option value="1">JYES</option>
12 <option value="0">JNO</option>
13 </field>
14
15 <field name="vier" type="radio" label="vier" layout="joomla.form.field.
radio.switcher" default="0">
16 <option value="1">JYES</option>
17 <option value="0">JNO</option>
18 </field>

18.1.2.3. administrator/components/com_foos/foos.xml

In the navigation menu on the left in the Joomla administration area we add two links. The first new
link leads to the view where custom fields are created for the component. The other one leads to the
view where field groups are created.

administrator/components/com_foos/foos.xml

1 <submenu>
2 <menu link="option=com_foos">COM_FOOS</menu>
3 <menu link="option=com_categories&amp;extension=com_foos">
JCATEGORY</menu>
4 + <menu link="option=com_fields&amp;context=com_foos.foo">
JGLOBAL_FIELDS</menu>
5 + <menu link="option=com_fields&amp;view=groups&amp;context=
com_foos.foo">JGLOBAL_FIELD_GROUPS</menu>

January 2023 Page 165


Joomla 4 - Developing Extensions 18. Integrate Custom Fields in Backend

6 </submenu>
7 <files folder="administrator/components/com_foos">
8 <filename>access.xml</filename>

18.1.2.4. administrator/components/com_foos/ src/Model/FooModel.php

The form through which a Foo element can be edited now has tabs. To ensure that the data is not
lost within the session when switching between tabs, we change the loadFormData() method in
the file administrator/components/com_foos/src/Model/FooModel.php. It is not necessary
that we cache data ourselves. The method $app->getUserState() does this for us. At the same
time we make sure that a default value is set for the category if a new element is loaded and therefore
$this->getState('foo.id')== 0 equals true.

administrator/components/com_foos/src/Model/FooModel.php

1
2 {
3 $app = Factory::getApplication();
4
5 - $data = $this->getItem();
6 + // Check the session for previously entered form data.
7 + $data = $app->getUserState('com_foos.edit.foo.data', []);
8 +
9 + if (empty($data)) {
10 + $data = $this->getItem();
11 +
12 + // Prime some default values.
13 + if ($this->getState('foo.id') == 0) {
14 + $data->set('catid', $app->input->get('catid', $app->
getUserState('com_foos.foos.filter.category_id'), 'int'));
15 + }
16 + }
17
18 $this->preprocessData($this->typeAlias, $data);

18.1.2.5. administrator/components/com_foos/ tmpl/foo/edit.php

To make editing the custom fields work the same way as in Joomla’s own extensions, we use UiTab1 .
$this->useCoreUI = true; ensures that the Helper2 flexibly provides the correct tab implemen-
tation.
1
libraries/src/html/helpers/uitab.php
2
layouts/joomla/edit/params.php#l20

Page 166 January 2023


18. Integrate Custom Fields in Backend Joomla 4 - Developing Extensions

A comparison between previously most used bootstrap.tab and uitab is provided by Pull
Request PR 21805a .
a
github.com/joomla/joomla-cms/pull/21805

administrator/components/com_foos/tmpl/foo/edit.php

1 use Joomla\CMS\Factory;
2 use Joomla\CMS\HTML\HTMLHelper;
3 use Joomla\CMS\Router\Route;
4 +use Joomla\CMS\Language\Text;
5 +use Joomla\CMS\Layout\LayoutHelper;
6
7 $app = Factory::getApplication();
8 $input = $app->input;
9
10 +$this->useCoreUI = true;
11 +
12 $wa = $this->document->getWebAssetManager();
13 $wa->useScript('keepalive')
14 ->useScript('form.validate')
15 ?>
16
17 <form action="<?php echo Route::_('index.php?option=com_foos&layout='
. $layout . $tmpl . '&id=' . (int) $this->item->id); ?>" method="
post" name="adminForm" id="foo-form" class="form-validate">
18 - <?php echo $this->getForm()->renderField('name'); ?>
19 - <?php echo $this->getForm()->renderField('alias'); ?>
20 - <?php echo $this->getForm()->renderField('access'); ?>
21 - <?php echo $this->getForm()->renderField('catid'); ?>
22 - <?php echo $this->getForm()->renderField('published'); ?>
23 - <?php echo $this->getForm()->renderField('publish_up'); ?>
24 - <?php echo $this->getForm()->renderField('publish_down'); ?>
25 + <div>
26 + <?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', ['active
' => 'details']); ?>
27 +
28 + <?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'details',
empty($this->item->id) ? Text::_('COM_FOOS_NEW_FOO') : Text::_('
COM_FOOS_EDIT_FOO')); ?>
29 + <div class="row">
30 + <div class="col-md-9">
31 + <div class="row">
32 + <div class="col-md-6">
33 + <?php echo $this->getForm()->renderField('name'
); ?>
34 + <?php echo $this->getForm()->renderField('alias
'); ?>
35 + <?php echo $this->getForm()->renderField('
access'); ?>

January 2023 Page 167


Joomla 4 - Developing Extensions 18. Integrate Custom Fields in Backend

36 + <?php echo $this->getForm()->renderField('


published'); ?>
37 + <?php echo $this->getForm()->renderField('
publish_up'); ?>
38 + <?php echo $this->getForm()->renderField('
publish_down'); ?>
39 + <?php echo $this->getForm()->renderField('catid
'); ?>
40 + </div>
41 + </div>
42 + </div>
43 + </div>
44 + <?php echo HTMLHelper::_('uitab.endTab'); ?>
45 +
46 + <?php echo LayoutHelper::render('joomla.edit.params', $this);
?>
47 +
48 + <?php echo HTMLHelper::_('uitab.endTabSet'); ?>
49 + </div>
50 <input type="hidden" name="task" value="">
51 <?php echo HTMLHelper::_('form.token'); ?>
52 </form>

18.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder into the administrator folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the files from the previous part.

2. Open the view of your component in the administration area. You will see entries in the navigation
on the left. Click on the menu item “Fields” in this new menu.

Page 168 January 2023


18. Integrate Custom Fields in Backend Joomla 4 - Developing Extensions

Figure 18.2.: Integrate Joomla Custom Fields into a custom component

3. after that create a custom field of type “text”.

4. make sure that when you edit a foo item, this custom field is also offered for editing.

Figure 18.3.: Integrate Joomla Custom Fields into a custom component

5. make sure that the custom fields can be turned on and off in the global configuration.

January 2023 Page 169


Joomla 4 - Developing Extensions 18. Integrate Custom Fields in Backend

Figure 18.4.: Integrate Joomla Custom Fields into a custom component

Page 170 January 2023


Joomla 4 - Developing Extensions 19. Integrate Custom Fields in Frontend

19. Integrate Custom Fields in Frontend

Very few use custom fields only in the administration area. As usual, an output in the frontend is
required. We will address this question in the current part of the article series. How and where are
custom fields in Joomla displayed in the frontend?

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t14a...t14b

19.1. Step by step

19.1.1. New files

No new files are added in this chapter.

19.1.2. Modified files

19.1.2.1. components/com_foos/src/View/Foo/HtmlView.php

Custom Fields display data in the frontend using events. The custom fields are displayed in three
different places on the website. By default, the data is displayed before the content. This setting can
be changed. Therefore we save the results of onContentAfterTitle, onContentBeforeDisplay
and onContentAfterDisplay. We do this in the View.

Specifically, we make sure that the events

• onContentAfterTitle1 ,
• onContentBeforeDisplay2 und
• onContentAfterDisplay3 are triggered and the result is stored in a variable.
1
docs.joomla.org/Plugin/Events/Content#onContentAfterTitle
2
docs.joomla.org/Plugin/Events/Content#onContentBeforeDisplay
3
docs.joomla.org/Plugin/Events/Content#onContentAfterDisplay

Page 171 January 2023


Joomla 4 - Developing Extensions 19. Integrate Custom Fields in Frontend

Joomla uses the observer design patterna for event handling. This is a software design pattern
where an object maintains a list of its dependents called observers and automatically notifies
them of state changes, usually by calling one of their methods.
a
en.wikipedia.org/wiki/Observer_pattern

components/com_foos/src/View/Foo/HtmlView.php

1 \defined('_JEXEC') or die;
2
3 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
4 +use Joomla\CMS\Factory;
5
6 class HtmlView extends BaseHtmlView
7 public function display($tpl = null)
8 {
9 - $this->item = $this->get('Item');
10 + $item = $this->item = $this->get('Item');
11 +
12 + Factory::getApplication()->triggerEvent('onContentPrepare', ['
com_foos.foo', &$item, &$item->params]);
13 +
14 + // Store the events for later
15 + $item->event = new \stdClass;
16 + $results = Factory::getApplication()->triggerEvent('
onContentAfterTitle', ['com_foos.foo', &$item, &$item->params]);
17 + $item->event->afterDisplayTitle = trim(implode("\n", $results))
;
18 +
19 + $results = Factory::getApplication()->triggerEvent('
onContentBeforeDisplay', ['com_foos.foo', &$item, &$item->params]);
20 + $item->event->beforeDisplayContent = trim(implode("\n",
$results));
21 +
22 + $results = Factory::getApplication()->triggerEvent('
onContentAfterDisplay', ['com_foos.foo', &$item, &$item->params]);
23 + $item->event->afterDisplayContent = trim(implode("\n", $results
));
24
25 return parent::display($tpl);
26 }

Are you wondering why we set &$item->params as parameters for the event methods

• onContentPrepare,
• onContentAfterTitle,
• onContentBeforeDisplay and
• onContentAfterDisplay,

Page 172 January 2023


19. Integrate Custom Fields in Frontend Joomla 4 - Developing Extensions

although we have not yet explicitly implemented &$item->params in the Foo extension? Implicitly,
the populateState method of the file /components/com_foos/src/Model/FooModel.php en-
sures that &$item->params is available. For our example, we do not need this third parameter so far.
However, it is possible that errors may occur in combination with other extensions if this parameter is
not set. Therefore, we set the three mandatory parameters ['com_foos.foo', &$item, &$item
->params] for all event methods.

Via onContentAfterTitle, onContentBeforeDisplay, onContentAfterDisplay, in ad-


dition to the custom fields, other elements are displayed that are mapped to the related event.

19.1.2.2. components/com_foos/ tmpl/foo/default.php

IIn the template we display our custom fields. In our case, this is not complex, so we write all the stored
texts one after the other. In a more complex file, the events are inserted at the correct place.

components/com_foos/tmpl/foo/default.php

1 }
2
3 echo $this->item->name;
4 +
5 +echo $this->item->event->afterDisplayTitle;
6 +echo $this->item->event->beforeDisplayContent;
7 +echo $this->item->event->afterDisplayContent;

19.2. Note: Individual positioning

Custom fields can be positioned as desired in custom overrides.

19.2.1. Calling a field in an override

Basically, all custom fields that belong to the current item are available in the template components/
com_foos/tmpl/foo/default.php. The call is made via a property of the variable $this->item
with the name jcfields. The variable $this->item->jcfields is an array which contains for
example the following data per field:

1 stdClass Object
2 (
3 [id] => 1
4 [alias] => nina
5 [publish_down] =>

January 2023 Page 173


Joomla 4 - Developing Extensions 19. Integrate Custom Fields in Frontend

6 [publish_up] =>
7 [published] => 0
8 [state] => 0
9 [catid] => 8
10 [access] => 1
11 [name] => Nina
12 [params] =>
13 [jcfields] => Array
14 (
15 [1] => stdClass Object
16 (
17 [id] => 1
18 [title] => Text Custom Field
19 [name] => text-custom-field
20 ...
21 )
22
23 )
24
25 [event] => stdClass Object
26 (
27 [afterDisplayTitle] =>
28 [beforeDisplayContent] =>
29
30
31
32 Text Custom Field:
33 Custom Field Value
34
35
36 [afterDisplayContent] =>
37 )
38
39 )

19.2.2. Render the field individually

To render the field in the template components/com_foos/tmpl/foo/default.php you can use


FieldsHelper::render().

You can find the class FieldsHelper in the file administrator/components/com_fields/


src/Helper/FieldsHelper.php, if you want to have a closer look at the method itself.

19.2.2.1. A simple override

1 <?php foreach ($this->item->jcfields as $field) : ?>

Page 174 January 2023


19. Integrate Custom Fields in Frontend Joomla 4 - Developing Extensions

2 <?php echo $field->label . ':' . $field->value; ?>


3 <?php endforeach ?>

19.2.2.2. Override using FieldsHelper

1<?php JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/


components/com_fields/helpers/fields.php'); ?>
2 <?php foreach ($this->item->jcfields as $field) : ?>
3 <?php echo FieldsHelper::render($field->context, 'field.render', array(
'field' => $field)); ?>
4 <?php endforeach ?>

19.2.3. Load fields individually

To add individual fields to the content, first select specific names for the custom fields. This way it is
possible to directly address the field in the override code of the file components/com_foos/tmpl
/foo/default.php using the field name. In the Joomla backend set the automatic display for the
field to No. This way you prevent it from being automatically displayed at one of the default positions.
Add the following code at the beginning of the template file components/com_foos/tmpl/foo/
default.php or its override to use direct access by name to fields in the overrides.

1 <?php
2 foreach($item->jcfields as $jcfield) {
3 $item->jcFields[$jcfield->name] = $jcfield;
4 }
5 ?>

Finally, add the insert statement for the custom field where you want the field label or value to appear.

1 <?php echo $item->jcFields['DEINFELDNAME']->label; ?>


2 <?php echo $item->jcFields['DEINFELDNAME']->value; ?>

You can find more information in the Joomla documentationa


a
docs.joomla.org/J3.x:Adding_custom_fields/Overrides/de

19.3. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the components folder
into the components folder of your Joomla 4 installation. A new installation is not necessary.
Continue using the files from the previous part.

January 2023 Page 175


Joomla 4 - Developing Extensions 19. Integrate Custom Fields in Frontend

2. open the view of your component in the administration area.

3. after that create a custom field of type “text”, if you didn’t do it in the previous chapter.

4. edit a published foo item. Make sure you add a value to the custom field.

Figure 19.1.: Integrate Joomla Custom Fields into a custom component | Assign a value to the field in
the backend.

5. at the end open the detail view of the just edited Foo item. Create a menu item for this. You will
see next to the previously existing values now additionally the text you entered in the custom
field.

Page 176 January 2023


19. Integrate Custom Fields in Frontend Joomla 4 - Developing Extensions

Figure 19.2.: Integrate Joomla Custom Fields into a custom component | Display value of the field in
the frontend.

January 2023 Page 177


Joomla 4 - Developing Extensions 20. Multilingual Associations

20. Multilingual Associations

With Joomla it is possible to set up a multilingual website without installing third party extensions. In
this tutorial, I’ll show you how to program your component to support Joomlas multilingual feature.

Multilingualism and language links: Multilingual content, menu items and language switches are
set up with a standard Joomla installation without any additional extensions. Until version 3.7,
Joomla required switching between views to translate content. Since 3.7 there is an improvement
in usability, the so-called Multilingual Associations. With this extension, multilingual content can
be created and linked in a user-friendly way. Thereby one remains in one view. The language links
show incidentally which multilingual content is missing.

The chapter is one of the most extensive in this series. For that it covers all areas of multilingualism
and language links in Joomla.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t14b...t15a

20.1. Step by step

20.1.1. New files

So that the language is saved to the element, we add a column to the database table. When updating
the component, the script 15.0.0.sql is the one that is executed for version 15.0.0.

20.1.1.1. administrator/components/com_foos/ sql/updates/mysql/15.0.0.sql

administrator/components/com_foos/sql/updates/mysql/15.0.0.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `language ` char(7) NOT NULL
DEFAULT '*' AFTER `alias`;
3

Page 179 January 2023


Joomla 4 - Developing Extensions 20. Multilingual Associations

4 ALTER TABLE `#__foos_details ` ADD KEY `idx_language ` ( `language`);

20.1.1.2. administrator/components/com_foos/ src/Helper/AssociationsHelper.php

The helper file AssociationsHelper.php is the interface to the language associations component
com_associations. AssociationsHelper.php exists in the frontend and the backend - we’ll look
at the latter first, the frontend version comes later in this chapter.

In this helper file we provide the details that are specific to our component, so that Joomla’s own
routines can find their way around in our component.

administrator/components/com_foos/src/Helper/AssociationsHelper.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Helper;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Association\AssociationExtensionHelper;
9 use Joomla\CMS\Language\Associations;
10 use Joomla\CMS\Table\Table;
11 use FooNamespace\Component\Foos\Site\Helper\AssociationHelper;
12
13 class AssociationsHelper extends AssociationExtensionHelper
14 {
15 protected $extension = 'com_foos';
16
17 protected $itemTypes = ['foo', 'category'];
18
19 protected $associationsSupport = true;
20
21 public function getAssociationsForItem($id = 0, $view = null)
22 {
23 return AssociationHelper::getAssociations($id, $view);
24 }
25
26 public function getAssociations($typeName, $id)
27 {
28 $type = $this->getType($typeName);
29
30 $context = $this->extension . '.item';
31 $catidField = 'catid';
32
33 if ($typeName === 'category') {
34 $context = 'com_categories.item';
35 $catidField = '';

Page 180 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

36 }
37
38 // Get the associations.
39 $associations = Associations::getAssociations(
40 $this->extension,
41 $type['tables']['a'],
42 $context,
43 $id,
44 'id',
45 'alias',
46 $catidField
47 );
48
49 return $associations;
50 }
51
52 public function getItem($typeName, $id)
53 {
54 if (empty($id)) {
55 return null;
56 }
57
58 $table = null;
59
60 switch ($typeName) {
61 case 'foo':
62 $table = Table::getInstance('FooTable', 'FooNamespace\\
Component\\Foos\\Administrator\\Table\\');
63 break;
64
65 case 'category':
66 $table = Table::getInstance('Category');
67 break;
68 }
69
70 if (empty($table)) {
71 return null;
72 }
73
74 $table->load($id);
75
76 return $table;
77 }
78
79 public function getType($typeName = '')
80 {
81 $fields = $this->getFieldsTemplate();
82 $tables = [];
83 $joins = [];
84 $support = $this->getSupportTemplate();
85 $title = '';

January 2023 Page 181


Joomla 4 - Developing Extensions 20. Multilingual Associations

86
87 if (in_array($typeName, $this->itemTypes)) {
88 switch ($typeName) {
89 case 'foo':
90 $fields['title'] = 'a.name';
91 $fields['state'] = 'a.published';
92
93 $support['state'] = true;
94 $support['acl'] = true;
95 $support['category'] = true;
96 $support['save2copy'] = true;
97
98 $tables = [
99 'a' => '#__foos_details'
100 ];
101
102 $title = 'foo';
103 break;
104
105 case 'category':
106 $fields['created_user_id'] = 'a.created_user_id';
107 $fields['ordering'] = 'a.lft';
108 $fields['level'] = 'a.level';
109 $fields['catid'] = '';
110 $fields['state'] = 'a.published';
111
112 $support['state'] = true;
113 $support['acl'] = true;
114 $support['checkout'] = false;
115 $support['level'] = false;
116
117 $tables = [
118 'a' => '#__categories'
119 ];
120
121 $title = 'category';
122 break;
123 }
124 }
125
126 return [
127 'fields' => $fields,
128 'support' => $support,
129 'tables' => $tables,
130 'joins' => $joins,
131 'title' => $title
132 ];
133 }
134
135 protected function getFieldsTemplate()
136 {

Page 182 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

137 return [
138 'id' => 'a.id',
139 'title' => 'a.title',
140 'alias' => 'a.alias',
141 'ordering' => 'a.id',
142 'menutype' => '',
143 'level' => '',
144 'catid' => 'a.catid',
145 'language' => 'a.language',
146 'access' => 'a.access',
147 'state' => 'a.state',
148 'created_user_id' => '',
149 'checked_out' => '',
150 'checked_out_time' => ''
151 ];
152 }
153 }

20.1.1.3. components/com_foos/src/Helper/AssociationHelper.php

The AssociationsHelper.php helper file is the interface to the com_associations language as-
sociations component. In it we configure the information that is specific to our component. Once this
is done, Joomla’s own routines take over and we don’t reinvent the wheel.

Attention: I had already written it: The class AssociationsHelper.php exists in the frontend
and in the backend: src/components/com_foos/src/Helper/AssociationHelper.php
and src/ administrator /components/com_foos/src/Helper/AssociationHelper.
php. We had already looked at the file for the backend before.

components/com_foos/src/Helper/AssociationHelper.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Helper;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\Language\Associations;
10 use Joomla\Component\Categories\Administrator\Helper\
CategoryAssociationHelper;
11 use FooNamespace\Component\Foos\Site\Helper\RouteHelper;
12
13 abstract class AssociationHelper extends CategoryAssociationHelper
14 {
15 public static function getAssociations($id = 0, $view = null)

January 2023 Page 183


Joomla 4 - Developing Extensions 20. Multilingual Associations

16 {
17 $jinput = Factory::getApplication()->input;
18 $view = $view ?? $jinput->get('view');
19 $id = empty($id) ? $jinput->getInt('id') : $id;
20
21 if ($view === 'foos') {
22 if ($id) {
23 $associations = Associations::getAssociations('com_foos
', '#__foos_details', 'com_foos.item', $id);
24
25 $return = [];
26
27 foreach ($associations as $tag => $item) {
28 $return[$tag] = RouteHelper::getFoosRoute($item->id
, (int) $item->catid, $item->language);
29 }
30
31 return $return;
32 }
33 }
34
35 if ($view === 'category' || $view === 'categories') {
36 return self::getCategoryAssociations($id, 'com_foos');
37 }
38
39 return [];
40 }
41 }

20.1.1.4. components/com_foos/src/Helper/RouteHelper.php

We create the class RouteHelper to correctly compose the links we create in this chapter. Within the
link there is one more piece of information as a parameter: the language.

components/com_foos/src/Helper/RouteHelper.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Helper;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Categories\CategoryNode;
9 use Joomla\CMS\Language\Multilanguage;
10
11 abstract class RouteHelper
12 {
13 public static function getFoosRoute($id, $catid, $language = 0)

Page 184 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

14 {
15 // Create the link
16 $link = 'index.php?option=com_foos&view=foos&id=' . $id;
17
18 if ($catid > 1) {
19 $link .= '&catid=' . $catid;
20 }
21
22 if ($language && $language !== '*' && Multilanguage::isEnabled
()) {
23 $link .= '&lang=' . $language;
24 }
25
26 return $link;
27 }
28
29 public static function getFooRoute($id, $catid, $language = 0)
30 {
31 // Create the link
32 $link = 'index.php?option=com_foos&view=foo&id=' . $id;
33
34 if ($catid > 1) {
35 $link .= '&catid=' . $catid;
36 }
37
38 if ($language && $language !== '*' && Multilanguage::isEnabled
()) {
39 $link .= '&lang=' . $language;
40 }
41
42 return $link;
43 }
44
45 public static function getCategoryRoute($catid, $language = 0)
46 {
47 if ($catid instanceof CategoryNode) {
48 $id = $catid->id;
49 } else {
50 $id = (int) $catid;
51 }
52
53 if ($id < 1) {
54 $link = '';
55 } else {
56 // Create the link
57 $link = 'index.php?option=com_foos&view=category&id=' . $id
;
58
59 if ($language && $language !== '*' && Multilanguage::
isEnabled()) {
60 $link .= '&lang=' . $language;

January 2023 Page 185


Joomla 4 - Developing Extensions 20. Multilingual Associations

61 }
62 }
63
64 return $link;
65 }
66 }

20.1.2. Modified files

20.1.2.1. administrator/components/com_foos/ forms/foo.xml

We create a field through which an author selects the language link. This is the field name="language".
In order for Joomla to find this field, we add the path in the form addfieldprefix= "FooNamespace
\Component\Foos\Administrator\Field" as a parameter in the <fieldset>.

administrator/components/com_foos/forms/foo.xml

1 <?xml version="1.0" encoding="utf-8"?>


2 <form>
3 - <fieldset addruleprefix="FooNamespace\Component\Foos\Administrator\
Rule">t
4 + <fieldset
5 + addruleprefix="FooNamespace\Component\Foos\Administrator\Rule"
6 + addfieldprefix="FooNamespace\Component\Foos\Administrator\Field
"
7 + >
8 <field
9 name="id"
10 type="number"
11
12 hint="JFIELD_ALIAS_PLACEHOLDER"
13 />
14
15 + <field
16 + name="language"
17 + type="contentlanguage"
18 + label="JFIELD_LANGUAGE_LABEL"
19 + >
20 + <option value="*">JALL</option>
21 + </field>
22 +
23 <field
24 name="published"
25 type="list"

Page 186 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

20.1.2.2. administrator/components/com_foos/ services/provider.php

In the provider we register our AssociationsHelper as a service that implements AssociationExtensionInterfa


1 . This way we ensure that all necessary functions are inherited into our component and are thus

available.
administrator/components/com_foos/services/provider.php

1 use Joomla\DI\Container;
2 use Joomla\DI\ServiceProviderInterface;
3 use FooNamespace\Component\Foos\Administrator\Extension\FoosComponent;
4 +use FooNamespace\Component\Foos\Administrator\Helper\
AssociationsHelper;
5 +use Joomla\CMS\Association\AssociationExtensionInterface;
6
7
8 public function register(Container $container)
9 {
10 + $container->set(AssociationExtensionInterface::class, new
AssociationsHelper);
11 +
12 $container->registerServiceProvider(new CategoryFactory('\\
FooNamespace\\Component\\Foos'));
13 $container->registerServiceProvider(new MVCFactory('\\
FooNamespace\\Component\\Foos'));
14 $container->registerServiceProvider(new
ComponentDispatcherFactory('\\FooNamespace\\Component\\Foos'
));
15 function (Container $container) {
16 $component->setRegistry($container->get(Registry::class
));
17 $component->setMVCFactory($container->get(
MVCFactoryInterface::class));
18 $component->setCategoryFactory($container->get(
CategoryFactoryInterface::class));
19 + $component->setAssociationExtension($container->get(
AssociationExtensionInterface::class));
20
21 return $component;
22 }

20.1.2.3. administrator/components/com_foos/ sql/install.mysql.utf8.sql

administrator/components/com_foos//install.mysql.utf8.sql
In order for the language to be saved to the element, we add a column in the database table. For new
installations, the script install.mysql.utf8.sql is the one that is called.
1
libraries/src/association/associationextensioninterface.php

January 2023 Page 187


Joomla 4 - Developing Extensions 20. Multilingual Associations

1 ALTER TABLE `#__foos_details ` ADD COLUMN `publish_down ` datetime


AFTER `alias`;
2
3 ALTER TABLE `#__foos_details ` ADD KEY `idx_state ` ( `published`);
4 +
5 +ALTER TABLE `#__foos_details ` ADD COLUMN `language ` char(7) NOT NULL
DEFAULT '*' AFTER `alias`;
6 +
7 +ALTER TABLE `#__foos_details ` ADD KEY `idx_language ` ( `language`);

20.1.2.4. administrator/components/com_foos/ src/Extension/FoosComponent.php

In FoosComponent we add AssociationServiceInterface and AssociationServiceTrait so


that everything necessary is implemented in our extension.

Traits are a code reuse mechanism used in programming languages with simple inheritance like
PHP.

administrator/components/com_foos/src/Extension/FoosComponent.php

1
2
3 defined('JPATH_PLATFORM') or die;
4
5 +use Joomla\CMS\Association\AssociationServiceInterface;
6 +use Joomla\CMS\Association\AssociationServiceTrait;
7 use Joomla\CMS\Categories\CategoryServiceInterface;
8 use Joomla\CMS\Categories\CategoryServiceTrait;
9 use Joomla\CMS\Extension\BootableExtensionInterface;
10
11 -class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface
12 +class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface,
AssociationServiceInterface
13 {
14 use CategoryServiceTrait;
15 + use AssociationServiceTrait;
16 use HTMLRegistryAwareTrait;

20.1.2.5. administrator/components/com_foos/ src/Field/Modal/FooField.php

We previously used the modal to select a Foo item using a popup when creating a menu item. Now we
use it again to select a language link. To make sure that only the matching languages are displayed, we
extend the URL with the language information.

Page 188 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

administrator/components/com_foos/src/Field/Modal/FooField.php

1 // Setup variables for display.


2 $linkFoos = 'index.php?option=com_foos&amp;view=foos&amp;layout
=modal&amp;tmpl=component&amp;'
3 . Session::getFormToken() . '=1';
4 - $linkFoo = 'index.php?option=com_foos&amp;view=foo&amp;layout=
modal&amp;tmpl=component&amp;'
5 - . Session::getFormToken() . '=1';
6 $modalTitle = Text::_('COM_FOOS_CHANGE_FOO');
7
8 + if (isset($this->element['language'])) {
9 + $linkFoos .= '&amp;forcedLanguage=' . $this->element['
language'];
10 + $modalTitle .= ' &#8212; ' . $this->element['label'];
11 + }
12 +
13 $urlSelect = $linkFoos . '&amp;function=jSelectFoo_' . $this->
id;
14
15 if ($value) {

Are you confused by the characters &#8212;a or &amp;b ? They are quite harmless. &#8212;
is nothing more than a dash[en.wikipedia.org/wiki/Dash#En_dash] -. &amp; stands for the
ampersand character &. In HTML, the latter stands for the beginning of an entity reference. Thus
it is a special character. If you use such a character in a text that is checked for security reasons,
you should use the encoded entity &amp; - more technical stuff on w3c.orgc . For the dash -, we
use Unicoded . The goal in this case is to unify the use of different and incompatible encodings in
different countries or cultures.
a
unicode-table.com/en/2014/
b
unicode-table.com/de/0026/
c
w3.org/tr/xhtml1/guidelines.html#c_12
d
en.wikipedia.org/wiki/unicode

20.1.2.6. administrator/components/com_foos/ src/Model/FooModel.php

We extend the model administrator/components/com_foos/src/Model/FooModel.php used


to build the data of an item in regard to the language. In this case getItem and preprocessForm
play the essential role.

administrator/components/com_foos/src/Model/FooModel.php

1 \defined('_JEXEC') or die;
2
3 use Joomla\CMS\Factory;
4 +use Joomla\CMS\Language\Associations;

January 2023 Page 189


Joomla 4 - Developing Extensions 20. Multilingual Associations

5 use Joomla\CMS\MVC\Model\AdminModel;
6 +use Joomla\CMS\Language\LanguageHelper;
7
8 class FooModel extends AdminModel
9 public $typeAlias = 'com_foos.foo';
10
11 + protected $associationsContext = 'com_foos.item';
12 +
13 protected function loadFormData()
14 return $data;
15 }
16
17 + public function getItem($pk = null)
18 + {
19 + $item = parent::getItem($pk);
20 +
21 + // Load associated foo items
22 + $assoc = Associations::isEnabled();
23 +
24 + if ($assoc) {
25 + $item->associations = [];
26 +
27 + if ($item->id != null) {
28 + $associations = Associations::getAssociations('com_foos
', '#__foos_details', 'com_foos.item', $item->id, 'id', null);
29 +
30 + foreach ($associations as $tag => $association) {
31 + $item->associations[$tag] = $association->id;
32 + }
33 + }
34 + }
35 +
36 + return $item;
37 + }
38 +
39 + protected function preprocessForm(\JForm $form, $data, $group = '
content')
40 + {
41 + if (Associations::isEnabled()) {
42 + $languages = LanguageHelper::getContentLanguages(false,
true, null, 'ordering', 'asc');
43 +
44 + if (count($languages) > 1) {
45 + $addform = new \SimpleXMLElement('<form />');
46 + $fields = $addform->addChild('fields');
47 + $fields->addAttribute('name', 'associations');
48 + $fieldset = $fields->addChild('fieldset');
49 + $fieldset->addAttribute('name', 'item_associations');
50 +
51 + foreach ($languages as $language) {
52 + $field = $fieldset->addChild('field');

Page 190 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

53 + $field->addAttribute('name', $language->lang_code);
54 + $field->addAttribute('type', 'modal_foo');
55 + $field->addAttribute('language', $language->
lang_code);
56 + $field->addAttribute('label', $language->title);
57 + $field->addAttribute('translate_label', 'false');
58 + $field->addAttribute('select', 'true');
59 + $field->addAttribute('new', 'true');
60 + $field->addAttribute('edit', 'true');
61 + $field->addAttribute('clear', 'true');
62 + }
63 +
64 + $form->load($addform, false);
65 + }
66 + }
67 +
68 + parent::preprocessForm($form, $data, $group);
69 + }
70 +

20.1.2.7. administrator/components/com_foos/ src/Model/FoosModel.php

Note: FooModel.php is the model which calculates the data for an element. FoosModel.php -
note the s - is the list view model - it handles data for a group of elements.

In the model of the list, besides adding the language information, it is important to update the state via
populateState. Otherwise the correct language will not be active at each time. The state includes
the information which language is active.

administrator/components/com_foos/src/Model/FoosModel.php

1 \defined('_JEXEC') or die;
2
3 use Joomla\CMS\MVC\Model\ListModel;
4 +use Joomla\CMS\Language\Associations;
5 +use Joomla\CMS\Factory;
6
7
8
9 // Select the required fields from the table.
10 $query->select(
11 - $db->quoteName(array('a.id', 'a.name', 'a.alias', 'a.access
', 'a.catid', 'a.published', 'a.publish_up', 'a.publish_down'))
12 + $db->quoteName(
13 + array(
14 + 'a.id', 'a.name', 'a.alias', 'a.access',
15 + 'a.catid', 'a.published', 'a.publish_up', 'a.
publish_down',

January 2023 Page 191


Joomla 4 - Developing Extensions 20. Multilingual Associations

16 + 'a.language'
17 + )
18 + )
19 );
20
21 $query->from($db->quoteName('#__foos_details', 'a'));
22
23 $db->quoteName('#__categories', 'c') . ' ON ' . $db->
quoteName('c.id') . ' = ' . $db->quoteName('a.catid'
)
24 );
25
26 + // Join over the language
27 + $query->select($db->quoteName('l.title', 'language_title'))
28 + ->select($db->quoteName('l.image', 'language_image'))
29 + ->join(
30 + 'LEFT',
31 + $db->quoteName('#__languages', 'l') . ' ON ' . $db->
quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')
32 + );
33 +
34 + // Join over the associations.
35 + if (Associations::isEnabled())
36 + {
37 + $subQuery = $db->getQuery(true)
38 + ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1
')
39 + ->from($db->quoteName('#__associations', 'asso1'))
40 + ->join('INNER', $db->quoteName('#__associations', '
asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2
.key'))
41 + ->where(
42 + [
43 + $db->quoteName('asso1.id') . ' = ' . $db->
quoteName('a.id'),
44 + $db->quoteName('asso1.context') . ' = ' . $db->
quote('com_foos.item'),
45 + ]
46 + );
47 +
48 + $query->select('(' . $subQuery . ') AS ' . $db->quoteName('
association'));
49 + }
50 +
51 + // Filter on the language.
52 + if ($language = $this->getState('filter.language'))
53 + {
54 + $query->where($db->quoteName('a.language') . ' = ' . $db->
quote($language));
55 + }
56 +

Page 192 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

57 return $query;
58 }
59 +
60 + protected function populateState($ordering = 'a.name', $direction =
'asc')
61 + {
62 + $app = Factory::getApplication();
63 + $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd')
;
64 +
65 + // Adjust the context to support modal layouts.
66 + if ($layout = $app->input->get('layout'))
67 + {
68 + $this->context .= '.' . $layout;
69 + }
70 +
71 + // Adjust the context to support forced languages.
72 + if ($forcedLanguage)
73 + {
74 + $this->context .= '.' . $forcedLanguage;
75 + }
76 +
77 + // List state information.
78 + parent::populateState($ordering, $direction);
79 +
80 + // Force a language.
81 + if (!empty($forcedLanguage))
82 + {
83 + $this->setState('filter.language', $forcedLanguage);
84 + }
85 + }
86 }

20.1.2.8. administrator/components/com_foos/ src/Service/HTML/AdministratorService.php

We implement the association service in AdministratorService.php. Via the ID the function


returns the HTML markup for editing the language links.

administrator/components/com_foos/src/Service/HTML/AdministratorService.php

1
2 defined('JPATH_BASE') or die;
3
4 +use Joomla\CMS\Factory;
5 +use Joomla\CMS\Language\Associations;
6 +use Joomla\CMS\Language\Text;
7 +use Joomla\CMS\Layout\LayoutHelper;
8 +use Joomla\CMS\Router\Route;
9

January 2023 Page 193


Joomla 4 - Developing Extensions 20. Multilingual Associations

10 class AdministratorService
11 {
12
13 + public function association($fooid)
14 + {
15 + // Defaults
16 + $html = '';
17 +
18 + // Get the associations
19 + if ($associations = Associations::getAssociations('com_foos', '
#__foos_details', 'com_foos.item', $fooid, 'id', null)) {
20 + foreach ($associations as $tag => $associated) {
21 + $associations[$tag] = (int) $associated->id;
22 + }
23 +
24 + // Get the associated foo items
25 + $db = Factory::getDbo();
26 + $query = $db->getQuery(true)
27 + ->select('c.id, c.name as title')
28 + ->select('l.sef as lang_sef, lang_code')
29 + ->from('#__foos_details as c')
30 + ->select('cat.title as category_title')
31 + ->join('LEFT', '#__categories as cat ON cat.id=c.catid'
)
32 + ->where('c.id IN (' . implode(',', array_values(
$associations)) . ')')
33 + ->where('c.id != ' . $fooid)
34 + ->join('LEFT', '#__languages as l ON c.language=l.
lang_code')
35 + ->select('l.image')
36 + ->select('l.title as language_title');
37 + $db->setQuery($query);
38 +
39 + try {
40 + $items = $db->loadObjectList('id');
41 + } catch (\RuntimeException $e) {
42 + throw new \Exception($e->getMessage(), 500, $e);
43 + }
44 +
45 + if ($items) {
46 + foreach ($items as &$item) {
47 + $text = strtoupper($item->lang_sef);
48 + $url = Route::_('index.php?option=com_foos&task=foo
.edit&id=' . (int) $item->id);
49 + $tooltip = '<strong>' . htmlspecialchars($item->
language_title, ENT_QUOTES, 'UTF-8') . '</strong><br>'
50 + . htmlspecialchars($item->title, ENT_QUOTES, '
UTF-8') . '<br>' . Text::sprintf('JCATEGORY_SPRINTF', $item->
category_title);
51 + $classes = 'badge bg-secondary';
52 +

Page 194 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

53 + $item->link = '<a href="' . $url . '" title="' .


$item->language_title . '" class="' . $classes . '">' . $text . '</a
>'
54 + . '<div role="tooltip" id="tip' . (int) $item->
id . '">' . $tooltip . '</div>';
55 + }
56 + }
57 +
58 + $html = LayoutHelper::render('joomla.content.associations',
$items);
59 + }
60 +
61 + return $html;
62 + }
63 }

20.1.2.9. administrator/components/com_foos/ src/View/Foo/HtmlView.php

If only one language is possible or changing it is not desired, we set the value of the language selection
field and protected it from write access. Also, only categories of this language are selectable.

administrator/components/com_foos/src/View/Foo/HtmlView.php

1 $this->form = $this->get('Form');
2 $this->item = $this->get('Item');
3
4 + // If we are forcing a language in modal (used for associations
).
5 + if ($this->getLayout() === 'modal' && $forcedLanguage = Factory
::getApplication()->input->get('forcedLanguage', '', 'cmd')) {
6 + // Set the language field to the forcedLanguage and disable
changing it.
7 + $this->form->setValue('language', null, $forcedLanguage);
8 + $this->form->setFieldAttribute('language', 'readonly', '
true');
9 +
10 + // Only allow to select categories with All language or
with the forced language.
11 + $this->form->setFieldAttribute('catid', 'language', '*,' .
$forcedLanguage);
12 + }
13 +
14 $this->addToolbar();
15
16 return parent::display($tpl);

January 2023 Page 195


Joomla 4 - Developing Extensions 20. Multilingual Associations

20.1.2.10. administrator/components/com_foos/ src/View/Foos/HtmlView.php

The view of the list should contain the sidebar and the toolbar if it is not a modal view or a popup. If
the view is modal, the toolbar and sidebar will confuse. In that case we filter the items automatically
according to the currently active language.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;


2 use Joomla\CMS\Toolbar\Toolbar;
3 use Joomla\CMS\Toolbar\ToolbarHelper;
4 +use Joomla\CMS\Factory;
5
6 public function display($tpl = null): void
7 $this->setLayout('emptystate');
8 }
9
10 - $this->addToolbar();
11 + // We don't need toolbar in the modal window.
12 + if ($this->getLayout() !== 'modal') {
13 + $this->addToolbar();
14 + $this->sidebar = \JHtmlSidebar::render();
15 + } else {
16 + // In article associations modal we need to remove language
filter if forcing a language.
17 + // We also need to change the category filter to show show
categories with All or the forced language.
18 + if ($forcedLanguage = Factory::getApplication()->input->get
('forcedLanguage', '', 'CMD')) {
19 + // If the language is forced we can't allow to select
the language, so transform the language selector filter into a
hidden field.
20 + $languageXml = new \SimpleXMLElement('<field name="
language" type="hidden" default="' . $forcedLanguage . '" />');
21 + }
22 + }
23
24 parent::display($tpl);
25 }

20.1.2.11. administrator/components/com_foos/ tmpl/foo/edit.php

In the form for editing an element we add a form field for specifying the language. For this we use the lay-
out administrator/components/com_foos/tmpl/foo/edit_associations.php created ear-
lier in this part.

Page 196 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

Why the layout edit_associations.php is called in the file edit.php with the name
associations, you might already think. In the part about the layouts, I go into this in more
detail.

administrator/components/com_foos/tmpl/foo/edit.php

1 use Joomla\CMS\Factory;
2 use Joomla\CMS\HTML\HTMLHelper;
3 +use Joomla\CMS\Language\Associations;
4 use Joomla\CMS\Router\Route;
5 use Joomla\CMS\Language\Text;
6 use Joomla\CMS\Layout\LayoutHelper;
7
8 $app = Factory::getApplication();
9 $input = $app->input;
10
11 +$assoc = Associations::isEnabled();
12 +
13 +$this->ignore_fieldsets = ['item_associations'];
14 $this->useCoreUI = true;
15
16 +$isModal = $input->get('layout') === 'modal';
17
18 $wa = $this->document->getWebAssetManager();
19
20 <?php echo $this->getForm()->renderField('
publish_up'); ?>
21 <?php echo $this->getForm()->renderField('
publish_down'); ?>
22 <?php echo $this->getForm()->renderField('catid
'); ?>
23 + <?php echo $this->getForm()->renderField('
language'); ?>
24 </div>
25 </div>
26 </div>
27 </div>
28 <?php echo HTMLHelper::_('uitab.endTab'); ?>
29
30 + <?php if (!$isModal && $assoc) : ?>
31 + <?php echo HTMLHelper::_('uitab.addTab', 'myTab
', 'associations', Text::_('JGLOBAL_FIELDSET_ASSOCIATIONS')); ?>
32 + <fieldset id="fieldset-associations" class="
options-form">
33 + <legend><?php echo Text::_('
JGLOBAL_FIELDSET_ASSOCIATIONS'); ?></legend>
34 + <div>
35 + <?php echo LayoutHelper::render
('joomla.edit.associations', $this); ?>
36 + </div>

January 2023 Page 197


Joomla 4 - Developing Extensions 20. Multilingual Associations

37 + </fieldset>
38 + <?php echo HTMLHelper::_('uitab.endTab'); ?>
39 + <?php elseif ($isModal && $assoc) : ?>
40 + <div class="hidden"><?php echo LayoutHelper::
render('joomla.edit.associations', $this); ?></div>
41 + <?php endif; ?>
42
43 <?php echo LayoutHelper::render('joomla.edit.params', $this);
?>
44
45 <?php echo HTMLHelper::_('uitab.endTabSet'); ?>

20.1.2.12. administrator/components/com_foos/ tmpl/foos/default.php

In the components overview in the administration area, we add columns to display the language infor-
mation. We display these columns only when it is required. This is the case when language associations
and multilingualism are enabled. To find this out we use Joomla’s own functions Associations::
isEnabled() and Multilanguage::isEnabled().

administrator/components/com_foos/tmpl/foos/default.php

1 use Joomla\CMS\HTML\HTMLHelper;
2 use Joomla\CMS\Language\Text;
3 use Joomla\CMS\Router\Route;
4 +use Joomla\CMS\Language\Multilanguage;
5 +use Joomla\CMS\Language\Associations;
6 +use Joomla\CMS\Layout\LayoutHelper;
7 +
8 +$assoc = Associations::isEnabled();
9 +
10 ?>
11 <form action="<?php echo Route::_('index.php?option=com_foos'); ?>"
method="post" name="adminForm" id="adminForm">
12 <div class="row">
13
14 <th scope="col" style="width:10%" class
="d-none d-md-table-cell">
15 <?php echo TEXT::_('
JGRID_HEADING_ACCESS') ?>
16 </th>
17 + <?php if ($assoc) : ?>
18 + <th scope="col" style="width:10%">
19 + <?php echo Text::_('
COM_FOOS_HEADING_ASSOCIATION'); ?>
20 + </th>
21 + <?php endif; ?>
22 + <?php if (Multilanguage::isEnabled()) :
?>

Page 198 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

23 + <th scope="col" style="width:10%"


class="d-none d-md-table-cell">
24 + <?php echo Text::_('
JGRID_HEADING_LANGUAGE'); ?>
25 + </th>
26 + <?php endif; ?>
27 <th scope="col">
28 <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_ID');
?>
29 </th>
30
31 <td class="small d-none d-md-table-cell
">
32 <?php echo $item->access_level; ?>
33 </td>
34 +
35 + <?php if ($assoc) : ?>
36 + <td class="d-none d-md-table-cell">
37 + <?php if ($item->association) : ?>
38 + <?php
39 + echo HTMLHelper::_('
foosadministrator.association', $item->id);
40 + ?>
41 + <?php endif; ?>
42 + </td>
43 + <?php endif; ?>
44 + <?php if (Multilanguage::isEnabled()) :
?>
45 + <td class="small d-none d-md-table-
cell">
46 + <?php echo LayoutHelper::render
('joomla.content.language', $item); ?>
47 + </td>
48 + <?php endif; ?>
49 +
50 <td class="d-none d-md-table-cell">
51 <?php echo $item->id; ?>
52 </td>

20.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation.

2. the database has been changed, so it is necessary to update it. Open the System |

January 2023 Page 199


Joomla 4 - Developing Extensions 20. Multilingual Associations

Information | Database section as described in part Publish and Unpublish. Select


your component and click on Update Structure.

3. install at least one more language via System | Install | Languages. I chose the german
and the persian language.

Persiana is together with Arabic, Hebrew, Pashto, Urdu, and Sindhi one of the most widely used
RTLb writing systems of modern times and can therefore be used to test the RTL integration in
Joomla. In a right-to-left, top-to-bottom scriptc (often abbreviated as right-to-left or RTL), one
writes from right to left on a page, with new lines written from top to bottom. This is in contrast to
the left-to-right writing system, where writing starts from the left and continues to the right.
a
wikipedia.org/wiki/persian_language
b
wikipedia.org/wiki/right-to-left_script
c

Figure 20.1.: Joomla Language Associations - Multilingual Associations in your extension

4. make sure via System | Manage | Plugins that the plugin System - Language Filter
is published.

Page 200 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

Figure 20.2.: Joomla Language Associations - Multilingual Associations in your extension

5. open the view of an item of your component in the administration area and make sure that the
Language is editable. Change it from All to any language.

Figure 20.3.: Joomla Language Links - Multilingual Associations in your extension

7. play with the language associations and make sure that everything is associated correctly. Pay
attention to the settings for the assigned categories.

January 2023 Page 201


Joomla 4 - Developing Extensions 20. Multilingual Associations

Figure 20.4.: Joomla Language Links - Multilingual Associations in your extension

Figure 20.5.: Joomla Language Links - Multilingual Associations in your extension

8. extend the tests to the component “Multilingual Associations”. This supports your extension as
well.

Page 202 January 2023


20. Multilingual Associations Joomla 4 - Developing Extensions

Figure 20.6.: Joomla Language Associations - Multilingual Associations in your extension

Figure 20.7.: Joomla Language Associations - Multilingual Associations in your extension

January 2023 Page 203


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

21. Filter, Sort, Search

Filtering, sorting and searching - now we organize the Joomla 4 component! Joomla offers view
filters and search tools with which you can limit the number of visible items. If the status filter is set
accordingly, only items whose status is published will be displayed. Beside the status filter the search
tools offer the search by title or content and the possibility to sort the table, i.e. to change the order.

For impatient people: Look at the changed program code in the Diff viewa and take over these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t15a...t16

21.1. Step by step

21.1.1. New files

21.1.1.1. administrator/components/com_foos/ forms/filter_foos.xml

First, we create the form through which the filters will be set.

administrator/components/com_foos/forms/filter_foos.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <form>
4
5 <fields name="filter">
6
7 <field
8 name="search"
9 type="text"
10 inputmode="search"
11 label="COM_FOOS_FILTER_SEARCH_LABEL"
12 description="COM_FOOS_FILTER_SEARCH_DESC"
13 hint="JSEARCH_FILTER"
14 />
15
16 <field
17 name="featured"

Page 205 January 2023


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

18 type="list"
19 onchange="this.form.submit();"
20 default=""
21 >
22 <option value="">JOPTION_SELECT_FEATURED</option>
23 <option value="0">JUNFEATURED</option>
24 <option value="1">JFEATURED</option>
25 </field>
26
27 <field
28 name="published"
29 type="status"
30 label="JOPTION_SELECT_PUBLISHED"
31 onchange="this.form.submit();"
32 >
33 <option value="">JOPTION_SELECT_PUBLISHED</option>
34 </field>
35
36 <field
37 name="category_id"
38 type="category"
39 label="JCATEGORY"
40 multiple="true"
41 extension="com_foos"
42 layout="joomla.form.field.list-fancy-select"
43 hint="JOPTION_SELECT_CATEGORY"
44 onchange="this.form.submit();"
45 published="0,1,2"
46 />
47
48 <field
49 name="access"
50 type="accesslevel"
51 label="JOPTION_SELECT_ACCESS"
52 onchange="this.form.submit();"
53 >
54 <option value="">JOPTION_SELECT_ACCESS</option>
55 </field>
56
57 <field
58 name="language"
59 type="contentlanguage"
60 label="JOPTION_SELECT_LANGUAGE"
61 onchange="this.form.submit();"
62 >
63 <option value="">JOPTION_SELECT_LANGUAGE</option>
64 <option value="*">JALL</option>
65 </field>
66
67 </fields>
68

Page 206 January 2023


21. Filter, Sort, Search Joomla 4 - Developing Extensions

69 <fields name="list">
70
71 <field
72 name="fullordering"
73 type="list"
74 label="JGLOBAL_SORT_BY"
75 default="a.name ASC"
76 onchange="this.form.submit();"
77 >
78 <option value="">JGLOBAL_SORT_BY</option>
79 <option value="a.ordering ASC">JGRID_HEADING_ORDERING_ASC</
option>
80 <option value="a.ordering DESC">JGRID_HEADING_ORDERING_DESC
</option>
81 <option value="a.published ASC">JSTATUS_ASC</option>
82 <option value="a.published DESC">JSTATUS_DESC</option>
83 <option value="a.name ASC">JGLOBAL_TITLE_ASC</option>
84 <option value="a.name DESC">JGLOBAL_TITLE_DESC</option>
85 <option value="category_title ASC">JCATEGORY_ASC</option>
86 <option value="category_title DESC">JCATEGORY_DESC</option>
87 <option value="access_level ASC">JGRID_HEADING_ACCESS_ASC</
option>
88 <option value="access_level DESC">JGRID_HEADING_ACCESS_DESC
</option>
89 <option value="association ASC" requires="associations">
JASSOCIATIONS_ASC</option>
90 <option value="association DESC" requires="associations">
JASSOCIATIONS_DESC</option>
91 <option value="language_title ASC" requires="multilanguage"
>JGRID_HEADING_LANGUAGE_ASC</option>
92 <option value="language_title DESC" requires="multilanguage
">JGRID_HEADING_LANGUAGE_DESC</option>
93 <option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
94 <option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
95 </field>
96
97 <field
98 name="limit"
99 type="limitbox"
100 label="JGLOBAL_LIST_LIMIT"
101 default="25"
102 onchange="this.form.submit();"
103 />
104 </fields>
105 </form>

featured is included here as a filter field for the sake of completeness, although we do not
support this in the extension yet.

January 2023 Page 207


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

21.1.1.2. administrator/components/com_foos/ sql/updates/mysql/16.0.0.sql

In case of an update of your component, the file 16.0.0.sql adds a column to store the sequence.

administrator/components/com_foos/sql/updates/mysql/16.0.0.sql

1 -- https://codeberg.org/astrid/j4examplecode/raw/branch/t16/src/
administrator/components/com_foos/sql/updates/mysql/16.0.0.sql
2
3 ALTER TABLE `#__foos_details ` ADD COLUMN `ordering ` int(11) NOT NULL
DEFAULT 0 AFTER `alias`;

21.1.2. Modified files

21.1.2.1. administrator/components/com_foos/ forms/foo.xml

The form used to create or modify an element is extended with a field for specifying the order.

administrator/components/com_foos/forms/foo.xml

1 label="JFIELD_ACCESS_LABEL"
2 size="1"
3 />
4 +
5 + <field
6 + name="ordering"
7 + type="ordering"
8 + label="JFIELD_ORDERING_LABEL"
9 + content_type="com_foos.foo"
10 + />
11 </fieldset>
12 </form>

21.1.2.2. administrator/components/com_foos/ sql/install.mysql.utf8.sql

In case of a new installation, the script in the file install.mysql.utf8.sql creates the database.
Here we add a column to store the order.

administrator/components/com_foos/sql/install.mysql.utf8.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `language ` char(7) NOT NULL
DEFAULT '*' AFTER `alias`;
3
4 ALTER TABLE `#__foos_details ` ADD KEY `idx_language ` ( `language`);
5 +

Page 208 January 2023


21. Filter, Sort, Search Joomla 4 - Developing Extensions

6 +ALTER TABLE `#__foos_details ` ADD COLUMN `ordering ` int(11) NOT NULL


DEFAULT 0 AFTER `alias`;

21.1.2.3. administrator/components/com_foos/ src/Model/FoosModel.php

There are a lot of changes in the model for the list. In the constructor we first save the filter fields to the
configuration.

In the getListQuery() method we adjust the database query to respect the filters and sorting. This
way the data is immediately in the form in which we display it.

administrator/components/com_foos/src/Model/FoosModel.php

1 use Joomla\CMS\MVC\Model\ListModel;
2 use Joomla\CMS\Language\Associations;
3 use Joomla\CMS\Factory;
4 +use Joomla\Utilities\ArrayHelper;
5
6
7 public function __construct($config = array())
8 {
9 +
10 + if (empty($config['filter_fields']))
11 + {
12 + $config['filter_fields'] = array(
13 + 'id', 'a.id',
14 + 'name', 'a.name',
15 + 'catid', 'a.catid', 'category_id', 'category_title',
16 + 'published', 'a.published',
17 + 'access', 'a.access', 'access_level',
18 + 'ordering', 'a.ordering',
19 + 'language', 'a.language', 'language_title',
20 + 'publish_up', 'a.publish_up',
21 + 'publish_down', 'a.publish_down',
22 + );
23 +
24 + $assoc = Associations::isEnabled();
25 +
26 + if ($assoc)
27 + {
28 + $config['filter_fields'][] = 'association';
29 + }
30 + }
31 +
32 parent::__construct($config);
33 }
34
35 array(
36 'a.id', 'a.name', 'a.alias', 'a.access',

January 2023 Page 209


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

37 'a.catid', 'a.published', 'a.publish_up', 'a.


publish_down',
38 - 'a.language'
39 + 'a.language', 'a.ordering', 'a.state'
40 )
41 )
42 );
43
44 $query->where($db->quoteName('a.language') . ' = ' . $db->
quote($language));
45 }
46
47 + // Filter by access level.
48 + if ($access = $this->getState('filter.access'))
49 + {
50 + $query->where($db->quoteName('a.access') . ' = ' . (int)
$access);
51 + }
52 +
53 + // Filter by published state
54 + $published = (string) $this->getState('filter.published');
55 +
56 + if (is_numeric($published))
57 + {
58 + $query->where($db->quoteName('a.published') . ' = ' . (int)
$published);
59 + }
60 + elseif ($published === '')
61 + {
62 + $query->where('(' . $db->quoteName('a.published') . ' = 0
OR ' . $db->quoteName('a.published') . ' = 1)');
63 + }
64 +
65 + // Filter by a single or group of categories.
66 + $categoryId = $this->getState('filter.category_id');
67 +
68 + if (is_numeric($categoryId))
69 + {
70 + $query->where($db->quoteName('a.catid') . ' = ' . (int)
$categoryId);
71 + }
72 + elseif (is_array($categoryId))
73 + {
74 + $query->where($db->quoteName('a.catid') . ' IN (' . implode
(',', ArrayHelper::toInteger($categoryId)) . ')');
75 + }
76 +
77 + // Filter by search in name.
78 + $search = $this->getState('filter.search');
79 +
80 + if (!empty($search))

Page 210 January 2023


21. Filter, Sort, Search Joomla 4 - Developing Extensions

81 + {
82 + if (stripos($search, 'id:') === 0)
83 + {
84 + $query->where('a.id = ' . (int) substr($search, 3));
85 + }
86 + else
87 + {
88 + $search = $db->quote('%' . str_replace(' ', '%', $db->
escape(trim($search), true) . '%'));
89 + $query->where(
90 + '(' . $db->quoteName('a.name') . ' LIKE ' . $search
. ')'
91 + );
92 + }
93 + }
94 +
95 + // Add the list ordering clause.
96 + $orderCol = $this->state->get('list.ordering', 'a.name');
97 + $orderDirn = $this->state->get('list.direction', 'asc');
98 +
99 + if ($orderCol == 'a.ordering' || $orderCol == 'category_title')
100 + {
101 + $orderCol = $db->quoteName('c.title') . ' ' . $orderDirn .
', ' . $db->quoteName('a.ordering');
102 + }
103 +
104 + $query->order($db->escape($orderCol . ' ' . $orderDirn));
105 +
106 return $query;
107 }

21.1.2.4. administrator/components/com_foos/ src/View/Foos/HtmlView.php

The view loads the filter form src/administrator/components/com_foos/forms/foo.xml,


which is displayed in the upper area. Besides we add here the check if the active user is allowed to
perform actions.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 \defined('_JEXEC') or die;
2
3 +use Joomla\CMS\Component\ComponentHelper;
4 +use Joomla\CMS\Helper\ContentHelper;
5 +use Joomla\CMS\Language\Associations;
6 use Joomla\CMS\Factory;
7 use Joomla\CMS\Language\Text;
8 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
9
10 use Joomla\CMS\Toolbar\ToolbarHelper;

January 2023 Page 211


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

11 use FooNamespace\Component\Foos\Administrator\Helper\FooHelper;
12 use Joomla\CMS\Factory;
13 +use Joomla\CMS\MVC\View\GenericDataException;
14
15
16 protected $items;
17
18 + protected $state;
19 +
20 + /**
21 + public $filterForm;
22 +
23 + public $activeFilters;
24 +
25
26 {
27 $this->items = $this->get('Items');
28
29 + $this->filterForm = $this->get('FilterForm');
30 + $this->activeFilters = $this->get('ActiveFilters');
31 + $this->state = $this->get('State');
32 +
33 + // Check for errors.
34 + if (count($errors = $this->get('Errors')))
35 + {
36 + throw new GenericDataException(implode("\n", $errors), 500)
;
37 + }
38 +
39 + // Preprocess the list of items to find ordering divisions.
40 + // TODO: Complete the ordering stuff with nested sets
41 + foreach ($this->items as &$item)
42 + {
43 + $item->order_up = true;
44 + $item->order_dn = true;
45 + }
46 +
47 // We don't need toolbar in the modal window.
48 if ($this->getLayout() !== 'modal')
49 {
50
51 {
52 // If the language is forced we can't allow to select
the language, so transform the language selector
filter into a hidden field.
53 $languageXml = new \SimpleXMLElement('<field name="
language" type="hidden" default="' . $forcedLanguage
. '" />');
54 + $this->filterForm->setField($languageXml, 'filter',
true);
55 +

Page 212 January 2023


21. Filter, Sort, Search Joomla 4 - Developing Extensions

56 + // Also, unset the active language filter so the search


tools is not open by default with this filter.
57 + unset($this->activeFilters['language']);
58 +
59 + // One last changes needed is to change the category
filter to just show categories with All language or with the forced
language.
60 + $this->filterForm->setFieldAttribute('category_id', '
language', '*,' . $forcedLanguage, 'filter');
61 }
62 }
63
64
65 protected function addToolbar()
66 {
67 - FooHelper::addSubmenu('foos');
68 - $this->sidebar = \JHtmlSidebar::render();
69 -
70 $canDo = ContentHelper::getActions('com_foos');

21.1.2.5. administrator/components/com_foos/ tmpl/foos/default.php

The code below shows all the essentials for using searchtools in the list view of the backend.
In the case of the header, I replaced <?php echo TEXT::_('JGRID_HEADING_ACCESS')?>
with <?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ACCESS', '
access_level', $listDirn, $listOrder); ?>. This way the header of the table is marked
with a small arrow when a sort is active in a column.

The code, which enables selection and deselection of column views via the code snippet

1 $wa = $this->document->getWebAssetManager();
2 $wa->useScript('table.columns');

was introduced to Joomla 4.2 via PR 365911 .

administrator/components/com_foos/tmpl/foos/default.php

1 use Joomla\CMS\Language\Multilanguage;
2 use Joomla\CMS\Language\Associations;
3 use Joomla\CMS\Layout\LayoutHelper;
4 +use Joomla\CMS\Session\Session;
5
6 +$wa = $this->document->getWebAssetManager();
7 +$wa->useScript('table.columns');
8
9 +$canChange = true;
1
github.com/joomla/joomla-cms/pull/36591

January 2023 Page 213


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

10 $assoc = Associations::isEnabled();
11 +$listOrder = $this->escape($this->state->get('list.ordering'));
12 +$listDirn = $this->escape($this->state->get('list.direction'));
13 +$saveOrder = $listOrder == 'a.ordering';
14
15 +if ($saveOrder && !empty($this->items)) {
16 + $saveOrderingUrl = 'index.php?option=com_foos&task=foos.
saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
17 +}
18 ?>
19 <form action="<?php echo Route::_('index.php?option=com_foos'); ?>"
method="post" name="adminForm" id="adminForm">
20 <div class="row">
21
22 echo 'col-md-12';
23 } ?>">
24 <div id="j-main-container" class="j-main-container">
25 + <?php echo LayoutHelper::render('joomla.searchtools.
default', ['view' => $this]); ?>
26 <?php if (empty($this->items)) : ?>
27 <div class="alert alert-warning">
28 <?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS
'); ?>
29 </div>
30 <?php else : ?>
31 <table class="table" id="fooList">
32 + <caption id="captionTable" class="sr-only">
33 + <?php echo Text::_('COM_FOOS_TABLE_CAPTION
'); ?>, <?php echo Text::_('JGLOBAL_SORTED_BY'); ?>
34 + </caption>
35 <thead>
36 <tr>
37 + <th scope="col" style="width:1%" class=
"text-center d-none d-md-table-cell">
38 + <?php echo HTMLHelper::_('
searchtools.sort', '', 'a.ordering', $listDirn, $listOrder, null, '
asc', 'JGRID_HEADING_ORDERING', 'icon-menu-2'); ?>
39 + </th>
40 <td style="width:1%" class="text-center
">
41 <?php echo HTMLHelper::_('grid.
checkall'); ?>
42 </td>
43 <th scope="col" style="width:1%" class=
"text-center d-none d-md-table-cell"
>
44 - <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_NAME'); ?>
45 - </th>
46 - <th scope="col" style="width:1%; min-
width:85px" class="text-center">

Page 214 January 2023


21. Filter, Sort, Search Joomla 4 - Developing Extensions

47 - <?php echo TEXT::_('JSTATUS'); ?>


48 + <?php echo HTMLHelper::_('
searchtools.sort', 'COM_FOOS_TABLE_TABLEHEAD_NAME', 'a.name',
$listDirn, $listOrder); ?>
49 </th>
50 <th scope="col" style="width:10%" class
="d-none d-md-table-cell">
51 - <?php echo TEXT::_('
JGRID_HEADING_ACCESS') ?>
52 + <?php echo HTMLHelper::_('
searchtools.sort', 'JGRID_HEADING_ACCESS', 'access_level', $listDirn
, $listOrder); ?>
53 </th>
54 <?php if ($assoc) : ?>
55 <th scope="col" style="width:10%">
56 - <?php echo Text::_('
COM_FOOS_HEADING_ASSOCIATION'); ?>
57 + <?php echo HTMLHelper::_('
searchtools.sort', 'COM_FOOS_HEADING_ASSOCIATION', 'association',
$listDirn, $listOrder); ?>
58 </th>
59 <?php endif; ?>
60 <?php if (Multilanguage::isEnabled()) :
?>
61 <th scope="col" style="width:10%"
class="d-none d-md-table-cell">
62 - <?php echo Text::_('
JGRID_HEADING_LANGUAGE'); ?>
63 + <?php echo HTMLHelper::_('
searchtools.sort', 'JGRID_HEADING_LANGUAGE', 'language_title',
$listDirn, $listOrder); ?>
64 </th>
65 <?php endif; ?>
66 <th scope="col" style="width:1%; min-
width:85px" class="text-center">
67 - <?php echo Text::_('JSTATUS'); ?>
68 + <?php echo HTMLHelper::_('
searchtools.sort', 'JSTATUS', 'a.published', $listDirn, $listOrder);
?>
69 </th>
70 <th scope="col">
71 - <?php echo Text::_('
COM_FOOS_TABLE_TABLEHEAD_ID'); ?>
72 + <?php echo HTMLHelper::_('
searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder
); ?>
73 </th>
74 </tr>
75 </thead>
76
77 foreach ($this->items as $i => $item) :

January 2023 Page 215


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

78 ?>
79 <tr class="row<?php echo $i % 2; ?>">
80 + <td class="order text-center d-none d-
md-table-cell">
81 + <?php
82 + $iconClass = '';
83 + if (!$canChange) {
84 + $iconClass = ' inactive';
85 + } else if (!$saveOrder) {
86 + $iconClass = ' inactive tip-top
hasTooltip" title="' . HTMLHelper::_('tooltipText', '
JORDERINGDISABLED');
87 + }
88 + ?>
89 + <span class="sortable-handler<?php
echo $iconClass; ?>">
90 + <span class="icon-menu" aria-
hidden="true"></span>
91 + </span>
92 + <?php if ($canChange && $saveOrder)
: ?>
93 + <input type="text" style="
display:none" name="order[]" size="5"
94 + value="<?php echo $item->
ordering; ?>" class="width-20 text-area-order">
95 + <?php endif; ?>
96 + </td>
97 <td class="text-center">
98 <?php echo HTMLHelper::_('grid.id',
$i, $item->id); ?>
99 </td>
100
101
102 <div class="small">
103 <?php echo Text::_('JCATEGORY')
. ': ' . $this->escape(
$item->category_title); ?>
104 - </div>
105 + </div>
106 </th>
107 <td class="text-center">
108 <?php
109 - echo HTMLHelper::_('jgrid.published
', $item->published, $i, 'foos.', true, 'cb', $item->publish_up,
$item->publish_down);
110 + echo HTMLHelper::_('jgrid.published
', $item->published, $i, 'foos.', $canChange, 'cb', $item->
publish_up, $item->publish_down);
111 ?>
112 </td>
113 <td class="small d-none d-md-table-cell

Page 216 January 2023


21. Filter, Sort, Search Joomla 4 - Developing Extensions

">

21.1.2.6. administrator/components/com_foos/ tmpl/foos/modal.php

Icons show us if a column is sorted and in which direction. To make the sorting clear to someone who
doesn’t see these markers, we add a <caption> element. This is not displayed, it is read out.

The class visually-hidden hides an element for all devices except screen readers.

administrator/components/com_foos/tmpl/foos/modal.php

1 <table class="table table-sm">


2 <thead>
3 <tr>
4 + <caption id="captionTable" class="sr-only">
5 + <?php echo Text::_('COM_FOOS_TABLE_CAPTION');
?>, <?php echo Text::_('JGLOBAL_SORTED_BY'); ?>
6 + </caption>
7 <th scope="col" style="width:10%" class="d-none
d-md-table-cell">
8 </th>
9 <th scope="col" style="width:1%">

21.2. Test your Joomla component

1- install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation.

2. the database has been changed, so it is necessary to update it. Open the System |
Information | Database section as described in part Publish and Unpublish. Select
your component and click on Update Structure.

3. open the view of your component in the administration area and filter, sort and search for items
in your component.

January 2023 Page 217


Joomla 4 - Developing Extensions 21. Filter, Sort, Search

Figure 21.1.: Joomla Filter Sort and Search - Searchtools

Page 218 January 2023


Joomla 4 - Developing Extensions 22. Toolbar Actions

22. Toolbar Actions

You don’t develop the extension as an end in itself. It helps with the completion of tasks. In order for the
people working with the component to always have an overview of the possible work steps, it makes
sense to have a toolbar. In this part of the tutorial we will extend the already existing toolbar with
the standard actions. Here we will access a variety of ready-made methods. Again, for the standard,
it is not necessary to invent the wheel yourself. Later on, for special tasks, it makes sense to use the
standard as an example.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t16...t17

22.1. Step by step

I’ll show you here how to integrate the standard functions into the toolbar. Each component has its
own functions. Just like the standard ones in Joomla, you add the special ones via buttons in the
toolbar. Look here at the standard functions.

22.1.1. New files

We only change files in this chapter, no new ones are added.

22.1.2. Modified files

22.1.2.1. administrator/components/ com_foos/src/View/Foo/HtmlView.php

The following code shows you which functions you use when creating or editing an element. The
ToolbarHelper1 class provides a lot of helpful functions. For example

• ToolbarHelper::title to position a title appropriately,


1
libraries/src/Toolbar/ToolbarHelper.php

Page 219 January 2023


Joomla 4 - Developing Extensions 22. Toolbar Actions

• ToolbarHelper::apply to add buttons with the default save function


• and ToolbarHelper::saveGroup to add a dropdown with the standard save commands.

See how to use ToolbarHelper::custom for custom tasks.

We add permission checking here. A button is displayed only if the user is authorized to use it. The
ContentHelper::getActions function collects the permissions implemented in the access.xml
file, which are allowed to the currently logged in user. If this is the case, then $canDo->get('...')
equals true. A concrete example: $canDo->get('core.create') is true if the user is allowed to
create content.

administrator/components/com_foos/src/View/Foo/HtmlView.php

1 {
2 Factory::getApplication()->input->set('hidemainmenu', true);
3
4 + $user = Factory::getUser();
5 + $userId = $user->id;
6 +
7 $isNew = ($this->item->id == 0);
8
9 ToolbarHelper::title($isNew ? Text::_('COM_FOOS_MANAGER_FOO_NEW
') : Text::_('COM_FOOS_MANAGER_FOO_EDIT'), 'address foo');
10
11 - ToolbarHelper::apply('foo.apply');
12 - ToolbarHelper::cancel('foo.cancel', 'JTOOLBAR_CLOSE');
13 + // Since we don't track these assets at the item level, use the
category id.
14 + $canDo = ContentHelper::getActions('com_foos', 'category',
$this->item->catid);
15 +
16 + // Build the actions for new and existing records.
17 + if ($isNew)
18 + {
19 + // For new records, check the create permission.
20 + if ($isNew && (count($user->getAuthorisedCategories('
com_foos', 'core.create')) > 0))
21 + {
22 + ToolbarHelper::apply('foo.apply');
23 + ToolbarHelper::saveGroup(
24 + [
25 + ['save', 'foo.save'],
26 + ['save2new', 'foo.save2new']
27 + ],
28 + 'btn-success'
29 + );
30 + }
31 +
32 + ToolbarHelper::cancel('foo.cancel');

Page 220 January 2023


22. Toolbar Actions Joomla 4 - Developing Extensions

33 + }
34 + else
35 + {
36 + // Since it's an existing record, check the edit permission
, or fall back to edit own if the owner.
37 + $itemEditable = $canDo->get('core.edit') || ($canDo->get('
core.edit.own') && $this->item->created_by == $userId);
38 + $toolbarButtons = [];
39 +
40 + // Can't save the record if it's not editable
41 + if ($itemEditable)
42 + {
43 + ToolbarHelper::apply('foo.apply');
44 + $toolbarButtons[] = ['save', 'foo.save'];
45 +
46 + // We can save this record, but check the create
permission to see if we can return to make a new one.
47 + if ($canDo->get('core.create'))
48 + {
49 + $toolbarButtons[] = ['save2new', 'foo.save2new'];
50 + }
51 + }
52 +
53 + // If checked out, we can still save
54 + if ($canDo->get('core.create'))
55 + {
56 + $toolbarButtons[] = ['save2copy', 'foo.save2copy'];
57 + }
58 +
59 + ToolbarHelper::saveGroup(
60 + $toolbarButtons,
61 + 'btn-success'
62 + );
63 +
64 + if (Associations::isEnabled() && ComponentHelper::isEnabled
('com_associations'))
65 + {
66 + ToolbarHelper::custom('foo.editAssociations', 'contract
', 'contract', 'JTOOLBAR_ASSOCIATIONS', false, false);
67 + }
68 +
69 + ToolbarHelper::cancel('foo.cancel', 'JTOOLBAR_CLOSE');
70 + }
71 }
72 }

January 2023 Page 221


Joomla 4 - Developing Extensions 22. Toolbar Actions

22.1.2.2. administrator/components/com_foos/ src/View/Foos/HtmlView.php

Here you can see an example of the List View toolbar - the view that gives you an overview of your
items. Permission checking has also been added here.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 protected function addToolbar()


2 {
3 - $canDo = ContentHelper::getActions('com_foos');
4 + $this->sidebar = \JHtmlSidebar::render();
5 +
6 + $canDo = ContentHelper::getActions('com_foos', 'category',
$this->state->get('filter.category_id'));
7 + $user = Factory::getUser();
8
9 // Get the toolbar object instance
10 $toolbar = Toolbar::getInstance('toolbar');
11
12 ToolbarHelper::title(Text::_('COM_FOOS_MANAGER_FOOS'), 'address
foo');
13
14 - if ($canDo->get('core.create'))
15 + if ($canDo->get('core.create') || count($user->
getAuthorisedCategories('com_foos', 'core.create')) > 0)
16 {
17 $toolbar->addNew('foo.add');
18 }
19
20 - if ($canDo->get('core.options'))
21 + if ($canDo->get('core.edit.state'))
22 + {
23 + $dropdown = $toolbar->dropdownButton('status-group')
24 + ->text('JTOOLBAR_CHANGE_STATUS')
25 + ->toggleSplit(false)
26 + ->icon('fa fa-ellipsis-h')
27 + ->buttonClass('btn btn-action')
28 + ->listCheck(true);
29 + $childBar = $dropdown->getChildToolbar();
30 + $childBar->publish('foos.publish')->listCheck(true);
31 + $childBar->unpublish('foos.unpublish')->listCheck(true);
32 + $childBar->archive('foos.archive')->listCheck(true);
33 +
34 + if ($user->authorise('core.admin'))
35 + {
36 + $childBar->checkin('foos.checkin')->listCheck(true);
37 + }
38 +
39 + if ($this->state->get('filter.published') != -2)
40 + {
41 + $childBar->trash('foos.trash')->listCheck(true);

Page 222 January 2023


22. Toolbar Actions Joomla 4 - Developing Extensions

42 + }
43 + }
44 +
45 + if ($this->state->get('filter.published') == -2 && $canDo->get(
'core.delete'))
46 + {
47 + $toolbar->delete('foos.delete')
48 + ->text('JTOOLBAR_EMPTY_TRASH')
49 + ->message('JGLOBAL_CONFIRM_DELETE')
50 + ->listCheck(true);
51 + }
52 +
53 + if ($user->authorise('core.admin', 'com_foos') || $user->
authorise('core.options', 'com_foos'))
54 {
55 $toolbar->preferences('com_foos');
56 }

22.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder to the administrator folder of your Joomla 4 installa-
tion.

A new installation is not necessary. Continue using the ones from the previous part.

2. Open the view of your component in the administration area. In the toolbar you will see a
dropdown list to trigger different actions.

January 2023 Page 223


Joomla 4 - Developing Extensions 22. Toolbar Actions

Figure 22.1.: Joomla Actions in the Toolbar

3. Open the detail view for an item. Here you will also have a toolbar.

Figure 22.2.: Joomla actions in the toolbar

Page 224 January 2023


Joomla 4 - Developing Extensions 23. Parameter

23. Parameter

Are there settings that apply to all items in your component that a user can customize to their needs?
For example, do you display digital maps and do you want to allow the user to determine the display
of the license for all his maps? In Joomla there are parameters for this purpose.

Parameters exist for

• one item in particular,


• for the whole component (all items of the component) and
• for a menu item. If a parameter is set for all of the three possibilities, the following hierarchy
applies in Joomla by default:
• The setting on the menu item always has priority.
• After that, the parameter that applies specifically to the item takes precedence.
• The parameter that is set for the component has the lowest priority.

Page 225 January 2023


Joomla 4 - Developing Extensions 23. Parameter

Figure 23.1.: Parameter handling in Joomla

For the menu item we had already set a parameter. For the component, you can find it in the options
of the configuration. We will look at the item in particular in this section.

For impatient people: Look at the changed program code in the Diff Viewa and take over these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t17...t18

23.1. Step by step

The code with which the assignment of a parameter is calculated, was for a long time differently
integrated in the Joomla core components. Shortly before the release of Joomla 4 there were efforts
to simplify and unify this. Example pull requests are PR 348941 and PR 325382 , from which one can be
1
github.com/joomla/joomla-cms/pull/34894
2
github.com/joomla/joomla-cms/pull/32538

Page 226 January 2023


23. Parameter Joomla 4 - Developing Extensions

inspired for own implementations.

23.1.1. New files

23.1.1.1. administrator/components/com_foos/ sql/updates/mysql/18.0.0.sql

In order to create the params column in the database where the parameters are stored when the com-
ponent is updated, we need the SQL file administrator/components/com_foos/sql/updates/
mysql/18.0.0.sql.

administrator/components/com_foos/sql/updates/mysql/18.0.0.sql

1 /* https://codeberg.org/astrid/j4examplecode/raw/branch
/39598941015020537d51ccb6ca4098f019d76b04/src/administrator/
components/com_foos/sql/updates/mysql/18.0.0.sql */
2
3 ALTER TABLE `#__foos_details ` ADD COLUMN `params ` text NOT NULL AFTER
`alias`;

23.1.2. Modified files

23.1.2.1. administrator/components/com_foos/config.xml

In the configuration, the parameter is saved to set a default value. We add a field show_name to
the configuration. Then we create the possibility to override it for a single element administrator
/components/com_foos/forms/foo.xml or a menu item components/com_foos/tmpl/foo/
default.xml.

administrator/components/com_foos/config.xml

1 <option value="0">JNO</option>
2 <option value="1">JYES</option>
3 </field>
4 +
5 + <field
6 + name="show_name"
7 + type="radio"
8 + label="COM_FOOS_FIELD_PARAMS_NAME_LABEL"
9 + default="1"
10 + layout="joomla.form.field.radio.switcher"
11 + >
12 + <option value="0">JHIDE</option>
13 + <option value="1">JSHOW</option>
14 + </field>
15 </fieldset>

January 2023 Page 227


Joomla 4 - Developing Extensions 23. Parameter

16 <fieldset
17 name="permissions"

23.1.2.2. administrator/components/com_foos/ forms/foo.xml

In the form we use to edit an element, we add the params field. So show_name is also configurable for
a single element.

administrator/components/com_foos/forms/foo.xml

1 content_type="com_foos.foo"
2 />
3 </fieldset>
4 + <fields name="params" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
5 + <fieldset name="display" label="
JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
6 + <field
7 + name="show_name"
8 + type="list"
9 + label="COM_FOOS_FIELD_PARAMS_NAME_LABEL"
10 + useglobal="true"
11 + >
12 + <option value="0">JHIDE</option>
13 + <option value="1">JSHOW</option>
14 + </field>
15 + </fieldset>
16 + </fields>
17 </form>

In Joomla there is the possibility to set the parmeter to the value global. The benefit is that when
you configure it, it shows what is set globally. Use useglobal="true" like /administrator/com-
ponents/com_contact/forms/contact.xml.

23.1.2.3. administrator/components/com_foos/ sql/install.mysql.utf8.sql

To create the column where the parameters will be stored during a new installation, we add a line to
the SQL file administrator/components/com_foos/sql/install.mysql.utf8.sql.

administrator/components/com_foos/sql/install.mysql.utf8.sql

1 ALTER TABLE `#__foos_details ` ADD KEY `idx_language ` ( `language`);


2
3 ALTER TABLE `#__foos_details ` ADD COLUMN `ordering ` int(11) NOT NULL
DEFAULT 0 AFTER `alias`;
4 +

Page 228 January 2023


23. Parameter Joomla 4 - Developing Extensions

5 +ALTER TABLE `#__foos_details ` ADD COLUMN `params ` text NOT NULL AFTER
`alias`;

23.1.2.4. administrator/components/com_foos/ src/Table/FooTable.php

In the class that handels the table, we make sure that the parameters are stored in the correct form. We
use the registry design pattern3 . This uses the ability to override properties in PHP. We add properties
using

1 $registry = new registry;


2 $registry->foo = 'foo';

to the registry. To get a value, we use

1 $foo = $registry->foo;

administrator/components/com_foos/src/Table/FooTable.php

1 use Joomla\CMS\Application\ApplicationHelper;
2 use Joomla\CMS\Table\Table;
3 use Joomla\Database\DatabaseDriver;
4 +use Joomla\CMS\Language\Text;
5 +use Joomla\Registry\Registry;
6
7 public function check()
8 public function store($updateNulls = true)
9 {
10 + // Transform the params field
11 + if (is_array($this->params)) {
12 + $registry = new Registry($this->params);
13 + $this->params = (string) $registry;
14 + }
15 +
16 return parent::store($updateNulls);
17 }
18 }

23.1.2.5. components/com_foos/src/View/Foo/HtmlView.php

The view combines the data on the parameters so that the display fits. In Joomla it is usual that the
setting at the menu item overwrites everything. If there is no parameter here, the value that was saved
for the element is used. Last but not least the value of the configuration is used. You query the active
menu item via $active = $app->getMenu()->getActive();.
3
martinfowler.com/eaacatalog/registry.html

January 2023 Page 229


Joomla 4 - Developing Extensions 23. Parameter

Sometimes it is more intuitive to use the display at the element as priority. This is what I implemented
here. $state->get('params') returns the value stored at the menu item. $item->params is the
parameter that was stored at the element. The code below shows how you combine the two so that
the value at the item is applied.

components/com_foos/src/View/Foo/HtmlView.php

1
2 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
3 use Joomla\CMS\Factory;
4 +use Joomla\Registry\Registry;
5
6 class HtmlView extends BaseHtmlView
7 {
8 + protected $params = null;
9 +
10 + protected $state;
11 +
12 public function display($tpl = null)
13 {
14 $item = $this->item = $this->get('Item');
15
16 + $state = $this->state = $this->get('State');
17 + $params = $this->params = $state->get('params');
18 + $itemparams = new Registry(json_decode($item->params));
19 +
20 + $temp = clone $params;
21 +
22 + /**
23 + * $item->params are the foo params, $temp are the menu item
params
24 + * Merge so that the menu item params take priority
25 + *
26 + * $itemparams->merge($temp);
27 + */
28 +
29 + // Merge so that foo params take priority
30 + $temp->merge($itemparams);
31 + $item->params = $temp;
32 +
33 Factory::getApplication()->triggerEvent('onContentPrepare', ['
com_foos.foo', &$item]);
34
35 // Store the events for later

Page 230 January 2023


23. Parameter Joomla 4 - Developing Extensions

23.1.2.6. components/com_foos/ tmpl/foo/default.php

At the end we use the parameter when handling the display in the template components/com_foos/
tmpl/foo/default.php. If there is the parameter and it is set that the name should be displayed
if ($this->item->params->get('show_name')), then the name will be displayed. The label
$this->params->get('show_foo_name_label') will also be displayed only in that case:

components/com_foos/tmpl/foo/default.php

1 use Joomla\CMS\Language\Text;
2
3 -if ($this->get('State')->get('params')->get('show_foo_name_label')) {
4 - echo Text::_('COM_FOOS_NAME');
5 -}
6 +if ($this->item->params->get('show_name')) {
7 + if ($this->params->get('show_foo_name_label')) {
8 + echo Text::_('COM_FOOS_NAME');
9 + }
10
11 -echo $this->item->name;
12 + echo $this->item->name;
13 +}
14
15 echo $this->item->event->afterDisplayTitle;
16 echo $this->item->event->beforeDisplayContent;

23.1.2.7. components/com_foos/ tmpl/foo/default.xml

components/com_foos/tmpl/foo/default.xml

To make it possible to store the parameter at the menu item, we add a field in the XML file. It is
important that it is placed under fields and is called params - at least for using the Joomla standard
functions.!

1 />
2 </fieldset>
3 </fields>
4 + <!-- Add fields to the parameters object for the layout. -->
5 + <fields name="params">
6 + <fieldset name="basic" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS"
>
7 + <field
8 + name="show_name"
9 + type="radio"
10 + label="COM_FOOS_FIELD_PARAMS_NAME_LABEL"
11 + layout="joomla.form.field.radio.switcher"
12 + default="1"

January 2023 Page 231


Joomla 4 - Developing Extensions 23. Parameter

13 + class=""
14 + >
15 + <option value="0">JHIDE</option>
16 + <option value="1">JSHOW</option>
17 + </field>
18 + </fieldset>
19 + </fields>
20 </metadata>

The html form element input with the type radio has a typical look in Joomla. It is called
switcher and you create the look using the layout joomla.form.field.radio.switcher.

1 <field
2 name="show_name"
3 type="radio"
4 label="COM_FOOS_FIELD_PARAMS_NAME_LABEL"
5 layout="joomla.form.field.radio.switcher"
6 default="1"
7 class=""
8 >
9 <option value="0">JHIDE</option>
10 <option value="1">JSHOW</option>
11 </field>

23.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator folder
into the administrator folder of your Joomla 4 installation. Copy the files in the components
folder into the components folder of your Joomla 4 installation.

Page 232 January 2023


23. Parameter Joomla 4 - Developing Extensions

2. the database has been changed, so it is necessary to update it. Open the System |
Information | Database section as described in part Publish and Unpublish. Select
your component and click on Update Structure.

3. Open the view of your component in the administration area. When editing an item, there is now
the Display tab and the Show Name parameter.

Figure 23.2.: Joomla parameter of an element.

4. Open the global options of your component in the administration area. Here is now also the
parameter Show Name.

January 2023 Page 233


Joomla 4 - Developing Extensions 23. Parameter

Figure 23.3.: Joomla parameters/options of a component

5. Open the menu manager to create a menu item. To do this, click on Menu in the left sidebar
and then on All Menu Items. Then click on the New button and fill in all necessary fields. You
can find the appropriate Menu Item Type by clicking the Select button. Now there is the tab
Display and the parameter Show Name.

Figure 23.4.: Joomla parameters of a the menu item.

6. Set the Show Name parameter in different combinations and make sure that the display in the

Page 234 January 2023


23. Parameter Joomla 4 - Developing Extensions

frontend is correct.

January 2023 Page 235


Joomla 4 - Developing Extensions 24. Pagination

24. Pagination

There is a lot of content soon. Displaying all elements on one page is not useful. It has a negative effect
on the layout and performance. Therefore, we divide the elements into sub-pages and add pagination
or page numbering. With this, navigation through the pages is possible. Links are inserted for this
purpose. Usually, these are located at the bottom of the page.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t18...t19

24.1. Step by step

24.1.1. New files

No new files are added.

24.1.2. Modified files

24.1.2.1. administrator/components/com_foos/ src/View/Foos/HtmlView.php

We do not have any special requests. To display the default pagination, more or less two lines are
enough. In the view you call $this->pagination = $this->get('Pagination'); to set the
variable $this->pagination.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 protected $items;
2
3 + protected $pagination;
4 +
5
6 public function display($tpl = null): void
7 {
8 $this->items = $this->get('Items');

Page 237 January 2023


Joomla 4 - Developing Extensions 24. Pagination

9 -
10 + $this->pagination = $this->get('Pagination');
11 $this->filterForm = $this->get('FilterForm');
12 $this->activeFilters = $this->get('ActiveFilters');
13 $this->state = $this->get('State');

24.1.2.2. administrator/components/com_foos/ tmpl/foos/default.php

In the template we use the getListFooter method of the variable $this->pagination. That was
all!

administrator/components/com_foos/tmpl/foos/default.php

1 </tbody>
2 </table>
3
4 + <?php echo $this->pagination->getListFooter(); ?>
5 +
6 <?php endif; ?>
7 <input type="hidden" name="task" value="">
8 <input type="hidden" name="boxchecked" value="0">

In the global configuration you can set the number of elements that will be displayed by default.
Normally this is set to 20 elements.

Figure 24.1.: Joomla Pagination in global configuration

Do you feel that something is missing in this chapter? Are you wondering where all the calculations

Page 238 January 2023


24. Pagination Joomla 4 - Developing Extensions

are that create the page links? Then take a look at the two files: libraries/src/Pagination/
Pagination.php and libraries/src/MVC/Model/ListModel.php. Joomla does all the work
for you if you use the default, so if specifically in our case the model extends the file libraries/src/
MVC/Model/ListModel.php.

24.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder to the administrator folder of your Joomla 4 installa-
tion.

A new installation is not necessary. Continue using the ones from the previous part.

2. open the view of your component in the administration area and create so many items that
they are no longer displayed on one page. In the lower part you will see a navigation to browse
through the contents.

Figure 24.2.: Joomla Pagination

January 2023 Page 239


Joomla 4 - Developing Extensions 25. Layouts

25. Layouts

Sometimes it is necessary to design the display differently in the frontend. This is basically the respon-
sibility of the template. A component is responsible for the output of content, no more and no less.
The template ensures a uniform appearance. Nevertheless, there are use cases for different layouts.
How you incorporate them for a view is the topic of the following article.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t19...t20

25.1. Step by step

We already use layouts. The empty state Layout (administrator/components/com_foos/tmpl


/foos/emptystate.php)1 is an example. This part of the text is about looking at all the steps to
integrate a layout as a whole.

25.1.1. New files

25.1.1.1. components/com_foos/ tmpl/foo/withhead.php

components/com_foos/tmpl/foo/withhead.php

1 <?php
2
3 \defined('_JEXEC') or die;
4
5 use Joomla\CMS\Language\Text;
6
7 echo "<hr>Hier kannst du einen Headertext anzeigen.<hr>";
8
9 if ($this->item->params->get('show_name')) {
10 if ($this->params->get('show_foo_name_label')) {
11 echo Text::_('COM_FOOS_NAME') . $this->item->name;
1
github.com/astridx/boilerplate/blob/t6b/src/administrator/components/com_foos/tmpl/foos/emptystate.php

Page 241 January 2023


Joomla 4 - Developing Extensions 25. Layouts

12 } else {
13 echo $this->item->name;
14 }
15 }
16
17 echo $this->item->event->afterDisplayTitle;
18 echo $this->item->event->beforeDisplayContent;
19 echo $this->item->event->afterDisplayContent;

25.1.1.2. components/com_foos/ tmpl/foo/withhead.xml

components/com_foos/tmpl/foo/withhead.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <metadata>
4 <layout title="COM_FOOS_FOO_VIEW_WITHHEAD_TITLE">
5 <message>
6 <![CDATA[COM_FOOS_FOO_VIEW_WITHHEAD_DESC]]>
7 </message>
8 </layout>
9 <!-- Add fields to the request variables for the layout. -->
10 <fields name="request">
11 <fieldset name="request"
12 addfieldprefix="FooNamespace\Component\Foos\Administrator\
Field"
13 >
14 <field
15 name="id"
16 type="modal_foo"
17 label="COM_FOOS_SELECT_FOO_LABEL"
18 required="true"
19 select="true"
20 new="true"
21 edit="true"
22 clear="true"
23 />
24 </fieldset>
25 </fields>
26 </metadata>

25.1.1.3. components/com_foos/ tmpl/foo/withheadandfoot.php

components/com_foos/tmpl/foo/withheadandfoot.php

1
2 <?php

Page 242 January 2023


25. Layouts Joomla 4 - Developing Extensions

3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Language\Text;
7
8 echo "<hr>Hier kannst du einen Headertext anzeigen.<hr>";
9
10 if ($this->item->params->get('show_name')) {
11 if ($this->Params->get('show_foo_name_label')) {
12 echo Text::_('COM_FOOS_NAME') . $this->item->name;
13 } else {
14 echo $this->item->name;
15 }
16 }
17
18 echo "<hr>Hier kannst du eine Fußzeile anzeigen.<hr>";
19
20 echo $this->item->event->afterDisplayTitle;
21 echo $this->item->event->beforeDisplayContent;
22 echo $this->item->event->afterDisplayContent;

25.1.2. Modified files

25.1.2.1. administrator/components/com_foos/ forms/foo.xml

In the form of the element we add a field to select the layout.

administrator/components/com_foos/forms/foo.xml

1 <option value="0">JHIDE</option>
2 <option value="1">JSHOW</option>
3 </field>
4 +
5 + <field
6 + name="foos_layout"
7 + type="componentlayout"
8 + label="JFIELD_ALT_LAYOUT_LABEL"
9 + class="custom-select"
10 + extension="com_foos"
11 + view="foo"
12 + useglobal="true"
13 + />
14 </fieldset>
15 </fields>
16 </form>

January 2023 Page 243


Joomla 4 - Developing Extensions 25. Layouts

25.1.2.2. components/com_foos/src/Model/FooModel.php

This is what happens during development. Basically we would not have to change the file components
/com_foos/src/Model/FooModel.php. In this chapter I noticed that a use entry is missing. There-
fore a change is made after all.

components/com_foos/src/Model/FooModel.php

1 use Joomla\CMS\Factory;
2 use Joomla\CMS\MVC\Model\BaseDatabaseModel;
3 +use Joomla\CMS\Language\Text;

25.1.2.3. components/com_foos/src/View/Foo/HtmlView.php

In the case of a menu item, I think it is important that it - or the content and design - is always displayed
consistently. That is why we query the active menu item. If, for example, elements are displayed via
a category view, then a uniform layout is possible with the help of this information. If the content is
displayed as a single element, a different layout can be used.

components/com_foos/src/View/Foo/HtmlView.php

1 $temp->merge($itemparams);
2 $item->params = $temp;
3
4 + $active = Factory::getApplication()->getMenu()->getActive();
5 +
6 + // Override the layout only if this is not the active menu item
7 + // If it is the active menu item, then the view and item id
will match
8 + if ((!$active) || ((strpos($active->link, 'view=foo') === false
) || (strpos($active->link, '&id=' . (string) $this->item->id) ===
false)))
9 + {
10 + if (($layout = $item->params->get('foos_layout')))
11 + {
12 + $this->setLayout($layout);
13 + }
14 + }
15 + elseif (isset($active->query['layout']))
16 + {
17 + // We need to set the layout in case this is an alternative
menu item (with an alternative layout)
18 + $this->setLayout($active->query['layout']);
19 + }
20 +
21 Factory::getApplication()->triggerEvent('onContentPrepare',
array ('com_foos.foo', &$item));

Page 244 January 2023


25. Layouts Joomla 4 - Developing Extensions

22
23 // Store the events for later

25.2. Test your Joomla component

1. perform a new installation. To do this, uninstall your previous installation and copy all files again.
In a freshly installed system the explanation of the layouts is more uncomplicated.

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.
Copy the files in the components folder into the components folder of your Joomla 4 installation.

Install your component as described in part one, after copying all the files.

2. set a special layout for an item. I set withhead at the item with ID 2.

Figure 25.1.: Joomla Layouts

3. look at the output in the frontend. Enter the URL JOOMLA4/?option=com_foos&id=2&view=


foo in the address bar of your browser. You will see the selected layout. Make sure that typing
JOOMLA4/?option=com_foos&id=1&view=foo calls the default layout - if no special one is
set.

4. this layout is also controllable by menu item.

January 2023 Page 245


Joomla 4 - Developing Extensions 25. Layouts

Figure 25.2.: Joomla Layouts

An item without XML file is not selectable in the administration area. Nevertheless such layouts
are useful! In the program code it is possible to assign it at any place: $this->setLayout('
withheadandfoot');

Since the view is expected to be uniform when controlling via a menu item, the layout set in the
menu item is preferred.

Page 246 January 2023


Joomla 4 - Developing Extensions 26. Checkin und Checkout

26. Checkin und Checkout

The checkout function avoids unexpected results that occur when two users edit the same item at the
same time. Checking out locks an item when a user opens it for editing. It is then unlocked again when
saved or closed. This is a useful function that we are integrating into our sample extension in this part
of the article series.
Sometimes it happens that an item is marked as checked out, although no one has opened it
for editing at the same time. This usually happens when a previous opening was not finished
correctly. For example, the web browser was closed even though the item was open for editing,
or the back button in the browser menu was clicked instead of closing the item properly.

For impatient people: Look at the changed programme code in the Diff Viewa and copy these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t20...t21

26.1. Step by step

26.1.1. New files

26.1.1.1. administrator/components/com_foos/ sql/updates/mysql/21.0.0.sql

Like all properties of a Foo element, the checkout state is stored in the database. We create two columns.
Below you can see the script that is called during a Joomla update.

administrator/components/com_foos/sql/install.mysql.utf8.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `checked_out ` int(10) unsigned
NOT NULL DEFAULT 0 AFTER `alias`;
3
4 ALTER TABLE `#__foos_details ` ADD COLUMN `checked_out_time ` datetime
AFTER `alias`;
5
6 ALTER TABLE `#__foos_details ` ADD KEY `idx_checkout ` ( `checked_out`);

Page 247 January 2023


Joomla 4 - Developing Extensions 26. Checkin und Checkout

26.1.2. Modified files

26.1.2.1. administrator/components/com_foos/ forms/foo.xml

In the form we add the fields for saving the state. We hide them with the attribute hidden, as they are
not changed by the user here. Joomla sets the values automatically in the background.
administrator/components/com_foos/forms/foo.xml

1 size="1"
2 />
3
4 + <field
5 + name="checked_out"
6 + type="hidden"
7 + filter="unset"
8 + />
9 +
10 + <field
11 + name="checked_out_time"
12 + type="hidden"
13 + filter="unset"
14 + />
15 +
16 <field
17 name="ordering"
18 type="ordering"

26.1.2.2. administrator/components/com_foos/ sql/install.mysql.utf8.sql

We add the database changes that we entered above for the update in the separate SQL file to the SQL
script that is called during a new installation.
administrator/components/com_foos/sql/install.mysql.utf8.sql

1 ALTER TABLE `#__foos_details ` ADD COLUMN `ordering ` int(11) NOT NULL


DEFAULT 0 AFTER `alias`;
2
3 ALTER TABLE `#__foos_details ` ADD COLUMN `params ` text NOT NULL AFTER
`alias`;
4 +
5 +ALTER TABLE `#__foos_details ` ADD COLUMN `checked_out ` int(10)
unsigned NOT NULL DEFAULT 0 AFTER `alias`;
6 +
7 +ALTER TABLE `#__foos_details ` ADD COLUMN `checked_out_time ` datetime
AFTER `alias`;
8 +
9 +ALTER TABLE `#__foos_details ` ADD KEY `idx_checkout ` ( `checked_out`);

Page 248 January 2023


26. Checkin und Checkout Joomla 4 - Developing Extensions

26.1.2.3. administrator/components/com_foos/ src/Model/FoosModel.php

In the model, we adjust everything so that the two new columns are loaded correctly.

Note the change array(...) to explode(', ',$this->getState(...)...). We now use


the PHP function explode together with getState to create the array for the database query.
This is safer and more error-tolerant.

administrator/components/com_foos/src/Model/FoosModel.php

1 // Select the required fields from the table.


2 $query->select(
3 $db->quoteName(
4 - array(
5 - 'a.id', 'a.name', 'a.alias', 'a.access',
6 - 'a.catid', 'a.published', 'a.publish_up', 'a.
publish_down',
7 - 'a.language', 'a.ordering', 'a.state'
8 + explode(
9 + ', ',
10 + $this->getState(
11 + 'list.select',
12 + 'a.id, a.name, a.catid' .
13 + ', a.access' .
14 + ', a.checked_out' .
15 + ', a.checked_out_time' .
16 + ', a.language' .
17 + ', a.ordering' .
18 + ', a.state' .
19 + ', a.published' .
20 + ', a.publish_up, a.publish_down'
21 + )
22 )
23 )
24 );
25
26 $query->select('(' . $subQuery . ') AS ' . $db->quoteName('
association'));
27 }
28
29 + // Join over the users for the checked out user.
30 + $query->select($db->quoteName('uc.name', 'editor'))
31 + ->join(
32 + 'LEFT',
33 + $db->quoteName('#__users', 'uc') . ' ON ' . $db->
quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')
34 + );
35 +
36 // Filter on the language.
37 if ($language = $this->getState('filter.language'))

January 2023 Page 249


Joomla 4 - Developing Extensions 26. Checkin und Checkout

38 {

26.1.2.4. administrator/components/com_foos/ tmpl/foos/default.php

In the list view we do not insert a separate column. A symbol is displayed by the name if
the element is locked. To display this, I choose the function that Joomla uses in its own
extensions: echo HTMLHelper::_('jgrid.checkedout', $i, $item->editor, $item
->checked_out_time, 'foos.', true). At the same time, this takes over the check whether the
contribution is released or not.

administrator/components/com_foos/tmpl/foos/default.php

1 <?php echo HTMLHelper::_('grid.id',


$i, $item->id); ?>
2 </td>
3 <th scope="row" class="has-context">
4 + <?php if ($item->checked_out) : ?>
5 + <?php echo HTMLHelper::_('jgrid
.checkedout', $i, $item->editor, $item->checked_out_time, 'foos.',
true); ?>
6 + <?php endif; ?>
7 <div>
8 <?php echo $this->escape($item
->name); ?>
9 </div>

I have kept it uncomplicated here. I do not check whether someone is authorised to release a
checked-out post again. The components in Joomla make this more restrictive. In com_contact,
for example, the relevant line looks like this: <?php echo HTMLHelper::*('jgrid.
checkedout', $i, $item->editor, $item->checked_out_time, 'contacts.',
$canCheckin); ?>. If you also don’t allow everyone to unlock and want to implement this, look
at the implementation in com_contact - there you find the code that computes$canCheckin.

26.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.

2. the database has been changed, so it is necessary to update it. Open the System |
Information | Database section as described in part Publish and Unpublish. Select

Page 250 January 2023


26. Checkin und Checkout Joomla 4 - Developing Extensions

your component and click on Update Structure.

3. Open an item in the edit view.

4. Switch to another browser window and try to edit the item again.

5. Make sure you see an icon that tells you that the item is locked and that an authorised user can
unlock it.

Figure 26.1.: Joomla checkin/checkout

January 2023 Page 251


Joomla 4 - Developing Extensions 27. Batch

27. Batch

Joomla offers a number of functions that enable administrators to process several items at once. We
now add this batch processing to the component. Based on this, it is possible for you to add your own
“batch processing” functions.

For impatient people: Look at the changed programme code in the Diff Viewa and copy these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t21...t22

27.1. Step by step

27.1.1. New files

27.1.1.1. administrator/components/com_foos/ tmpl/foos/default_batch_body.php

The following file creates the middle part of the form that is displayed to trigger batch processing.

administrator/components/com_foos/tmpl/foos/default_batch_body.php

1
2 <?php
3 \defined('_JEXEC') or die;
4
5 use Joomla\CMS\Layout\LayoutHelper;
6
7 $published = $this->state->get('filter.published');
8 $noUser = true;
9 ?>
10
11 <div class="p-3">
12 <div class="row">
13 <div class="form-group col-md-6">
14 <div class="controls">
15 <?php echo LayoutHelper::render('joomla.html.batch.
language', []); ?>
16 </div>
17 </div>

Page 253 January 2023


Joomla 4 - Developing Extensions 27. Batch

18 <div class="form-group col-md-6">


19 <div class="controls">
20 <?php echo LayoutHelper::render('joomla.html.batch.
access', []); ?>
21 </div>
22 </div>
23 </div>
24 <div class="row">
25 <?php if ($published >= 0) : ?>
26 <div class="form-group col-md-6">
27 <div class="controls">
28 <?php echo LayoutHelper::render('joomla.html.batch.
item', ['extension' => 'com_foos']); ?>
29 </div>
30 </div>
31 <?php endif; ?>
32 </div>
33 </div>

27.1.1.2. administrator/components/com_foos/ tmpl/foos/default_batch_footer.php

The following file creates the footer of the form that is displayed to trigger batch processing.
The “JCANCEL” button clears all values in the form fields using document.getElementById('
ELEMENT_ID').value=''. I have included all possible fields here, although we don’t use them all
yet. For example, batch-user-id and batch-tag-id are not yet used in our form. The button
JGLOBAL_BATCH_PROCESS starts the batch processing.

It is important that you create the batch form as described above in the file administrator/
components/com_foos/tmpl/foos/default_batch_body.php. LayoutHelper in combi-
nation with the appropriate layout ensures that all variables and IDs are set so that the standard
functions run correctly.

administrator/components/com_foos/tmpl/foos/default_batch_footer.php

1
2 <?php
3 \defined('_JEXEC') or die;
4
5 use Joomla\CMS\Language\Text;
6
7 ?>
8 <button type="button" class="btn btn-secondary" onclick="document.
getElementById('batch-category-id').value='';document.getElementById
('batch-access').value='';document.getElementById('batch-language-id
').value='';document.getElementById('batch-user-id').value='';
document.getElementById('batch-tag-id').value=''" data-bs-dismiss="
modal">

Page 254 January 2023


27. Batch Joomla 4 - Developing Extensions

9 <?php echo Text::_('JCANCEL'); ?>


10 </button>
11 <button type="submit" class="btn btn-success" onclick="Joomla.
submitbutton('foo.batch');return false">
12 <?php echo Text::_('JGLOBAL_BATCH_PROCESS'); ?>
13 </button>

27.1.2. Modified files

27.1.2.1. administrator/components/com_foos/ src/Controller/FooController.php

In the controller we implement the method batch. If we look at it closely, we add nothing more than
the specifics: The name of the model used for data processing and the address to forward to after
processing. At the end we call the implementation of Joomla with return parent::batch($model
);. Done! For the standard functions, the wheel has already been invented by Joomla.

administrator/components/com_foos/src/Controller/FooController.php

1
2 \defined('_JEXEC') or die;
3
4 use Joomla\CMS\MVC\Controller\FormController;
5 +use Joomla\CMS\Router\Route;
6
7
8 class FooController extends FormController
9 {
10 + public function batch($model = null)
11 + {
12 + $this->checkToken();
13 +
14 + $model = $this->getModel('Foo', 'Administrator', array());
15 +
16 + // Preset the redirect
17 + $this->setRedirect(Route::_('index.php?option=com_foos&view=
foos' . $this->getRedirectToListAppend(), false));
18 +
19 + return parent::batch($model);
20 + }
21 }

27.1.2.2. administrator/components/com_foos/ src/Model/FooModel.php

In the model we specify whether copying and moving is supported. In case of false the command is
not provided by the batch processing. We also specify the properties that are editable using the batch

January 2023 Page 255


Joomla 4 - Developing Extensions 27. Batch

function.

administrator/components/com_foos/src/Model/FooModel.php

1 protected $associationsContext = 'com_foos.item';


2
3 + protected $batch_copymove = 'category_id';
4 +
5 + protected $batch_commands = array(
6 + 'assetgroup_id' => 'batchAccess',
7 + 'language_id' => 'batchLanguage',
8 + 'user_id' => 'batchUser',
9 + );
10 +

27.1.2.3. administrator/components/com_foos/ src/View/Foos/HtmlView.php

To make the batch processing usable via a button, we add an entry to the toolbar.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 {
2 $childBar->trash('foos.trash')->listCheck(true);
3 }
4 - }
5
6 - if ($this->state->get('filter.published') == -2 && $canDo->get(
'core.delete'))
7 - {
8 - $toolbar->delete('foos.delete')
9 - ->text('JTOOLBAR_EMPTY_TRASH')
10 - ->message('JGLOBAL_CONFIRM_DELETE')
11 - ->listCheck(true);
12 + if ($this->state->get('filter.published') == -2 && $canDo->
get('core.delete'))
13 + {
14 + $childBar->delete('foos.delete')
15 + ->text('JTOOLBAR_EMPTY_TRASH')
16 + ->message('JGLOBAL_CONFIRM_DELETE')
17 + ->listCheck(true);
18 + }
19 +
20 + // Add a batch button
21 + if ($user->authorise('core.create', 'com_foos')
22 + && $user->authorise('core.edit', 'com_foos')
23 + && $user->authorise('core.edit.state', 'com_foos'))
24 + {
25 + $childBar->popupButton('batch')
26 + ->text('JTOOLBAR_BATCH')
27 + ->selector('collapseModal')

Page 256 January 2023


27. Batch Joomla 4 - Developing Extensions

28 + ->listCheck(true);
29 + }
30 }
31
32 if ($user->authorise('core.admin', 'com_foos') || $user->
authorise('core.options', 'com_foos'))

27.1.2.4. administrator/components/com_foos/ tmpl/foos/default.php

We create the template that is used to create the form to trigger batch processing with the help of
HTMLHelper.

administrator/components/com_foos/tmpl/foos/default.php

1 <?php echo $this->pagination->getListFooter(); ?>


2
3 + <?php echo HTMLHelper::_(
4 + 'bootstrap.renderModal',
5 + 'collapseModal',
6 + array(
7 + 'title' => Text::_('COM_FOOS_BATCH_OPTIONS'),
8 + 'footer' => $this->loadTemplate('batch_footer'),
9 + ),
10 + $this->loadTemplate('batch_body')
11 + ); ?>
12 +
13 <?php endif; ?>
14 <input type="hidden" name="task" value="">
15 <input type="hidden" name="boxchecked" value="0">

27.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.

A new installation is not necessary. Continue using the files from the previous part.

2. Open the view of your component in the administration area. In the toolbar you will see a
selection list for triggering various actions. Click the entry “Batch”.

January 2023 Page 257


Joomla 4 - Developing Extensions 27. Batch

Figure 27.1.: Joomla Batch Processing

3. test the batch processing.

Figure 27.2.: Joomla Batch Processing

Page 258 January 2023


Joomla 4 - Developing Extensions 28. Help Sites

28. Help Sites

Self-explanatory software is ideal. But which programme is? For this reason, help is always a useful
addition. Depending on the system, help pages cannot be found immediately or are even hidden.
Joomla offers a uniform procedure for this.

On the one hand, there is a button positioned in the same place in each component, which is used to
call up an external help page.

Figure 28.1.: Joomla Help link in the list view

In addition, it is possible to show descriptions next to the fields in forms. Since Joomla 4.1, these
descriptions can be shown and hidden for a better overview. This feature was introduced with PR
356101 and called inline help.

For impatient people: Look at the changed programme code in the Diff Viewa and copy these

1
github.com/joomla/joomla-cms/pull/35610/

Page 259 January 2023


Joomla 4 - Developing Extensions 28. Help Sites

changes into your development version.


a
codeberg.org/astrid/j4examplecode/compare/t22...t23

28.1. Step by step

The first thing you need to do is to create the help pages for your extension and save them online.
Perhaps you would like to base the structure of your individual help pages on Joomla’s own.

You can find Joomla’s own help pages on the Internet at the address help.joomla.org/proxya . An
example page would be help.joomla.org/proxy?keyref=Help40:Articles&lang=en. Here, help.
joomla.org/proxy stands for the base address and ?keyref=Help40:Articles&lang=en
addresses the specific subpage.
a
help.joomla.org/proxy

28.1.1. New files

In this chapter, only files are changed.

28.1.2. Modified files

Two lines per view are sufficient to display a button at the top right of the dashboard views that contains
a question mark as an icon and has an Internet address specified in the code as the link target. I have
chosen http://example.org as an example. The principle is clear. You have the possibility to create
a separate help site for each View and to link it in the view of the component - exactly where questions
usually arise. And another line is enough to turn descriptions into inline help, which means to make
them fade in and out or toggleable.

28.1.2.1. administrator/components/com_foos/config.xml

In the form for the config, we add a description as an example. This will be shown or hidden later as
inline help..

administrator/components/com_foos/forms/foo.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <config>
4 + <inlinehelp button="show"/>
5 <fieldset

Page 260 January 2023


28. Help Sites Joomla 4 - Developing Extensions

6 name="foo"
7 label="COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DISPLAY"
8 name="show_foo_name_label"
9 type="list"
10 label="COM_FOOS_FIELD_FOO_SHOW_CATEGORY_LABEL"
11 + description="
COM_FOOS_FIELD_FOO_SHOW_CATEGORY_DESC"
12 default="1"
13 >
14 <option value="0">JNO</option>

28.1.2.2. administrator/components/com_foos/forms/foo.xml

In the form, we add a description as an example. This will be shown or hidden later as inline help.

administrator/components/com_foos/forms/foo.xml

1 <?xml version="1.0" encoding="utf-8"?>


2 <form>
3 + <config>
4 + <inlinehelp button="show"/>
5 + </config>
6 <fieldset
7 addruleprefix="FooNamespace\Component\Foos\
Administrator\Rule"
8
9 validate="Letter"
10 class="validate-letter"
11 label="COM_FOOS_FIELD_NAME_LABEL"
12 + description="COM_FOOS_FIELD_NAME_DESC"
13 size="40"
14 required="true"
15 />

28.1.2.3. administrator/components/com_foos/ src/View/Foo/HtmlView.php

The toolbar helper supports us. The line ToolbarHelper::divider(); ensures that the follow-
ing buttons are displayed right-aligned. ToolbarHelper::inlinehelp(); inserts the button that
shows and hides the inline help. The text for this is searched behind description= in the form at the
field. ToolbarHelper::help('', false, 'http://example.org'); inserts the button that
redirects to the external help page. The address of the external page, here in the example http://
example.org, is given as a parameter.

administrator/components/com_foos/src/View/Foos/HtmlView.php

January 2023 Page 261


Joomla 4 - Developing Extensions 28. Help Sites

2
3 ToolbarHelper::cancel('foo.cancel', 'JTOOLBAR_CLOSE');
4 }
5 +
6 + ToolbarHelper::divider();
7 + ToolbarHelper::inlinehelp();
8 + ToolbarHelper::help('', false, 'http://example.org');
9 }
10 }

28.1.2.4. administrator/components/com_foos/ src/View/Foos/HtmlView.php

The same I wrote under administrator/components/com_foos/src/View/Foos/HtmlView.


php also applies here.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 if ($user->authorise('core.admin', 'com_foos') || $user->


authorise('core.options', 'com_foos')) {
2 $toolbar->preferences('com_foos');
3 }
4 + ToolbarHelper::divider();
5 + ToolbarHelper::help('', false, 'http://joomla.org');
6 }
7 }

28.1.2.5. administrator/components/com_foos/ tmpl/foo/edit.php

In the template file administrator/components/com_foos/tmpl/foo/edit.php we load the


required JavaScript.

administrator/components/com_foos/tmpl/foo/edit.php

1 $wa = $this->document->getWebAssetManager();
2 $wa->useScript('keepalive')
3 ->useScript('form.validate')
4 + ->useScript('inlinehelp')
5 ->useScript('com_foos.admin-foos-letter');
6
7 $isModal = $input->get('layout') === 'modal';

Page 262 January 2023


28. Help Sites Joomla 4 - Developing Extensions

28.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder into the administrator folder of your Joomla 4 installation. A new installation is not
necessary. Continue using the files from the previous part.

2. Open the view of your component in the administration area. Click on the help link and make
sure that you are redirected to the help page you entered.

Figure 28.2.: Joomla Help Link in the item view

3. Open the view of your component in the administration area and click several times on the
Inline Toggle Help button. Make sure that all texts that are available as description for a field are
toggled on and off.

January 2023 Page 263


Joomla 4 - Developing Extensions 28. Help Sites

Figure 28.3.: Joomla Inline-Help Toggle Button in the Detail View

Page 264 January 2023


Joomla 4 - Developing Extensions 29. Featured

29. Featured

Some items are special and for them there is a special attribute in Joomla: featured or main entry.
This part of the article series adds featured to our component.
In Joomla, elements marked with featured are displayed when the home page menu item is
linked to the featured layout. In this way, it is possible to show or hide an element only by
changing the “featured” property on a page - for example the start page. This has no effect on
other display properties - for example, displaying in a category blog.

For impatient people: Look at the changed programme code in the Diff Viewa and copy these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t23...t24

29.1. Step by step

29.1.1. New files

29.1.1.1. administrator/components/com_foos/ sql/updates/mysql/24.0.0.sql

You already know this. We store the property featured in the database, so we extend the database
table by one column. We do this in the file 24.0.0.sql.
administrator/components/com_foos/sql/updates/mysql/24.0.0.sql

1
2 ALTER TABLE `#__foos_details ` ADD COLUMN `featured ` tinyint(3)
unsigned NOT NULL DEFAULT 0 COMMENT 'Set if foo is featured.';
3
4 ALTER TABLE `#__foos_details ` ADD KEY `idx_featured_catid ` ( `featured
` , `catid`);

29.1.1.2. components/com_foos/src/Model/FeaturedModel.php

To process the data that is featured, we create our own model.

Page 265 January 2023


Joomla 4 - Developing Extensions 29. Featured

components/com_foos/src/Model/FeaturedModel.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Model;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Component\ComponentHelper;
9 use Joomla\CMS\Factory;
10 use Joomla\CMS\Language\Multilanguage;
11 use Joomla\CMS\MVC\Model\ListModel;
12 use Joomla\Database\ParameterType;
13 use Joomla\Registry\Registry;
14
15 class FeaturedModel extends ListModel
16 {
17 public function __construct($config = [])
18 {
19 if (empty($config['filter_fields'])) {
20 $config['filter_fields'] = [
21 'id', 'a.id',
22 'name', 'a.name',
23 'ordering', 'a.ordering',
24 ];
25 }
26
27 parent::__construct($config);
28 }
29
30 public function getItems()
31 {
32 // Invoke the parent getItems method to get the main list
33 $items = parent::getItems();
34
35 // Convert the params field into an object, saving original in
_params
36 for ($i = 0, $n = count($items); $i < $n; $i++) {
37 $item = &$items[$i];
38
39 if (!isset($this->_params)) {
40 $item->params = new Registry($item->params);
41 }
42 }
43
44 return $items;
45 }
46
47 protected function getListQuery()
48 {

Page 266 January 2023


29. Featured Joomla 4 - Developing Extensions

49 $user = Factory::getUser();
50 $groups = $user->getAuthorisedViewLevels();
51
52 // Create a new query object.
53 $db = $this->getDbo();
54 $query = $db->getQuery(true);
55
56 // Select required fields from the categories.
57 $query->select($this->getState('list.select', 'a.*'))
58 ->from($db->quoteName('#__foos_details', 'a'))
59 ->where($db->quoteName('a.featured') . ' = 1')
60 ->whereIn($db->quoteName('a.access'), $groups)
61 ->innerJoin($db->quoteName('#__categories', 'c') . ' ON c.
id = a.catid')
62 ->whereIn($db->quoteName('c.access'), $groups);
63
64 // Filter by category.
65 if ($categoryId = $this->getState('category.id')) {
66 $query->where($db->quoteName('a.catid') . ' = :catid');
67 $query->bind(':catid', $categoryId, ParameterType::INTEGER)
;
68 }
69
70 $query->select('c.published as cat_published, c.published AS
parents_published')
71 ->where('c.published = 1');
72
73 // Filter by state
74 $state = $this->getState('filter.published');
75
76 if (is_numeric($state)) {
77 $query->where($db->quoteName('a.published') . ' = :
published');
78 $query->bind(':published', $state, ParameterType::INTEGER);
79
80 // Filter by start and end dates.
81 $nowDate = Factory::getDate()->toSql();
82
83 $query->where('(' . $db->quoteName('a.publish_up') .
84 ' IS NULL OR ' . $db->quoteName('a.publish_up') . ' <=
:publish_up)')
85 ->where('(' . $db->quoteName('a.publish_down') .
86 ' IS NULL OR ' . $db->quoteName('a.publish_down') .
' >= :publish_down)')
87 ->bind(':publish_up', $nowDate)
88 ->bind(':publish_down', $nowDate);
89 }
90
91 // Filter by language
92 if ($this->getState('filter.language')) {
93 $language = [Factory::getLanguage()->getTag(), '*'];

January 2023 Page 267


Joomla 4 - Developing Extensions 29. Featured

94 $query->whereIn($db->quoteName('a.language'), $language,
ParameterType::STRING);
95 }
96
97 // Add the list ordering clause.
98 $query->order($db->escape($this->getState('list.ordering', 'a.
ordering')) . ' ' . $db->escape($this->getState('list.
direction', 'ASC')));
99
100 return $query;
101 }
102
103 protected function populateState($ordering = null, $direction =
null)
104 {
105 $app = Factory::getApplication();
106 $params = ComponentHelper::getParams('com_foos');
107
108 // List state information
109 $limit = $app->getUserStateFromRequest('global.list.limit', '
limit', $app->get('list_limit'), 'uint');
110 $this->setState('list.limit', $limit);
111
112 $limitstart = $app->input->get('limitstart', 0, 'uint');
113 $this->setState('list.start', $limitstart);
114
115 $orderCol = $app->input->get('filter_order', 'ordering');
116
117 if (!in_array($orderCol, $this->filter_fields)) {
118 $orderCol = 'ordering';
119 }
120
121 $this->setState('list.ordering', $orderCol);
122
123 $listOrder = $app->input->get('filter_order_Dir', 'ASC');
124
125 if (!in_array(strtoupper($listOrder), ['ASC', 'DESC', ''])) {
126 $listOrder = 'ASC';
127 }
128
129 $this->setState('list.direction', $listOrder);
130
131 $user = Factory::getUser();
132
133 if ((!$user->authorise('core.edit.state', 'com_foos')) && (!
$user->authorise('core.edit', 'com_foos'))) {
134 // Limit to published for people who can't edit or edit.
state.
135 $this->setState('filter.published', 1);
136
137 // Filter by start and end dates.

Page 268 January 2023


29. Featured Joomla 4 - Developing Extensions

138 $this->setState('filter.publish_date', true);


139 }
140
141 $this->setState('filter.language', Multilanguage::isEnabled());
142
143 // Load the parameters.
144 $this->setState('params', $params);
145 }
146 }

29.1.1.3. components/com_foos/src/View/Featured/HtmlView.php

featured gets its own file to manage the display in the frontend.

You see here the first time the word slug in the line $item->slug = $item->alias ? ($item
->id . ':'. $item->alias): $item->id;. A slug is used to keep the code supporting
search engine friendly URLs as short as possible. It is composed of the ID of the element, a colon
and the alias.

components/com_foos/src/View/Featured/HtmlView.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\View\Featured;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\HTML\HTMLHelper;
10 use Joomla\CMS\Language\Text;
11 use Joomla\CMS\Mail\MailHelper;
12 use Joomla\CMS\MVC\View\GenericDataException;
13 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
14
15 class HtmlView extends BaseHtmlView
16 {
17 protected $state;
18
19 protected $items;
20
21 protected $pagination;
22
23 protected $params = null;
24
25 protected $pageclass_sfx = '';
26
27 public function display($tpl = null)

January 2023 Page 269


Joomla 4 - Developing Extensions 29. Featured

28 {
29 $app = Factory::getApplication();
30 $params = $app->getParams();
31
32 // Get some data from the models
33 $state = $this->get('State');
34 $items = $this->get('Items');
35 $category = $this->get('Category');
36 $children = $this->get('Children');
37 $parent = $this->get('Parent');
38 $pagination = $this->get('Pagination');
39
40 // Flag indicates to not add limitstart=0 to URL
41 $pagination->hideEmptyLimitstart = true;
42
43 // Check for errors.
44 if (count($errors = $this->get('Errors'))) {
45 throw new GenericDataException(implode("\n", $errors), 500)
;
46 }
47
48 // Prepare the data.
49 // Compute the foos slug.
50 for ($i = 0, $n = count($items); $i < $n; $i++) {
51 $item = &$items[$i];
52 $item->slug = $item->alias ? ($item->id . ':' . $item->
alias) : $item->id;
53 $temp = $item->params;
54 $item->params = clone $params;
55 $item->params->merge($temp);
56
57 if ($item->params->get('show_email', 0) == 1) {
58 $item->email_to = trim($item->email_to);
59
60 if (!empty($item->email_to) && MailHelper::
isEmailAddress($item->email_to)) {
61 $item->email_to = HTMLHelper::_('email.cloak',
$item->email_to);
62 } else {
63 $item->email_to = '';
64 }
65 }
66 }
67
68 // Escape strings for HTML output
69 $this->pageclass_sfx = htmlspecialchars($params->get('
pageclass_sfx'), ENT_COMPAT, 'UTF-8');
70
71 $maxLevel = $params->get('maxLevel', -1);
72 $this->maxLevel = &$maxLevel;
73 $this->state = &$state;

Page 270 January 2023


29. Featured Joomla 4 - Developing Extensions

74 $this->items = &$items;
75 $this->category = &$category;
76 $this->children = &$children;
77 $this->params = &$params;
78 $this->parent = &$parent;
79 $this->pagination = &$pagination;
80
81 $this->_prepareDocument();
82
83 return parent::display($tpl);
84 }
85
86 protected function _prepareDocument()
87 {
88 $app = Factory::getApplication();
89 $menus = $app->getMenu();
90 $title = null;
91
92 // Because the application sets a default page title,
93 // we need to get it from the menu item itself
94 $menu = $menus->getActive();
95
96 if ($menu) {
97 $this->params->def('page_heading', $this->params->get('
page_title', $menu->title));
98 } else {
99 $this->params->def('page_heading', Text::_('
COM_FOOS_DEFAULT_PAGE_TITLE'));
100 }
101
102 $title = $this->params->get('page_title', '');
103
104 if (empty($title)) {
105 $title = $app->get('sitename');
106 } else if ($app->get('sitename_pagetitles', 0) == 1) {
107 $title = Text::sprintf('JPAGETITLE', $app->get('sitename'),
$title);
108 } else if ($app->get('sitename_pagetitles', 0) == 2) {
109 $title = Text::sprintf('JPAGETITLE', $title, $app->get('
sitename'));
110 }
111
112 $this->document->setTitle($title);
113
114 if ($this->params->get('menu-meta_description')) {
115 $this->document->setDescription($this->params->get('menu-
meta_description'));
116 }
117
118 if ($this->params->get('menu-meta_keywords')) {
119 $this->document->setMetaData('keywords', $this->params->get

January 2023 Page 271


Joomla 4 - Developing Extensions 29. Featured

('menu-meta_keywords'));
120 }
121
122 if ($this->params->get('robots')) {
123 $this->document->setMetaData('robots', $this->params->get('
robots'));
124 }
125 }
126 }

29.1.1.4. components/com_foos/ tmpl/featured/default.php

The display in the frontend is done as before via a template, which we implement in the file default.
php.

components/com_foos/tmpl/featured/default.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 ?>
7 <div class="com-foos-featured blog-featured">
8 <?php if ($this->params->get('show_page_headings') != 0) : ?>
9 <h1>
10 <?php echo $this->escape($this->params->get('page_heading'));
?>
11 </h1>
12 <?php endif; ?>
13
14 <?php echo $this->loadTemplate('items'); ?>
15
16 <?php if ($this->params->def('show_pagination', 2) == 1 || ($this->
params->get('show_pagination') == 2 && $this->pagination->pagesTotal
> 1)) : ?>
17 <div class="com-foos-featured__pagination w-100">
18 <?php if ($this->params->def('show_pagination_results', 1)) :
?>
19 <p class="counter float-right pt-3 pr-2">
20 <?php echo $this->pagination->getPagesCounter(); ?>
21 </p>
22 <?php endif; ?>
23
24 <?php echo $this->pagination->getPagesLinks(); ?>
25 </div>
26 <?php endif; ?>
27 </div>

Page 272 January 2023


29. Featured Joomla 4 - Developing Extensions

29.1.1.5. components/com_foos/ tmpl/featured/default.xml

We need the file components/com_foos/tmpl/featured/default.xml to enable the display of


featured elements via a menu item in the frontend.

components/com_foos/tmpl/featured/default.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3
4 <metadata>
5 <layout title="COM_FOOS_FEATURED_VIEW_DEFAULT_TITLE">
6 <help
7 key = "JHELP_MENUS_MENU_ITEM_FOOS_FEATURED"
8 />
9 <message>
10 <![CDATA[COM_FOOS_FEATURED_VIEW_DEFAULT_DESC]]>
11 </message>
12 </layout>
13
14 <!-- Add fields to the parameters object for the layout. -->
15 <fields name="params">
16 <fieldset name="advanced" label="JGLOBAL_LIST_LAYOUT_OPTIONS">
17
18 <field
19 name="spacer"
20 type="spacer"
21 label="JGLOBAL_SUBSLIDER_DRILL_CATEGORIES_LABEL"
22 class="text"
23 />
24
25 <field
26 name="show_headings"
27 type="list"
28 label="JGLOBAL_SHOW_HEADINGS_LABEL"
29 useglobal="true"
30 class="custom-select-color-state"
31 >
32 <option value="0">JHIDE</option>
33 <option value="1">JSHOW</option>
34 </field>
35
36 <field
37 name="show_pagination"
38 type="list"
39 label="JGLOBAL_PAGINATION_LABEL"
40 useglobal="true"
41 class="custom-select-color-state"
42 >
43 <option value="0">JHIDE</option>
44 <option value="1">JSHOW</option>

January 2023 Page 273


Joomla 4 - Developing Extensions 29. Featured

45 <option value="2">JGLOBAL_AUTO</option>
46 </field>
47 </fieldset>
48 </fields>
49 </metadata>

29.1.1.6. components/com_foos/ tmpl/featured/default_items.php

In the file default.php we use the statement <?php echo $this->loadTemplate('items');


?>. This way we keep the template clear. Everything concerning an item is inserted into default.php
via the subtemplate default_items.php.

When using subtemplates, it is important that they are located in the same directory as the actual
template and that their names match:
The call <?php echo $this->loadTemplate('NAME'); ?> loads the file
default_NAME.php if it is in the file default.php is executed.

components/com_foos/tmpl/featured/default_items.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\HTML\HTMLHelper;
7 use Joomla\CMS\Language\Text;
8 use Joomla\CMS\Uri\Uri;
9
10 HTMLHelper::_('behavior.core');
11
12 $listOrder = $this->escape($this->state->get('list.ordering'));
13 $listDirn = $this->escape($this->state->get('list.direction'));
14
15 ?>
16
17 <div class="com-foos-featured__items">
18 <?php if (empty($this->items)) : ?>
19 <p class="com-foos-featured__message"> <?php echo Text::_('
COM_FOO_NO_FOOS'); ?> </p>
20 <?php else : ?>
21 <form action="<?php echo htmlspecialchars(Uri::getInstance()->
toString()); ?>" method="post" name="adminForm" id="adminForm">
22 <table class="com-foos-featured__table table">
23 <?php if ($this->params->get('show_headings')) : ?>
24 <thead class="thead-default">
25 <tr>
26 <th class="item-num">

Page 274 January 2023


29. Featured Joomla 4 - Developing Extensions

27 <?php echo Text::_('JGLOBAL_NUM'); ?>


28 </th>
29
30 <th class="item-title">
31 <?php echo HTMLHelper::_('grid.sort', '
COM_FOO_FOO_EMAIL_NAME_LABEL', 'a.name',
$listDirn, $listOrder); ?>
32 </th>
33 </tr>
34 </thead>
35 <?php endif; ?>
36
37 <tbody>
38 <?php foreach ($this->items as $i => $item) : ?>
39 <tr class="<?php echo ($i % 2) ? 'odd' : 'even'; ?>
" itemscope itemtype="https://schema.org/Person"
>
40 <td class="item-num">
41 <?php echo $i; ?>
42 </td>
43
44 <td class="item-title">
45 <?php if ($this->items[$i]->published == 0)
: ?>
46 <span class="badge badge-warning"><?php
echo Text::_('JUNPUBLISHED'); ?></
span>
47 <?php endif; ?>
48 <span itemprop="name"><?php echo $item
->name; ?></span>
49 </td>
50 </tr>
51 <?php endforeach; ?>
52 </tbody>
53 </table>
54
55 </form>
56 <?php endif; ?>
57 </div>

29.1.2. Modified files

29.1.2.1. administrator/components/com_foos/ forms/foo.xml

We extend the form with which an element is created or changed by the field for setting the property
featured.

administrator/components/com_foos/forms/foo.xml

January 2023 Page 275


Joomla 4 - Developing Extensions 29. Featured

1 <option value="*">JALL</option>
2 </field>
3
4 + <field
5 + name="featured"
6 + type="radio"
7 + class="switcher"
8 + label="JFEATURED"
9 + default="0"
10 + >
11 + <option value="0">JNO</option>
12 + <option value="1">JYES</option>
13 + </field>
14 +
15 <field
16 name="published"
17 type="list"

29.1.2.2. administrator/components/com_foos/ sql/install.mysql.utf8.sql

In the case of a new installation, the script in the file install.mysql.utf8.sql creates the database.
Here we add a column to store the property featured.

administrator/components/com_foos/sql/install.mysql.utf8.sql

1 ALTER TABLE `#__foos_details ` ADD KEY `idx_checkout ` ( `checked_out`);


2
3 +ALTER TABLE `#__foos_details ` ADD COLUMN `featured ` tinyint(3)
unsigned NOT NULL DEFAULT 0 COMMENT 'Set if foo is featured.';
4 +
5 +ALTER TABLE `#__foos_details ` ADD KEY `idx_featured_catid ` ( `featured
` , `catid`);

29.1.2.3. administrator/components/com_foos/ src/Controller/FoosController.php

We implement the logic with which we set the featured property in the featured() function in the
FoosController.

administrator/components/com_foos/src/Controller/FoosController.php

1 \defined('_JEXEC') or die;
2
3 +use Joomla\CMS\Language\Text;
4 +use Joomla\Utilities\ArrayHelper;
5 use Joomla\CMS\Application\CMSApplication;
6 use Joomla\CMS\MVC\Controller\AdminController;

Page 276 January 2023


29. Featured Joomla 4 - Developing Extensions

7 use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
8
9 public function __construct($config = array(), MVCFactoryInterface
$factory = null, $app = null, $input = null)
10 {
11 parent::__construct($config, $factory, $app, $input);
12 +
13 + $this->registerTask('unfeatured', 'featured');
14 + }
15 +
16 + public function featured()
17 + {
18 + // Check for request forgeries
19 + $this->checkToken();
20 +
21 + $ids = $this->input->get('cid', array(), 'array');
22 + $values = array('featured' => 1, 'unfeatured' => 0);
23 + $task = $this->getTask();
24 + $value = ArrayHelper::getValue($values, $task, 0, 'int');
25 +
26 + $model = $this->getModel();
27 +
28 + // Access checks.
29 + foreach ($ids as $i => $id)
30 + {
31 + $item = $model->getItem($id);
32 +
33 + if (!$this->app->getIdentity()->authorise('core.edit.state'
, 'com_foos.category.' . (int) $item->catid))
34 + {
35 + // Prune items that you can't change.
36 + unset($ids[$i]);
37 + $this->app->enqueueMessage(Text::_('
JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
38 + }
39 + }
40 +
41 + if (empty($ids))
42 + {
43 + $this->app->enqueueMessage(Text::_('
COM_FOOS_NO_ITEM_SELECTED'), 'warning');
44 + }
45 + else
46 + {
47 + // Publish the items.
48 + if (!$model->featured($ids, $value))
49 + {
50 + $this->app->enqueueMessage($model->getError(), 'warning
');
51 + }
52 +

January 2023 Page 277


Joomla 4 - Developing Extensions 29. Featured

53 + if ($value == 1)
54 + {
55 + $message = Text::plural('COM_FOOS_N_ITEMS_FEATURED',
count($ids));
56 + }
57 + else
58 + {
59 + $message = Text::plural('COM_FOOS_N_ITEMS_UNFEATURED',
count($ids));
60 + }
61 + }
62 +
63 + $this->setRedirect('index.php?option=com_foos&view=foos',
$message);
64 }

29.1.2.4. administrator/components/com_foos/ src/Model/FooModel.php

In the model of an element we implement the method with which the assignment of the property
(data) featured is saved and changed.

administrator/components/com_foos/src/Model/FooModel.php

1 use Joomla\CMS\Language\Associations;
2 use Joomla\CMS\MVC\Model\AdminModel;
3 use Joomla\CMS\Language\LanguageHelper;
4 +use Joomla\Database\ParameterType;
5 +use Joomla\Utilities\ArrayHelper;
6
7
8 return $item;
9 }
10
11 + public function featured($pks, $value = 0)
12 + {
13 + // Sanitize the ids.
14 + $pks = ArrayHelper::toInteger((array) $pks);
15 +
16 + if (empty($pks))
17 + {
18 + $this->setError(Text::_('COM_FOOS_NO_ITEM_SELECTED'));
19 +
20 + return false;
21 + }
22 +
23 + $table = $this->getTable();
24 +
25 + try
26 + {

Page 278 January 2023


29. Featured Joomla 4 - Developing Extensions

27 + $db = $this->getDbo();
28 +
29 + $query = $db->getQuery(true);
30 + $query->update($db->quoteName('#__foos_details'));
31 + $query->set($db->quoteName('featured') . ' = :featured');
32 + $query->whereIn($db->quoteName('id'), $pks);
33 + $query->bind(':featured', $value, ParameterType::INTEGER);
34 +
35 + $db->setQuery($query);
36 +
37 + $db->execute();
38 + }
39 + catch (\Exception $e)
40 + {
41 + $this->setError($e->getMessage());
42 +
43 + return false;
44 + }
45 +
46 + $table->reorder();
47 +
48 + // Clean component's cache
49 + $this->cleanCache();
50 +
51 + return true;
52 + }
53 +

29.1.2.5. administrator/components/com_foos/ src/Model/FoosModel.php

In the list view model, we make the necessary adjustments to the database query.

administrator/components/com_foos/src/Model/FoosModel.php

1 'published', 'a.published',
2 'access', 'a.access', 'access_level',
3 'ordering', 'a.ordering',
4 + 'featured', 'a.featured',
5 'language', 'a.language', 'language_title',
6 'publish_up', 'a.publish_up',
7 'publish_down', 'a.publish_down',
8
9
10 parent::__construct($config);
11 }
12 +
13
14 ', a.checked_out_time' .
15 ', a.language' .

January 2023 Page 279


Joomla 4 - Developing Extensions 29. Featured

16 ', a.ordering' .
17 + ', a.featured' .
18 ', a.state' .
19 ', a.published' .
20 ', a.publish_up, a.publish_down'
21
22 }
23 }
24
25 + // Filter by featured.
26 + $featured = (string) $this->getState('filter.featured');
27 +
28 + if (in_array($featured, ['0','1']))
29 + {
30 + $query->where($db->quoteName('a.featured') . ' = ' . (int)
$featured);
31 + }
32 +
33 // Add the list ordering clause.
34 $orderCol = $this->state->get('list.ordering', 'a.name');
35 $orderDirn = $this->state->get('list.direction', 'asc');

29.1.2.6. administrator/components/com_foos/ src/Service/HTML/AdministratorService.php

In AdministratorService.php we make it possible to change the assignment of the property also


in the overview list. A click on the star symbol toggles the value.

administrator/components/com_foos/src/Service/HTML/AdministratorService.php

1 use Joomla\CMS\Language\Text;
2 use Joomla\CMS\Layout\LayoutHelper;
3 use Joomla\CMS\Router\Route;
4 +use Joomla\Utilities\ArrayHelper;
5
6
7 $html = LayoutHelper::render('joomla.content.associations',
$items);
8 }
9
10 + return $html;
11 + }
12 + public function featured($value, $i, $canChange = true)
13 + {
14 + // Array of image, task, title, action
15 + $states = array(
16 + 0 => array('unfeatured', 'foos.featured', '
COM_CONTACT_UNFEATURED', 'JGLOBAL_ITEM_FEATURE'),
17 + 1 => array('featured', 'foos.unfeatured', 'JFEATURED', '
JGLOBAL_ITEM_UNFEATURE'),

Page 280 January 2023


29. Featured Joomla 4 - Developing Extensions

18 + );
19 + $state = ArrayHelper::getValue($states, (int) $value, $states
[1]);
20 + $icon = $state[0] === 'featured' ? 'star featured' : 'star';
21 +
22 + if ($canChange)
23 + {
24 + $html = '<a href="#" onclick="return Joomla.listItemTask(\'
cb' . $i . '\',\'' . $state[1] . '\')" class="tbody-icon'
25 + . ($value == 1 ? ' active' : '') . '" aria-labelledby="
cb' . $i . '-desc">'
26 + . '<span class="fas fa-' . $icon . '" aria-hidden="true
"></span></a>'
27 + . '<div role="tooltip" id="cb' . $i . '-desc">' . Text
::_($state[3]);
28 + }
29 + else
30 + {
31 + $html = '<a class="tbody-icon disabled' . ($value == 1 ? '
active' : '')
32 + . '" title="' . Text::_($state[2]) . '"><span class="
fas fa-' . $icon . '" aria-hidden="true"></span></a>';
33 + }
34 +
35 return $html;
36 }
37 }

29.1.2.7. administrator/components/com_foos/ src/View/Foos/HtmlView.php

We add to the toolbar. featured should also be editable here via an action.

administrator/components/com_foos/src/View/Foos/HtmlView.php

1 $childBar = $dropdown->getChildToolbar();
2 $childBar->publish('foos.publish')->listCheck(true);
3 $childBar->unpublish('foos.unpublish')->listCheck(true);
4 +
5 + $childBar->standardButton('featured')
6 + ->text('JFEATURE')
7 + ->task('foos.featured')
8 + ->listCheck(true);
9 + $childBar->standardButton('unfeatured')
10 + ->text('JUNFEATURE')
11 + ->task('foos.unfeatured')
12 + ->listCheck(true);
13 +
14 $childBar->archive('foos.archive')->listCheck(true);
15

January 2023 Page 281


Joomla 4 - Developing Extensions 29. Featured

16 if ($user->authorise('core.admin'))

29.1.2.8. administrator/components/com_foos/ tmpl/foo/edit.php

In the form for creating or editing an element, we insert the command that creates a field using the
XML file.

administrator/components/com_foos/tmpl/foo/edit.php

1 <?php echo $this->getForm()->renderField('


publish_down'); ?>
2 <?php echo $this->getForm()->renderField('catid
'); ?>
3 <?php echo $this->getForm()->renderField('
language'); ?>
4 + <?php echo $this->getForm()->renderField('
featured'); ?>
5 </div>
6 </div>
7 </div>

29.1.2.9. administrator/components/com_foos/ tmpl/foos/default.php

In the overview of all elements in the backend in the file administrator/components/com_foos/


tmpl/foos/default.php we add a column in which the state is displayed with a filled or empty star
and can be changed by clicking. The file HTMLHelper does the master work for us.

administrator/components/com_foos/tmpl/foos/default.php

1 <th scope="col" style="width:1%" class=


"text-center d-none d-md-table-cell"
>
2 <?php echo HTMLHelper::_('
searchtools.sort', '
COM_FOOS_TABLE_TABLEHEAD_NAME',
'a.name', $listDirn, $listOrder)
; ?>
3 </th>
4 + <th scope="col" style="width:1%" class=
"text-center">
5 + <?php echo HTMLHelper::_('
searchtools.sort', 'JFEATURED', 'a.featured', $listDirn, $listOrder)
; ?>
6 + </th>
7 <th scope="col" style="width:10%" class
="d-none d-md-table-cell">

Page 282 January 2023


29. Featured Joomla 4 - Developing Extensions

8 <?php echo HTMLHelper::_('


searchtools.sort', '
JGRID_HEADING_ACCESS', '
access_level', $listDirn,
$listOrder); ?>
9 </th>
10
11 </div>
12 </th>
13 <td class="text-center">
14 - <?php
15 - echo HTMLHelper::_('jgrid.published
', $item->published, $i, 'foos.', $canChange, 'cb', $item->
publish_up, $item->publish_down);
16 - ?>
17 + <?php echo HTMLHelper::_('
foosadministrator.featured', $item->featured, $i, $canChange); ?>
18 </td>
19 <td class="small d-none d-md-table-cell
">
20 <?php echo $item->access_level; ?>
21 </td>
22 + <td class="text-center">
23 + <?php
24 + echo HTMLHelper::_('jgrid.published
', $item->published, $i, 'foos.', $canChange, 'cb', $item->
publish_up, $item->publish_down);
25 + ?>
26
27 + </td>
28 <?php if ($assoc) : ?>
29 <td class="d-none d-md-table-cell">
30 <?php if ($item->association) : ?>

29.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.
Copy the files in the components folder into the components folder of your Joomla 4 installation.

2. the database has been changed, so it is necessary to update it. Open the System |
Information | Database section as described in part Publish and Unpublish. Select
your component and click on Update Structure.

January 2023 Page 283


Joomla 4 - Developing Extensions 29. Featured

3. Open the view of your component in the administration area. The list contains a column that is
overwritten with featured.

Figure 29.1.: Joomla Featured

Open an item in the edit view and make sure that you are offered the attribute featured for editing.

Figure 29.2.: Joomla Featured

4. create a menu item of the type featured.

Page 284 January 2023


29. Featured Joomla 4 - Developing Extensions

Figure 29.3.: Joomla Featured

5. switch to the frontend and make sure that only items are displayed under the menu item
’featured for which the attribute is set and for which the item and the corresponding category
is also published.

Figure 29.4.: Joomla Featured elements in the backend

January 2023 Page 285


Joomla 4 - Developing Extensions 29. Featured

Figure 29.5.: Joomla Featured elements in the frontend

Page 286 January 2023


Joomla 4 - Developing Extensions 30. Backend Form

30. Backend Form

The administration area has filled up. I have inserted the individual parameters without structure so
far. It is user-friendly if the views in an application are uniform. That way, everyone can quickly find
their way around. It is not necessary to familiarise oneself with every new extension. In this part of
the tutorial we will tidy up the view in the administration area. Our aim is to adapt the display to the
standard views in the content management system. As in the following picture, your backend looks
tidy and “Joomla-like”.

Figure 30.1.: Joomla Ansicht im Backend

For impatient people: Look at the changed programme code in the Diff Viewa and copy these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t24...t24b

Page 287 January 2023


Joomla 4 - Developing Extensions 30. Backend Form

30.1. Step by step

30.1.1. New files

Nothing new

30.1.2. Modified files

30.1.2.1. administrator/components/com_foos/ tmpl/foo/edit.php

We replace the previously rudimentary form fields. The result is a view that resembles the normal
Joomla extensions.

1 <form action="<?php echo Route::_('index.php?option=com_foos&layout='


. $layout . $tmpl . '&id=' . (int) $this->item->id); ?>" method="
post" name="adminForm" id="foo-form" class="form-validate">
2 +
3 + <?php echo LayoutHelper::render('joomla.edit.title_alias', $this);
?>
4 <div>
5 <?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', ['active
' => 'details']); ?>
6
7
8 <div class="col-md-9">
9 <div class="row">
10 <div class="col-md-6">
11 - <?php echo $this->getForm()->renderField('name'
); ?>
12 - <?php echo $this->getForm()->renderField('alias
'); ?>
13 - <?php echo $this->getForm()->renderField('
access'); ?>
14 - <?php echo $this->getForm()->renderField('
published'); ?>
15 - <?php echo $this->getForm()->renderField('
publish_up'); ?>
16 - <?php echo $this->getForm()->renderField('
publish_down'); ?>
17 - <?php echo $this->getForm()->renderField('catid
'); ?>
18 - <?php echo $this->getForm()->renderField('
language'); ?>
19 - <?php echo $this->getForm()->renderField('
featured'); ?>
20 + <?php echo 'Hier ist Platz für die Inhalte
deiner Erweiterung'; ?>

Page 288 January 2023


30. Backend Form Joomla 4 - Developing Extensions

21 + </div>
22 + </div>
23 + </div>
24 + <div class="col-lg-3">
25 + <div class="card">
26 + <div class="card-body">
27 + <?php echo LayoutHelper::render('joomla.edit.
global', $this); ?>
28 </div>
29 </div>
30 </div>
31
32
33 <?php echo LayoutHelper::render('joomla.edit.params', $this);
?>
34
35 + <?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'publishing',
Text::_('JGLOBAL_FIELDSET_PUBLISHING')); ?>
36 + <div class="row">
37 + <div class="col-md-6">
38 + <fieldset id="fieldset-publishingdata" class="options-
form">
39 + <legend><?php echo Text::_('
JGLOBAL_FIELDSET_PUBLISHING'); ?></legend>
40 + <div>
41 + <?php echo LayoutHelper::render('joomla.edit.
publishingdata', $this); ?>
42 + </div>
43 + </fieldset>
44 + </div>
45 + </div>
46 + <?php echo HTMLHelper::_('uitab.endTab'); ?>
47 +
48 <?php echo HTMLHelper::_('uitab.endTabSet'); ?>
49 </div>
50 <input type="hidden" name="task" value="">

The main change is that we now use Joomla’s own layout joomla.edit.publishingdata. This is
in the directory /layouts/joomla/edit/publishingdata.php and you can check the content
in the following code example. Besides the uniform view, another advantage is that the layout file
is maintained by Joomla and you are therefore less likely to experience unpleasant surprises when
updating.

1 <?php
2
3 defined('_JEXEC') or die;
4
5 $form = $displayData->getForm();
6
7 $fields = $displayData->get('fields') ?: array(

January 2023 Page 289


Joomla 4 - Developing Extensions 30. Backend Form

8 'publish_up',
9 'publish_down',
10 'featured_up',
11 'featured_down',
12 array('created', 'created_time'),
13 array('created_by', 'created_user_id'),
14 'created_by_alias',
15 array('modified', 'modified_time'),
16 array('modified_by', 'modified_user_id'),
17 'version',
18 'hits',
19 'id'
20 );
21
22 $hiddenFields = $displayData->get('hidden_fields') ?: array();
23
24 foreach ($fields as $field) {
25 foreach ((array) $field as $f) {
26 if ($form->getField($f)) {
27 if (in_array($f, $hiddenFields)) {
28 $form->setFieldAttribute($f, 'type', 'hidden');
29 }
30
31 echo $form->renderField($f);
32 break;
33 }
34 }
35 }

30.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.

A new installation is not necessary. Continue using the files from the previous part.

2. Open the view of your component in the administration area. Edit an item and make sure that
the display is as you expect it to be in Joomla. You can see an example in the picture at the
beginning of this chapter.

Page 290 January 2023


Joomla 4 - Developing Extensions 31. Frontend Editing

31. Frontend Editing

There are several reasons for allowing a user to edit in the frontend. For one thing, users feel that
working directly on the website is more user-friendly than logging into the backend. Or, it is important
for an administrator not to release access to the administration area. Therefore, in the next step, we
equip our component with the possibility to edit items in the frontend.

For impatient people: Look at the changed programme code in the Diff Viewa and copy these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t24b...t25

31.1. Step by step

31.1.1. New files

31.1.1.1. administrator/components/com_foos/ src/Service/HTML/Icon.php

The following file contains all the information needed to display an icon used to open the edit in the
frontend - provided the viewer is allowed to edit.

administrator/components/com_foos/src/Service/HTML/Icon.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Application\CMSApplication;
9 use Joomla\CMS\Factory;
10 use Joomla\CMS\HTML\HTMLHelper;
11 use Joomla\CMS\Language\Text;
12 use Joomla\CMS\Layout\LayoutHelper;
13 use Joomla\CMS\Router\Route;
14 use Joomla\CMS\Uri\Uri;
15 use FooNamespace\Component\Foos\Site\Helper\RouteHelper;

Page 291 January 2023


Joomla 4 - Developing Extensions 31. Frontend Editing

16 use Joomla\Registry\Registry;
17
18 class Icon
19 {
20 private $application;
21
22 public function __construct(CMSApplication $application)
23 {
24 $this->application = $application;
25 }
26
27 public static function create($category, $params, $attribs = [])
28 {
29 $uri = Uri::getInstance();
30
31 $url = 'index.php?option=com_foos&task=foo.add&return=' .
base64_encode($uri) . '&id=0&catid=' . $category->id;
32
33 $text = LayoutHelper::render('joomla.content.icons.create', ['
params' => $params, 'legacy' => false]);
34
35 // Add the button classes to the attribs array
36 if (isset($attribs['class'])) {
37 $attribs['class'] .= ' btn btn-primary';
38 } else {
39 $attribs['class'] = 'btn btn-primary';
40 }
41
42 $button = HTMLHelper::_('link', Route::_($url), $text, $attribs
);
43
44 $output = '<span class="hasTooltip" title="' . HTMLHelper::_('
tooltipText', 'COM_FOOS_CREATE_FOO') . '">' . $button . '</
span>';
45
46 return $output;
47 }
48
49 public static function edit($foo, $params, $attribs = [], $legacy =
false)
50 {
51 $user = Factory::getUser();
52 $uri = Uri::getInstance();
53
54 // Ignore if in a popup window.
55 if ($params && $params->get('popup')) {
56 return '';
57 }
58
59 // Ignore if the state is negative (trashed).
60 if ($foo->published < 0) {

Page 292 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

61 return '';
62 }
63
64 // Set the link class
65 $attribs['class'] = 'dropdown-item';
66
67 // Show checked_out icon if the foo is checked out by a
different user
68 if (property_exists($foo, 'checked_out')
69 && property_exists($foo, 'checked_out_time')
70 && $foo->checked_out > 0
71 && $foo->checked_out != $user->get('id')) {
72 $checkoutUser = Factory::getUser($foo->checked_out);
73 $date = HTMLHelper::_('date', $foo->
checked_out_time);
74 $tooltip = Text::_('JLIB_HTML_CHECKED_OUT') . ' :: ' .
Text::sprintf('COM_FOOS_CHECKED_OUT_BY', $checkoutUser
->name)
75 . ' <br /> ' . $date;
76
77 $text = LayoutHelper::render('joomla.content.icons.
edit_lock', ['tooltip' => $tooltip, 'legacy' => $legacy
]);
78
79 $output = HTMLHelper::_('link', '#', $text, $attribs);
80
81 return $output;
82 }
83
84 if (!isset($foo->slug)) {
85 $foo->slug = "";
86 }
87
88 $fooUrl = RouteHelper::getFooRoute($foo->slug, $foo->catid,
$foo->language);
89 $url = $fooUrl . '&task=foo.edit&id=' . $foo->id . '&
return=' . base64_encode($uri);
90
91 if ($foo->published == 0) {
92 $overlib = Text::_('JUNPUBLISHED');
93 } else {
94 $overlib = Text::_('JPUBLISHED');
95 }
96
97 if (!isset($foo->created)) {
98 $date = HTMLHelper::_('date', 'now');
99 } else {
100 $date = HTMLHelper::_('date', $foo->created);
101 }
102
103 if (!isset($created_by_alias) && !isset($foo->created_by)) {

January 2023 Page 293


Joomla 4 - Developing Extensions 31. Frontend Editing

104 $author = '';


105 } else {
106 $author = $foo->created_by_alias ?: Factory::getUser($foo->
created_by)->name;
107 }
108
109 $overlib .= '&lt;br /&gt;';
110 $overlib .= $date;
111 $overlib .= '&lt;br /&gt;';
112 $overlib .= Text::sprintf('COM_FOOS_WRITTEN_BY',
htmlspecialchars($author, ENT_COMPAT, 'UTF-8'));
113
114 $icon = $foo->published ? 'edit' : 'eye-slash';
115
116 if (strtotime($foo->publish_up) > strtotime(Factory::getDate())
117 || ((strtotime($foo->publish_down) < strtotime(Factory::
getDate())) && $foo->publish_down != Factory::getDbo()->
getNullDate())) {
118 $icon = 'eye-slash';
119 }
120
121 $text = '<span class="hasTooltip fa fa-' . $icon . '" title="'
122 . HTMLHelper::tooltipText(Text::_('COM_FOOS_EDIT_FOO'),
$overlib, 0, 0) . '"></span> ';
123 $text .= Text::_('JGLOBAL_EDIT');
124
125 $attribs['title'] = Text::_('COM_FOOS_EDIT_FOO');
126 $output = HTMLHelper::_('link', Route::_($url), $text
, $attribs);
127
128 return $output;
129 }
130 }

31.1.1.2. components/com_foos/ forms/foo.xml

We adapt the XML file that Joomla uses to build the form.

components/com_foos/forms/foo.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <form>
4 <fieldset
5 addruleprefix="FooNamespace\Component\Foos\Administrator\Rule"
6 addfieldprefix="FooNamespace\Component\Foos\Administrator\Field
"
7 >
8 <field

Page 294 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

9 name="id"
10 type="number"
11 label="JGLOBAL_FIELD_ID_LABEL"
12 default="0"
13 class="readonly"
14 readonly="true"
15 />
16
17 <field
18 name="name"
19 type="text"
20 validate="Letter"
21 class="validate-letter"
22 label="COM_FOOS_FIELD_NAME_LABEL"
23 size="40"
24 required="true"
25 />
26
27 <field
28 name="alias"
29 type="text"
30 label="JFIELD_ALIAS_LABEL"
31 size="45"
32 hint="JFIELD_ALIAS_PLACEHOLDER"
33 />
34 </fieldset>
35 <fieldset name="language" label="JFIELD_LANGUAGE_LABEL">
36 <field
37 name="language"
38 type="contentlanguage"
39 label="JFIELD_LANGUAGE_LABEL"
40 >
41 <option value="*">JALL</option>
42 </field>
43 </fieldset>
44 <fieldset name="publishing" label="JGLOBAL_FIELDSET_PUBLISHING">
45 <field
46 name="featured"
47 type="list"
48 label="JFEATURED"
49 default="0"
50 validate="options"
51 >
52 <option value="0">JNO</option>
53 <option value="1">JYES</option>
54 </field>
55
56 <field
57 name="published"
58 type="list"
59 label="JSTATUS"

January 2023 Page 295


Joomla 4 - Developing Extensions 31. Frontend Editing

60 default="1"
61 id="published"
62 class="custom-select-color-state"
63 size="1"
64 >
65 <option value="1">JPUBLISHED</option>
66 <option value="0">JUNPUBLISHED</option>
67 <option value="2">JARCHIVED</option>
68 <option value="-2">JTRASHED</option>
69 </field>
70
71 <field
72 name="publish_up"
73 type="calendar"
74 label="COM_FOOS_FIELD_PUBLISH_UP_LABEL"
75 translateformat="true"
76 showtime="true"
77 size="22"
78 filter="user_utc"
79 />
80
81 <field
82 name="publish_down"
83 type="calendar"
84 label="COM_FOOS_FIELD_PUBLISH_DOWN_LABEL"
85 translateformat="true"
86 showtime="true"
87 size="22"
88 filter="user_utc"
89 />
90
91 <field
92 name="catid"
93 type="categoryedit"
94 label="JCATEGORY"
95 extension="com_foos"
96 addfieldprefix="Joomla\Component\Categories\Administrator\
Field"
97 required="true"
98 default=""
99 />
100
101 <field
102 name="access"
103 type="accesslevel"
104 label="JFIELD_ACCESS_LABEL"
105 size="1"
106 />
107
108 <field
109 name="checked_out"

Page 296 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

110 type="hidden"
111 filter="unset"
112 />
113
114 <field
115 name="checked_out_time"
116 type="hidden"
117 filter="unset"
118 />
119 </fieldset>
120 <fields name="params" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
121 <fieldset name="display" label="
JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
122 <field
123 name="show_name"
124 type="list"
125 label="COM_FOOS_FIELD_PARAMS_NAME_LABEL"
126 useglobal="true"
127 >
128 <option value="0">JHIDE</option>
129 <option value="1">JSHOW</option>
130 </field>
131
132 <field
133 name="foos_layout"
134 type="componentlayout"
135 label="JFIELD_ALT_LAYOUT_LABEL"
136 class="custom-select"
137 extension="com_foos"
138 view="foo"
139 useglobal="true"
140 />
141 </fieldset>
142 </fields>
143 </form>

31.1.1.3. components/com_foos/src/Controller/FooController.php

The file components/com_foos/src/Controller/FooController.php contains the logic for


processing in the form.

Note the function save. This is not usual in the FormController, because Joomla takes care of
everything for you. Since the ID is first created when an element is created and is therefore not
known, Joomla forwards to the overview page after creation. We have not yet created this in the
frontend. That is why I have changed this function here.

components/com_foos/src/Controller/FooController.php

January 2023 Page 297


Joomla 4 - Developing Extensions 31. Frontend Editing

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Controller;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\MVC\Controller\FormController;
10 use Joomla\CMS\Router\Route;
11 use Joomla\CMS\Uri\Uri;
12 use Joomla\Utilities\ArrayHelper;
13
14 class FooController extends FormController
15 {
16 protected $view_item = 'form';
17
18 public function getModel($name = 'form', $prefix = '', $config = ['
ignore_request' => true])
19 {
20 return parent::getModel($name, $prefix, ['ignore_request' =>
false]);
21 }
22
23 protected function allowAdd($data = [])
24 {
25 if ($categoryId = ArrayHelper::getValue($data, 'catid', $this->
input->getInt('catid'), 'int')) {
26 $user = Factory::getUser();
27
28 // If the category has been passed in the data or URL check
it.
29 return $user->authorise('core.create', 'com_foos.category.'
. $categoryId);
30 }
31
32 // In the absence of better information, revert to the
component permissions.
33 return parent::allowAdd();
34 }
35
36 protected function allowEdit($data = [], $key = 'id')
37 {
38 $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
39
40 if (!$recordId) {
41 return false;
42 }
43
44 // Need to do a lookup from the model.

Page 298 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

45 $record = $this->getModel()->getItem($recordId);
46 $categoryId = (int) $record->catid;
47
48 if ($categoryId) {
49 $user = Factory::getUser();
50
51 // The category has been set. Check the category
permissions.
52 if ($user->authorise('core.edit', $this->option . '.
category.' . $categoryId)) {
53 return true;
54 }
55
56 // Fallback on edit.own.
57 if ($user->authorise('core.edit.own', $this->option . '.
category.' . $categoryId)) {
58 return ($record->created_by == $user->id);
59 }
60
61 return false;
62 }
63
64 // Since there is no asset tracking, revert to the component
permissions.
65 return parent::allowEdit($data, $key);
66 }
67
68 public function save($key = null, $urlVar = null)
69 {
70 $result = parent::save($key, $urlVar = null);
71
72 $this->setRedirect(Route::_($this->getReturnPage(), false));
73
74 return $result;
75 }
76
77 public function cancel($key = null)
78 {
79 $result = parent::cancel($key);
80
81 $this->setRedirect(Route::_($this->getReturnPage(), false));
82
83 return $result;
84 }
85
86 protected function getRedirectToItemAppend($recordId = 0, $urlVar =
'id')
87 {
88 // Need to override the parent method completely.
89 $tmpl = $this->input->get('tmpl');
90

January 2023 Page 299


Joomla 4 - Developing Extensions 31. Frontend Editing

91 $append = '';
92
93 // Setup redirect info.
94 if ($tmpl) {
95 $append .= '&tmpl=' . $tmpl;
96 }
97
98 $append .= '&layout=edit';
99
100 $append .= '&' . $urlVar . '=' . (int) $recordId;
101
102 $itemId = $this->input->getInt('Itemid');
103 $return = $this->getReturnPage();
104 $catId = $this->input->getInt('catid');
105
106 if ($itemId) {
107 $append .= '&Itemid=' . $itemId;
108 }
109
110 if ($catId) {
111 $append .= '&catid=' . $catId;
112 }
113
114 if ($return) {
115 $append .= '&return=' . base64_encode($return);
116 }
117
118 return $append;
119 }
120
121 protected function getReturnPage()
122 {
123 $return = $this->input->get('return', null, 'base64');
124
125 if (empty($return) || !Uri::isInternal(base64_decode($return)))
{
126 return Uri::base();
127 }
128
129 return base64_decode($return);
130 }
131 }

31.1.1.4. components/com_foos/src/Model/FormModel.php

The file components/com_foos/src/Model/FormModel.php organises all the necessary data for


processing in the form.

components/com_foos/src/Model/FormModel.php

Page 300 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Model;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\Form\Form;
10 use Joomla\CMS\Language\Associations;
11 use Joomla\CMS\Language\Multilanguage;
12 use Joomla\Registry\Registry;
13 use Joomla\Utilities\ArrayHelper;
14
15 class FormModel extends \FooNamespace\Component\Foos\Administrator\
Model\FooModel
16 {
17 public $typeAlias = 'com_foos.foo';
18
19 protected $formName = 'form';
20
21 public function getForm($data = [], $loadData = true)
22 {
23 $form = parent::getForm($data, $loadData);
24
25 // Prevent messing with article language and category when
editing existing foo with associations
26 if ($id = $this->getState('foo.id') && Associations::isEnabled
()) {
27 $associations = Associations::getAssociations('com_foos', '
#__foos_details', 'com_foos.item', $id);
28
29 // Make fields read only
30 if (!empty($associations)) {
31 $form->setFieldAttribute('language', 'readonly', 'true'
);
32 $form->setFieldAttribute('language', 'filter', 'unset')
;
33 }
34 }
35
36 return $form;
37 }
38
39 public function getItem($itemId = null)
40 {
41 $itemId = (int) (!empty($itemId)) ? $itemId : $this->getState('
foo.id');
42
43 // Get a row instance.

January 2023 Page 301


Joomla 4 - Developing Extensions 31. Frontend Editing

44 $table = $this->getTable();
45
46 // Attempt to load the row.
47 try {
48 if (!$table->load($itemId)) {
49 return false;
50 }
51 } catch (Exception $e) {
52 Factory::getApplication()->enqueueMessage($e->getMessage())
;
53
54 return false;
55 }
56
57 $properties = $table->getProperties();
58 $value = ArrayHelper::toObject($properties, 'JObject');
59
60 // Convert field to Registry.
61 $value->params = new Registry($value->params);
62
63 return $value;
64 }
65
66 public function getReturnPage()
67 {
68 return base64_encode($this->getState('return_page'));
69 }
70
71 public function save($data)
72 {
73 // Associations are not edited in frontend ATM so we have to
inherit them
74 if (Associations::isEnabled() && !empty($data['id'])
75 && $associations = Associations::getAssociations('com_foos'
, '#__foos_details', 'com_foos.item', $data['id'])) {
76 foreach ($associations as $tag => $associated) {
77 $associations[$tag] = (int) $associated->id;
78 }
79
80 $data['associations'] = $associations;
81 }
82
83 return parent::save($data);
84 }
85
86 protected function populateState()
87 {
88 $app = Factory::getApplication();
89
90 // Load state from the request.
91 $pk = $app->input->getInt('id');

Page 302 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

92 $this->setState('foo.id', $pk);
93
94 $this->setState('foo.catid', $app->input->getInt('catid'));
95
96 $return = $app->input->get('return', null, 'base64');
97 $this->setState('return_page', base64_decode($return));
98
99 // Load the parameters.
100 $params = $app->getParams();
101 $this->setState('params', $params);
102
103 $this->setState('layout', $app->input->getString('layout'));
104 }
105
106 protected function preprocessForm(Form $form, $data, $group = 'foo'
)
107 {
108 if (!Multilanguage::isEnabled()) {
109 $form->setFieldAttribute('language', 'type', 'hidden');
110 $form->setFieldAttribute('language', 'default', '*');
111 }
112
113 return parent::preprocessForm($form, $data, $group);
114 }
115
116 public function getTable($name = 'Foo', $prefix = 'Administrator',
$options = [])
117 {
118 return parent::getTable($name, $prefix, $options);
119 }
120 }

31.1.1.5. components/com_foos/src/View/Form/HtmlView.php

The file components/com_foos/src/View/Form/HtmlView.php fetches all the necessary data


and passes it on to the template file edit.php.

components/com_foos/src/View/Form/HtmlView.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\View\Form;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Factory;
9 use Joomla\CMS\Language\Multilanguage;
10 use Joomla\CMS\Language\Text;

January 2023 Page 303


Joomla 4 - Developing Extensions 31. Frontend Editing

11 use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;


12 use FooNamespace\Component\Foos\Administrator\Helper\FooHelper;
13
14 class HtmlView extends BaseHtmlView
15 {
16 protected $form;
17
18 protected $item;
19
20 protected $return_page;
21
22 protected $pageclass_sfx;
23
24 protected $state;
25
26 protected $params;
27
28 public function display($tpl = null)
29 {
30 $user = Factory::getUser();
31 $app = Factory::getApplication();
32
33 // Get model data.
34 $this->state = $this->get('State');
35 $this->item = $this->get('Item');
36 $this->form = $this->get('Form');
37 $this->return_page = $this->get('ReturnPage');
38
39 if (empty($this->item->id)) {
40 $authorised = $user->authorise('core.create', 'com_foos')
|| count($user->getAuthorisedCategories('com_foos', '
core.create'));
41 } else {
42 // Since we don't track these assets at the item level, use
the category id.
43 $canDo = FooHelper::getActions('com_foos', 'category',
$this->item->catid);
44 $authorised = $canDo->get('core.edit') || ($canDo->get('
core.edit.own') && $this->item->created_by == $user->id)
;
45 }
46
47 if ($authorised !== true) {
48 $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), '
error');
49 $app->setHeader('status', 403, true);
50
51 return false;
52 }
53
54 // Check for errors.

Page 304 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

55 if (count($errors = $this->get('Errors'))) {
56 $app->enqueueMessage(implode("\n", $errors), 'error');
57
58 return false;
59 }
60
61 // Create a shortcut to the parameters.
62 $this->params = $this->state->params;
63
64 // Escape strings for HTML output
65 $this->pageclass_sfx = htmlspecialchars($this->params->get('
pageclass_sfx'));
66
67 // Override global params with foo specific params
68 $this->params->merge($this->item->params);
69
70 // Propose current language as default when creating new foo
71 if (empty($this->item->id) && Multilanguage::isEnabled()) {
72 $lang = Factory::getLanguage()->getTag();
73 $this->form->setFieldAttribute('language', 'default', $lang
);
74 }
75
76 $this->_prepareDocument();
77
78 parent::display($tpl);
79 }
80
81 protected function _prepareDocument()
82 {
83 $app = Factory::getApplication();
84 $menus = $app->getMenu();
85 $title = null;
86
87 // Because the application sets a default page title,
88 // we need to get it from the menu item itself
89 $menu = $menus->getActive();
90
91 if ($menu) {
92 $this->params->def('page_heading', $this->params->get('
page_title', $menu->title));
93 } else {
94 $this->params->def('page_heading', Text::_('
COM_FOOS_FORM_EDIT_FOO'));
95 }
96
97 $title = $this->params->def('page_title', Text::_('
COM_FOOS_FORM_EDIT_FOO'));
98
99 if ($app->get('sitename_pagetitles', 0) == 1) {
100 $title = Text::sprintf('JPAGETITLE', $app->get('sitename'),

January 2023 Page 305


Joomla 4 - Developing Extensions 31. Frontend Editing

$title);
101 } else if ($app->get('sitename_pagetitles', 0) == 2) {
102 $title = Text::sprintf('JPAGETITLE', $title, $app->get('
sitename'));
103 }
104
105 $this->document->setTitle($title);
106
107 $pathway = $app->getPathWay();
108 $pathway->addItem($title, '');
109 }
110 }

In the code example above, I have used the code in Joomla as a guide when checking the permissions.
If someone is not authorised, a message is displayed. Depending on the environment in which the
extension is programmed, it is more user-friendly to offer a login option immediately. In this case: Place
in the file components/com_foos/src/View/Form/HtmlView.php the following code excerpt

1 if ($authorised !== true) {


2 $app->redirect('index.php?option=com_users&view=login');
3 }

instead of this

1 if ($authorised !== true)


2 {
3 $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), '
error');
4 $app->setHeader('status', 403, true);
5
6 return false;
7 }

If the authorisation check fails, you are immediately redirected to the registration form.

31.1.1.6. components/com_foos/ tmpl/form/edit.php

As a template, components/com_foos/tmpl/form/edit.php ensures that the form is already dis-


played in the frontend.

components/com_foos/tmpl/form/edit.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Factory;

Page 306 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

7 use Joomla\CMS\Language\Multilanguage;
8 use Joomla\CMS\HTML\HTMLHelper;
9 use Joomla\CMS\Language\Associations;
10 use Joomla\CMS\Router\Route;
11 use Joomla\CMS\Language\Text;
12 use Joomla\CMS\Layout\LayoutHelper;
13
14 HTMLHelper::_('behavior.keepalive');
15 HTMLHelper::_('behavior.formvalidator');
16 HTMLHelper::_('script', 'com_foos/admin-foos-letter.js', ['version' =>
'auto', 'relative' => true]);
17
18 $this->tab_name = 'com-foos-form';
19 $this->ignore_fieldsets = ['details', 'item_associations', 'language'];
20 $this->useCoreUI = true;
21 ?>
22 <form action="<?php echo Route::_('index.php?option=com_foos&id=' . (
int) $this->item->id); ?>" method="post" name="adminForm" id="
adminForm" class="form-validate form-vertical">
23 <fieldset>
24 <?php echo HTMLHelper::_('uitab.startTabSet', $this->tab_name,
['active' => 'details']); ?>
25 <?php echo HTMLHelper::_('uitab.addTab', $this->tab_name, '
details', empty($this->item->id) ? Text::_('COM_FOOS_NEW_FOO
') : Text::_('COM_FOOS_EDIT_FOO')); ?>
26 <?php echo $this->form->renderField('name'); ?>
27
28 <?php if (is_null($this->item->id)) : ?>
29 <?php echo $this->form->renderField('alias'); ?>
30 <?php endif; ?>
31 <?php echo $this->form->renderFieldset('details'); ?>
32 <?php echo HTMLHelper::_('uitab.endTab'); ?>
33
34 <?php if (Multilanguage::isEnabled()) : ?>
35 <?php echo HTMLHelper::_('uitab.addTab', $this->
tab_name, 'language', Text::_('JFIELD_LANGUAGE_LABEL
')); ?>
36 <?php echo $this->form->renderField('language'); ?>
37 <?php echo HTMLHelper::_('uitab.endTab'); ?>
38 <?php else : ?>
39 <?php echo $this->form->renderField('language'); ?>
40 <?php endif; ?>
41
42 <?php echo LayoutHelper::render('joomla.edit.params', $this);
?>
43 <?php echo HTMLHelper::_('uitab.endTabSet'); ?>
44
45 <input type="hidden" name="task" value=""/>
46 <input type="hidden" name="return" value="<?php echo $this->
return_page; ?>"/>
47 <?php echo HTMLHelper::_('form.token'); ?>

January 2023 Page 307


Joomla 4 - Developing Extensions 31. Frontend Editing

48 </fieldset>
49 <div class="mb-2">
50 <button type="button" class="btn btn-primary" onclick="Joomla.
submitbutton('foo.save')">
51 <span class="fas fa-check" aria-hidden="true"></span>
52 <?php echo Text::_('JSAVE'); ?>
53 </button>
54 <button type="button" class="btn btn-danger" onclick="Joomla.
submitbutton('foo.cancel')">
55 <span class="fas fa-times-cancel" aria-hidden="true"></span
>
56 <?php echo Text::_('JCANCEL'); ?>
57 </button>
58 </div>
59 </form>

31.1.1.7. components/com_foos/ tmpl/form/edit.xml

Last but not least we need the file components/com_foos/tmpl/form/edit.xml to create the
menu item.

components/com_foos/tmpl/form/edit.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <metadata>
4 <layout title="COM_FOOS_FORM_VIEW_DEFAULT_TITLE">
5 <help
6 key="JHELP_MENUS_MENU_ITEM_FOO_CREATE"
7 />
8 <message>
9 <![CDATA[COM_FOOS_FORM_VIEW_DEFAULT_DESC]]>
10 </message>
11 </layout>
12 <fields name="params">
13
14 </fields>
15 </metadata>

31.1.2. Modified files

31.1.2.1. administrator/components/com_foos/ src/Extension/FoosComponent.php

In the file administrator/components/com_foos/src/Extension/FoosComponent.php we


register the icon. In other words, we make the icon known to Joomla.

Page 308 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

administrator/components/com_foos/src/Extension/FoosComponent.php

1 defined('JPATH_PLATFORM') or die;
2
3 +use Joomla\CMS\Application\SiteApplication;
4 use Joomla\CMS\Association\AssociationServiceInterface;
5 use Joomla\CMS\Association\AssociationServiceTrait;
6 use Joomla\CMS\Categories\CategoryServiceInterface;
7
8 use Joomla\CMS\Extension\MVCComponent;
9 use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
10 use FooNamespace\Component\Foos\Administrator\Service\HTML\
AdministratorService;
11 +use FooNamespace\Component\Foos\Administrator\Service\HTML\Icon;
12 use Psr\Container\ContainerInterface;
13 use Joomla\CMS\Helper\ContentHelper;
14
15
16 public function boot(ContainerInterface $container)
17 {
18 $this->getRegistry()->register('foosadministrator', new
AdministratorService);
19 + $this->getRegistry()->register('fooicon', new Icon($container->
get(SiteApplication::class)));
20 }

31.1.2.2. components/com_foos/ tmpl/foo/default.php

We extend the template for the view: If you are allowed to edit the element if ($canEdit), then you
see the icon to open the form.

components/com_foos/tmpl/foo/default.php

1 \defined('_JEXEC') or die;
2
3 +use Joomla\CMS\Factory;
4 +use Joomla\CMS\Helper\ContentHelper;
5 use Joomla\CMS\Language\Text;
6
7 -if ($this->item->params->get('show_name')) {
8 +$canDo = ContentHelper::getActions('com_foos', 'category', $this->
item->catid);
9 +$canEdit = $canDo->get('core.edit') || ($canDo->get('core.edit.own')
&& $this->item->created_by == Factory::getUser()->id);
10 +$tparams = $this->item->params;
11
12 +if ($tparams->get('show_name')) {
13 if ($this->params->get('show_foo_name_label')) {
14 echo Text::_('COM_FOOS_NAME');

January 2023 Page 309


Joomla 4 - Developing Extensions 31. Frontend Editing

15 }
16
17 echo $this->item->name;
18 }
19 +?>
20
21 +<?php if ($canEdit) : ?>
22 + <div class="icons">
23 + <div class="btn-group float-right">
24 + <button class="btn btn-secondary dropdown-toggle" type="
button" id="dropdownMenuButton-<?php echo $this->item->id; ?>"
25 + aria-label="<?php echo JText::_('JUSER_TOOLS'); ?>"
26 + data-toggle="dropdown" aria-haspopup="true" aria-
expanded="false">
27 + <span class="fa fa-cog" aria-hidden="true"></span>
28 + </button>
29 + <ul class="dropdown-menu" aria-labelledby="
dropdownMenuButton-<?php echo $this->item->id; ?>">
30 + <li class="edit-icon"> <?php echo JHtml::_('fooicon.
edit', $this->item, $tparams); ?> </li>
31 + </ul>
32 + </div>
33 + </div>
34 +<?php endif; ?>
35 +
36 +<?php
37 echo $this->item->event->afterDisplayTitle;
38 echo $this->item->event->beforeDisplayContent;
39 echo $this->item->event->afterDisplayContent;

Tip: Do you want a user to be redirected to the finished view of an item after it has been created?
This is only possible in a indirect way. Because you don’t know the ID when you create it, you
have to ask for it. Since we extend the model classes of Joomla-Core, we can access the ID via the
model in the postSaveHook() method of the controller. Concretely, in the file src/components
/com_foos/src/Controller/FooController.php the following code could be used to set
up the redirection:

1 ...
2 protected function postSaveHook(\Joomla\CMS\MVC\Model\BaseDatabaseModel
$model, $validData = [])
3 {
4 $id = $model->getState($model->getName() . '.id');
5 $this->setRedirect(Route::_('index.php?option=com_agosms&view=foo&
id=' . $id, false));
6 return $id;
7 }
8 ...

Page 310 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

31.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.
Copy the files in the components folder into the components folder of your Joomla 4 installation.

Install your component as described in part one, after you have copied all the files. Joomla will update
the namespaces for you during the installation. Since a new file has been added, this is necessary.

2. Create a menu item to change a Foo element and one that displays a Foo element.

Figure 31.1.: Joomla Frontend Editing Menu Item to Create a Foo Element

3. Open the menu item to create a Foo element in the frontend. Make sure you have the necessary
rights. If you have left the default rights, you must log in with a user who is at least an author.
Make sure that you can create an element.

January 2023 Page 311


Joomla 4 - Developing Extensions 31. Frontend Editing

Figure 31.2.: Joomla Frontend Editing - Creating a Foo Element

4. Make sure that you see the edit icon in the detail view of an element and that an element is
editable.

Figure 31.3.: Joomla Frontend Editing - Editing a Foo Element

Page 312 January 2023


31. Frontend Editing Joomla 4 - Developing Extensions

31.3. Links

Adding frontend edit for com_contact1

1
github.com/joomla/joomla-cms/pull/24311

January 2023 Page 313


Joomla 4 - Developing Extensions 32. View by Categories

32. View by Categories

Why use categories? Categories are often used when there are many posts on a site. With the help
of categories, they can be grouped and managed more easily. Example: In the component content,
articles can be filtered by category. If there are 200 articles on the site, it is easier to find a post if you
know its category.

For the frontend, there are built-in menu item types in Joomla that use categories: Category Blog and
Category List. The menu item types or layouts simplify the display of posts in a category. When a new
post is assigned to the category, it automatically appears on the page. This display is configurable. For
example, imagine a blog layout of the “Events” category that displays the latest articles first on the site.
When a new article is added to this category, it will automatically appear at the top of the Events blog.
All you have to do is add the post to the category. The category structure, for example “Events | Online
Events | Sports | Yoga”, is completely independent of the site’s menu structure. The site can have one
or six menu levels and Yoga can be placed as a menu item in the first level.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t25...t26

32.1. Step by step

Categories1 are a way to organize content in Joomla. A category contains items and other categories.
An item can belong to only one category. If a category is contained in another, it is a subcategory of
that category. Does it happen in your structure that single elements belong to several subsets? Then
categories are not the right choice. In this case use tags.

1
docs.joomla.org/category

Page 315 January 2023


Joomla 4 - Developing Extensions 32. View by Categories

32.1.1. New files

32.1.1.1. components/com_foos/src/Model/CategoryModel.php

The class we use to prepare the data for displaying the category view extends the ListModel class
in the /libraries/src/MVC/Model/ListModel.php file, as does the FeaturedModel class in
components/com_foos/src/Model/FeaturedModel.php. ListModel provides, among other
things, the ability to handle the display of multiple items simultaneously on a web page, including
support for pagination. Below I include my full code, which is derived from com_contact.

components/com_foos/src/Model/CategoryModel.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Model;
5
6 defined('_JEXEC') or die;
7
8 use Joomla\CMS\Categories\Categories;
9 use Joomla\CMS\Categories\CategoryNode;
10 use Joomla\CMS\Component\ComponentHelper;
11 use Joomla\CMS\Factory;
12 use Joomla\CMS\Helper\TagsHelper;
13 use Joomla\CMS\Language\Multilanguage;
14 use Joomla\CMS\MVC\Model\ListModel;
15 use Joomla\CMS\Table\Table;
16 use Joomla\Database\ParameterType;
17 use Joomla\Registry\Registry;
18
19 class CategoryModel extends ListModel
20 {
21 protected $_item = null;
22
23 protected $_articles = null;
24
25 protected $_siblings = null;
26
27 protected $_children = null;
28
29 protected $_parent = null;
30
31 protected $_category = null;
32
33 protected $_categories = null;
34
35 public function __construct($config = [])
36 {
37 if (empty($config['filter_fields'])) {

Page 316 January 2023


32. View by Categories Joomla 4 - Developing Extensions

38 $config['filter_fields'] = [
39 'id', 'a.id',
40 'name', 'a.name',
41 'state', 'a.state',
42 'ordering', 'a.ordering',
43 'featuredordering', 'a.featured'
44 ];
45 }
46
47 parent::__construct($config);
48 }
49
50 public function getItems()
51 {
52 // Invoke the parent getItems method to get the main list
53 $items = parent::getItems();
54
55 if ($items === false) {
56 return false;
57 }
58
59 // Convert the params field into an object, saving original in
_params
60 for ($i = 0, $n = count($items); $i < $n; $i++) {
61 $item = &$items[$i];
62
63 if (!isset($this->_params)) {
64 $item->params = new Registry($item->params);
65 }
66
67 // Some contexts may not use tags data at all, so we allow
callers to disable loading tag data
68 if ($this->getState('load_tags', true)) {
69 $this->tags = new TagsHelper;
70 $this->tags->getItemTags('com_foos.foo', $item->id);
71 }
72 }
73
74 return $items;
75 }
76
77 protected function getListQuery()
78 {
79 $user = Factory::getUser();
80 $groups = $user->getAuthorisedViewLevels();
81
82 // Create a new query object.
83 $db = $this->getDbo();
84 $query = $db->getQuery(true);
85
86 $query->select($this->getState('list.select', 'a.*'))

January 2023 Page 317


Joomla 4 - Developing Extensions 32. View by Categories

87 ->select($this->getSlugColumn($query, 'a.id', 'a.alias') .


' AS slug')
88 ->select($this->getSlugColumn($query, 'c.id', 'c.alias') .
' AS catslug')
89 ->from($db->quoteName('#__foos_details', 'a'))
90 ->leftJoin($db->quoteName('#__categories', 'c') . ' ON c.id
= a.catid')
91 ->whereIn($db->quoteName('a.access'), $groups);
92
93 // Filter by category.
94 if ($categoryId = $this->getState('category.id')) {
95 $query->where($db->quoteName('a.catid') . ' = :acatid')
96 ->whereIn($db->quoteName('c.access'), $groups);
97 $query->bind(':acatid', $categoryId, ParameterType::INTEGER
);
98 }
99
100 // Filter by state
101 $state = $this->getState('filter.published');
102
103 if (is_numeric($state)) {
104 $query->where($db->quoteName('a.published') . ' = :
published');
105 $query->bind(':published', $state, ParameterType::INTEGER);
106 } else {
107 $query->whereIn($db->quoteName('c.published'), [0,1,2]);
108 }
109
110 // Filter by start and end dates.
111 $nowDate = Factory::getDate()->toSql();
112
113 if ($this->getState('filter.publish_date')) {
114 $query->where('(' . $db->quoteName('a.publish_up')
115 . ' IS NULL OR ' . $db->quoteName('a.publish_up') . '
<= :publish_up)')
116 ->where('(' . $db->quoteName('a.publish_down')
117 . ' IS NULL OR ' . $db->quoteName('a.publish_down')
. ' >= :publish_down)')
118 ->bind(':publish_up', $nowDate)
119 ->bind(':publish_down', $nowDate);
120 }
121
122 // Filter by search in title
123 $search = $this->getState('list.filter');
124
125 if (!empty($search)) {
126 $search = '%' . trim($search) . '%';
127 $query->where($db->quoteName('a.name') . ' LIKE :name ');
128 $query->bind(':name', $search);
129 }
130

Page 318 January 2023


32. View by Categories Joomla 4 - Developing Extensions

131 // Filter on the language.


132 if ($language = $this->getState('filter.language')) {
133 $language = [Factory::getLanguage()->getTag(), '*'];
134 $query->whereIn($db->quoteName('a.language'), $language);
135 }
136
137 // Set sortname ordering if selected
138 if ($this->getState('list.ordering') === 'sortname') {
139 $query->order($db->escape('a.sortname1') . ' ' . $db->
escape($this->getState('list.direction', 'ASC')))
140 ->order($db->escape('a.sortname2') . ' ' . $db->escape(
$this->getState('list.direction', 'ASC')))
141 ->order($db->escape('a.sortname3') . ' ' . $db->escape(
$this->getState('list.direction', 'ASC')));
142 } else if ($this->getState('list.ordering') === '
featuredordering') {
143 $query->order($db->escape('a.featured') . ' DESC')
144 ->order($db->escape('a.ordering') . ' ASC');
145 } else {
146 $query->order($db->escape($this->getState('list.ordering',
'a.ordering')) . ' ' . $db->escape($this->getState('list
.direction', 'ASC')));
147 }
148
149 return $query;
150 }
151
152 protected function populateState($ordering = null, $direction =
null)
153 {
154 $app = Factory::getApplication();
155 $params = ComponentHelper::getParams('com_foos');
156
157 // Get list ordering default from the parameters
158 if ($menu = $app->getMenu()->getActive()) {
159 $menuParams = $menu->getParams();
160 } else {
161 $menuParams = new Registry;
162 }
163
164 $mergedParams = clone $params;
165 $mergedParams->merge($menuParams);
166
167 // List state information
168 $format = $app->input->getWord('format');
169
170 $numberOfFoosToDisplay = $mergedParams->get('foos_display_num')
;
171
172 if ($format === 'feed') {
173 $limit = $app->get('feed_limit');

January 2023 Page 319


Joomla 4 - Developing Extensions 32. View by Categories

174 } else if (isset($numberOfFoosToDisplay)) {


175 $limit = $numberOfFoosToDisplay;
176 } else {
177 $limit = $app->getUserStateFromRequest('global.list.limit',
'limit', $app->get('list_limit'), 'uint');
178 }
179
180 $this->setState('list.limit', $limit);
181
182 $limitstart = $app->input->get('limitstart', 0, 'uint');
183 $this->setState('list.start', $limitstart);
184
185 // Optional filter text
186 $itemid = $app->input->get('Itemid', 0, 'int');
187 $search = $app->getUserStateFromRequest('com_foos.category.list
.' . $itemid . '.filter-search', 'filter-search', '', '
string');
188 $this->setState('list.filter', $search);
189
190 $orderCol = $app->input->get('filter_order', $mergedParams->get
('initial_sort', 'ordering'));
191
192 if (!in_array($orderCol, $this->filter_fields)) {
193 $orderCol = 'ordering';
194 }
195
196 $this->setState('list.ordering', $orderCol);
197
198 $listOrder = $app->input->get('filter_order_Dir', 'ASC');
199
200 if (!in_array(strtoupper($listOrder), ['ASC', 'DESC', ''])) {
201 $listOrder = 'ASC';
202 }
203
204 $this->setState('list.direction', $listOrder);
205
206 $id = $app->input->get('id', 0, 'int');
207 $this->setState('category.id', $id);
208
209 $user = Factory::getUser();
210
211 if ((!$user->authorise('core.edit.state', 'com_foos')) && (!
$user->authorise('core.edit', 'com_foos'))) {
212 // Limit to published for people who can't edit or edit.
state.
213 $this->setState('filter.published', 1);
214
215 // Filter by start and end dates.
216 $this->setState('filter.publish_date', true);
217 }
218

Page 320 January 2023


32. View by Categories Joomla 4 - Developing Extensions

219 $this->setState('filter.language', Multilanguage::isEnabled());


220
221 // Load the parameters.
222 $this->setState('params', $params);
223 }
224
225 public function getCategory()
226 {
227 if (!is_object($this->_item)) {
228 $app = Factory::getApplication();
229 $menu = $app->getMenu();
230 $active = $menu->getActive();
231
232 if ($active) {
233 $params = $active->getParams();
234 } else {
235 $params = new Registry;
236 }
237
238 $options = [];
239 $options['countItems'] = $params->get('show_cat_items', 1)
|| $params->get('show_empty_categories', 0);
240 $categories = Categories::getInstance('Foos', $options);
241 $this->_item = $categories->get($this->getState('category.
id', 'root'));
242
243 if (is_object($this->_item)) {
244 $this->_children = $this->_item->getChildren();
245 $this->_parent = false;
246
247 if ($this->_item->getParent()) {
248 $this->_parent = $this->_item->getParent();
249 }
250
251 $this->_rightsibling = $this->_item->getSibling();
252 $this->_leftsibling = $this->_item->getSibling(false);
253 } else {
254 $this->_children = false;
255 $this->_parent = false;
256 }
257 }
258
259 return $this->_item;
260 }
261
262 public function getParent()
263 {
264 if (!is_object($this->_item)) {
265 $this->getCategory();
266 }
267

January 2023 Page 321


Joomla 4 - Developing Extensions 32. View by Categories

268 return $this->_parent;


269 }
270
271 public function &getLeftSibling()
272 {
273 if (!is_object($this->_item)) {
274 $this->getCategory();
275 }
276
277 return $this->_leftsibling;
278 }
279
280 public function &getRightSibling()
281 {
282 if (!is_object($this->_item)) {
283 $this->getCategory();
284 }
285
286 return $this->_rightsibling;
287 }
288
289 public function &getChildren()
290 {
291 if (!is_object($this->_item)) {
292 $this->getCategory();
293 }
294
295 return $this->_children;
296 }
297
298 private function getSlugColumn($query, $id, $alias)
299 {
300 return 'CASE WHEN '
301 . $query->charLength($alias, '!=', '0')
302 . ' THEN '
303 . $query->concatenate([$query->castAsChar($id), $alias], ':
')
304 . ' ELSE '
305 . $query->castAsChar($id) . ' END';
306 }
307
308 public function hit($pk = 0)
309 {
310 $input = Factory::getApplication()->input;
311 $hitcount = $input->getInt('hitcount', 1);
312
313 if ($hitcount) {
314 $pk = (!empty($pk)) ? $pk : (int) $this->getState('category
.id');
315
316 $table = Table::getInstance('Category');

Page 322 January 2023


32. View by Categories Joomla 4 - Developing Extensions

317 $table->load($pk);
318 $table->hit($pk);
319 }
320
321 return true;
322 }
323 }

32.1.1.2. components/com_foos/src/Service/Category.php

In the Category service for the frontend part we set the specific options for our component.

components/com_foos/src/Service/Category.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Service;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Categories\Categories;
9
10 class Category extends Categories
11 {
12 public function __construct($options = [])
13 {
14 $options['table'] = '#__foos_details';
15 $options['extension'] = 'com_foos';
16 $options['statefield'] = 'published';
17
18 parent::__construct($options);
19 }
20 }

32.1.1.3. components/com_foos/src/View/Category/HtmlView.php

We handle the category view in the frontend via the file components/com_foos/src/View/
Category/HtmlView.php.

components/com_foos/src/View/Category/HtmlView.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\View\Category;
5

January 2023 Page 323


Joomla 4 - Developing Extensions 32. View by Categories

6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\MVC\View\CategoryView;
9 use FooNamespace\Component\Foos\Site\Helper\RouteHelper;
10
11 class HtmlView extends CategoryView
12 {
13 protected $extension = 'com_foos';
14
15 protected $defaultPageTitle = 'COM_FOO_DEFAULT_PAGE_TITLE';
16
17 protected $viewName = 'foo';
18
19 protected $runPlugins = true;
20
21 public function display($tpl = null)
22 {
23 parent::commonCategoryDisplay();
24
25 $this->pagination->hideEmptyLimitstart = true;
26
27 foreach ($this->items as $item) {
28 $item->slug = $item->id;
29 $temp = $item->params;
30 $item->params = clone $this->params;
31 $item->params->merge($temp);
32 }
33
34 return parent::display($tpl);
35 }
36
37 protected function prepareDocument()
38 {
39 parent::prepareDocument();
40
41 $menu = $this->menu;
42 $id = (int) @$menu->query['id'];
43
44 if ($menu && (!isset($menu->query['option']) || $menu->query['
option'] != $this->extension || $menu->query['view'] ==
$this->viewName
45 || $id != $this->category->id)) {
46 $path = [['title' => $this->category->title, 'link' => ''
]];
47 $category = $this->category->getParent();
48
49 while ((!isset($menu->query['option']) || $menu->query['
option'] !== 'com_foos' || $menu->query['view'] === 'foo
'
50 || $id != $category->id) && $category->id > 1) {
51 $path[] = ['title' => $category->title, 'link' =>

Page 324 January 2023


32. View by Categories Joomla 4 - Developing Extensions

RouteHelper::getCategoryRoute($category->id,
$category->language)];
52 $category = $category->getParent();
53 }
54
55 $path = array_reverse($path);
56
57 foreach ($path as $item) {
58 $this->pathway->addItem($item['title'], $item['link']);
59 }
60 }
61
62 parent::addFeed();
63 }
64 }

32.1.1.4. components/com_foos/tmpl/category/default.php

That we also create a template for the category view is not new. As usual we create the file
default.php in the directory components/com_foos/tmpl/category. We use joomla.
content.category_default here. You can find this layout file in the folder layouts/joomla/
content/category_default.php.

components/com_foos/tmpl/category/default.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Layout\LayoutHelper;
7 ?>
8
9 <div class="com-foo-category">
10 <?php
11 $this->subtemplatename = 'items';
12 echo LayoutHelper::render('joomla.content.category_default',
$this);
13 ?>
14 </div>

32.1.1.5. components/com_foos/tmpl/category/default.xml

Um im Backend auf benutzerfreundliche Art und Weise einen Menüpunkt für die Navigation im Frontend
anlegen zu können, erstellen wir die Datei components/com_foos/tmpl/category/default.xml.

January 2023 Page 325


Joomla 4 - Developing Extensions 32. View by Categories

Das haben wir hier im Text vorher schon öfter erledigt. Beispielsweise für ein Element oder für die
Ansicht der Haupteinträge (featured).

components/com_foos/tmpl/category/default.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <metadata>
4 <layout title="COM_FOOS_CATEGORY_VIEW_DEFAULT_TITLE">
5 <help
6 key = "JHELP_MENUS_MENU_ITEM_FOO_CATEGORY"
7 />
8 <message>
9 <![CDATA[COM_FOOS_CATEGORY_VIEW_DEFAULT_DESC]]>
10 </message>
11 </layout>
12
13 <!-- Add fields to the request variables for the layout. -->
14 <fields name="request">
15 <fieldset
16 name="request"
17 addfieldprefix="Joomla\Component\Categories\Administrator\
Field"
18 >
19 <field
20 name="id"
21 type="modal_category"
22 label="JGLOBAL_CHOOSE_CATEGORY_LABEL"
23 extension="com_foos"
24 required="true"
25 select="true"
26 new="true"
27 edit="true"
28 clear="true"
29 />
30 </fieldset>
31 </fields>
32 <fields name="params">
33 <fieldset name="basic" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS"
>
34 <field
35 name="show_pagination"
36 type="list"
37 label="JGLOBAL_PAGINATION_LABEL"
38 useglobal="true"
39 >
40 <option value="0">JHIDE</option>
41 <option value="1">JSHOW</option>
42 <option value="2">JGLOBAL_AUTO</option>
43 </field>
44

Page 326 January 2023


32. View by Categories Joomla 4 - Developing Extensions

45 <field
46 name="show_pagination_results"
47 type="list"
48 label="JGLOBAL_PAGINATION_RESULTS_LABEL"
49 useglobal="true"
50 class="custom-select-color-state"
51 >
52 <option value="0">JHIDE</option>
53 <option value="1">JSHOW</option>
54 </field>
55 </fieldset>
56 </fields>
57 </metadata>

The category views in Joomla usually have a lot of other parameters. For example, I have ignored
the subcategories and filters. This keeps the example clear. Look up in the core extensions what is
important to you .

If your element is not displayed, it may be because you have set the parameter show_name to no
for the element.

32.1.1.6. components/com_foos/tmpl/category/default_items.php

To make the category view code clear, we work with layouts. In the template components/com_foos
/tmpl/category/default.php we use the layout joomla.content.category_default. This
in turn requires the items layout, which we implement in the file components/com_foos/tmpl/
category/default_items.php. At first glance, this seems cumbersome. In practice, however, it
has proven its worth.

components/com_foos/tmpl/category/default_items.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\HTML\HTMLHelper;
7 use Joomla\CMS\Language\Text;
8 use Joomla\CMS\Router\Route;
9 use Joomla\CMS\Uri\Uri;
10 use FooNamespace\Component\Foos\Site\Helper\RouteHelper;
11
12 HTMLHelper::_('behavior.core');
13 ?>
14 <div class="com-foo-category__items">

January 2023 Page 327


Joomla 4 - Developing Extensions 32. View by Categories

15 <form action="<?php echo htmlspecialchars(Uri::getInstance()->


toString()); ?>" method="post" name="adminForm" id="adminForm">
16 <?php if (empty($this->items)) : ?>
17 <p>
18 <?php echo Text::_('JGLOBAL_SELECT_NO_RESULTS_MATCH');
?>
19 </p>
20 <?php else : ?>
21 <ul class="com-foo-category__list category">
22 <?php foreach ($this->items as $i => $item) : ?>
23 <?php if (in_array($item->access, $this->user->
getAuthorisedViewLevels())) : ?>
24 <li class="row cat-list-row" >
25
26 <div class="list-title">
27 <a href="<?php echo Route::_(RouteHelper::
getFooRoute($item->slug, $item->catid,
$item->language)); ?>">
28 <?php echo $item->name; ?></a>
29 <?php echo $item->event->afterDisplayTitle;
?>
30
31 <?php echo $item->event->
beforeDisplayContent; ?>
32 </div>
33
34 <?php echo $item->event->afterDisplayContent;
?>
35 </li>
36 <?php endif; ?>
37 <?php endforeach; ?>
38 </ul>
39 <?php endif; ?>
40
41 <?php if ($this->params->get('show_pagination', 2)) : ?>
42 <div class="com-foo-category__counter">
43 <?php if ($this->params->def('show_pagination_results',
1)) : ?>
44 <p class="counter">
45 <?php echo $this->pagination->getPagesCounter()
; ?>
46 </p>
47 <?php endif; ?>
48
49 <?php echo $this->pagination->getPagesLinks(); ?>
50 </div>
51 <?php endif; ?>
52 </form>
53 </div>

Page 328 January 2023


32. View by Categories Joomla 4 - Developing Extensions

The view in this chapter is not styled. Since this is a matter of taste - and in my opinion a task
of the template - anyway, I leave the styling to you. I am of the opinion that the layouts of the
categories do not respect the separation of model, view and controller. That’s why discussions
like the one in Issue 32012a keep coming up. Again and again it has to be decided whether the
insertion of a CSS class in the output of a component brings too much dependency and belongs
only in the template - or whether only in this way a user-friendly offer is possible - where the
number of intro articles can be determined in the backend via a user interface.
a
github.com/joomla/joomla-cms/issues/32012

32.1.2. Modified files

In this chapter we only add new files.

32.2. Test your Joomla component

1. install your component in Joomla version 4 to test it: Copy the files in the administrator
folder into the administrator folder of your Joomla 4 installation. Install your component as
described in part one, after copying all files. Joomla will update the namespaces for you during
the installation. Since new files have been added, this is necessary.

2. Create a menu item that displays the elements of a category of our extension.

Figure 32.1.: Categories in Joomla - Create Menu Item

January 2023 Page 329


Joomla 4 - Developing Extensions 32. View by Categories

3. switch to the frontend and make sure that the elements are displayed correctly.

Figure 32.2.: Categories in Joomla - View in Frontend

Figure 32.3.: Categories in Joomla - View in Frontend

Page 330 January 2023


Joomla 4 - Developing Extensions 33. Add a Service - Routing

33. Add a Service - Routing

Search engine friendly URLs do not work yet. The URL to the items of the component will appear
in the form JOOMLA/category?view=category&id=8. We use a service to repair this fault. At the
same time, this is a good example to work out what is necessary to integrate a service in a Joomla
extension.

Search Engine Friendly (SEF), human readable1 are URLs that make sense to both humans and search
engines because they explain the path to the specific page. Joomla is able to create URLs in any format.
This does not depend on URL rewriting performed by the web server, so it will work even if Joomla
uses a server other than Apache with the mod_rewrite module. The SEF URLs follow a certain fixed
pattern, but the user can define a short descriptive text alias2 for each segment of the URL.

Internally, the local part of a SEF URL (the part after the domain name) is called the route. The
creation and processing of SEF URLs is therefore called routing, and the corresponding code is
called router.

An example of routing is the URL to the article “Welcome to Joomla” in the sample data. Without
SEF URLs switched on, the URL is /index.php?option=com_content&view=article&id=1:
welcome-to-joomla&catid=1:latest-news&Itemid=50. With SEF-URLs switched on and
mod_rewrite switched off, it is /index.php/the-news/1-latest-news/1-welcome-to-joomla.
With SEF URLs and mod_rewrite turned on, it is /the-news/1-latest-news/1-welcome-to-
joomla.

Search Engine Friendly URLs can be enabled by turning on the Search Engine Friendly URLs option
in the Global configuration. This option is activated by default in Joomla 4. For more information,
see Enabling Search Engine Friendly (SEF) URLs in the documentationa .
a
docs.joomla.org/Enabling_Search_Engine_Friendly_(SEF)_URLs

For impatient people: View the changed program code in the Diff Viewa and copy these changes

1
en.wikipedia.org/wiki/Clean_URL
2
docs.joomla.org/Alias

Page 331 January 2023


Joomla 4 - Developing Extensions 33. Add a Service - Routing

into your development version.


a
codeberg.org/astrid/j4examplecode/compare/t26...t27

33.1. Step by step

33.1.1. New files

33.1.1.1. components/com_foos/src/Service/Router.php

The service components/com_foos/src/Service/Router.php does the actual work and converts


the URLs into search engine friendly versions.

components/com_foos/src/Service/Router.php

1
2 <?php
3
4 namespace FooNamespace\Component\Foos\Site\Service;
5
6 \defined('_JEXEC') or die;
7
8 use Joomla\CMS\Application\SiteApplication;
9 use Joomla\CMS\Categories\CategoryFactoryInterface;
10 use Joomla\CMS\Categories\CategoryInterface;
11 use Joomla\CMS\Component\ComponentHelper;
12 use Joomla\CMS\Component\Router\RouterView;
13 use Joomla\CMS\Component\Router\RouterViewConfiguration;
14 use Joomla\CMS\Component\Router\Rules\MenuRules;
15 use Joomla\CMS\Component\Router\Rules\NomenuRules;
16 use Joomla\CMS\Component\Router\Rules\StandardRules;
17 use Joomla\CMS\Menu\AbstractMenu;
18 use Joomla\Database\DatabaseInterface;
19 use Joomla\Database\ParameterType;
20
21 class Router extends RouterView
22 {
23 protected $noIDs = false;
24
25 private $categoryFactory;
26
27 private $categoryCache = [];
28
29 private $db;
30
31 public function __construct(SiteApplication $app, AbstractMenu
$menu, CategoryFactoryInterface $categoryFactory,
DatabaseInterface $db)

Page 332 January 2023


33. Add a Service - Routing Joomla 4 - Developing Extensions

32 {
33 $this->categoryFactory = $categoryFactory;
34 $this->db = $db;
35
36 $params = ComponentHelper::getParams('com_foos');
37 $this->noIDs = (bool) $params->get('sef_ids');
38 $categories = new RouterViewConfiguration('categories');
39 $categories->setKey('id');
40 $this->registerView($categories);
41 $category = new RouterViewConfiguration('category');
42 $category->setKey('id')->setParent($categories, 'catid')->
setNestable();
43 $this->registerView($category);
44 $foo = new RouterViewConfiguration('foo');
45 $foo->setKey('id')->setParent($category, 'catid');
46 $this->registerView($foo);
47 $this->registerView(new RouterViewConfiguration('featured'));
48 $form = new RouterViewConfiguration('form');
49 $form->setKey('id');
50 $this->registerView($form);
51
52 parent::__construct($app, $menu);
53
54 $this->attachRule(new MenuRules($this));
55 $this->attachRule(new StandardRules($this));
56 $this->attachRule(new NomenuRules($this));
57 }
58
59 public function getCategorySegment($id, $query)
60 {
61 $category = $this->getCategories()->get($id);
62
63 if ($category) {
64 $path = array_reverse($category->getPath(), true);
65 $path[0] = '1:root';
66
67 if ($this->noIDs) {
68 foreach ($path as &$segment) {
69 list($id, $segment) = explode(':', $segment, 2);
70 }
71 }
72
73 return $path;
74 }
75
76 return [];
77 }
78
79 public function getCategoriesSegment($id, $query)
80 {
81 return $this->getCategorySegment($id, $query);

January 2023 Page 333


Joomla 4 - Developing Extensions 33. Add a Service - Routing

82 }
83
84 public function getFooSegment($id, $query)
85 {
86 if (!strpos($id, ':')) {
87 $id = (int) $id;
88 $dbquery = $this->db->getQuery(true);
89 $dbquery->select($this->db->quoteName('alias'))
90 ->from($this->db->quoteName('#__foos_details'))
91 ->where($this->db->quoteName('id') . ' = :id')
92 ->bind(':id', $id, ParameterType::INTEGER);
93 $this->db->setQuery($dbquery);
94
95 $id .= ':' . $this->db->loadResult();
96 }
97
98 if ($this->noIDs) {
99 list($void, $segment) = explode(':', $id, 2);
100
101 return [$void => $segment];
102 }
103
104 return [(int) $id => $id];
105 }
106
107 public function getFormSegment($id, $query)
108 {
109 return $this->getFooSegment($id, $query);
110 }
111
112 public function getCategoryId($segment, $query)
113 {
114 if (isset($query['id'])) {
115 $category = $this->getCategories(['access' => false])->get(
$query['id']);
116
117 if ($category) {
118 foreach ($category->getChildren() as $child) {
119 if ($this->noIDs) {
120 if ($child->alias == $segment) {
121 return $child->id;
122 }
123 } else {
124 if ($child->id == (int) $segment) {
125 return $child->id;
126 }
127 }
128 }
129 }
130 }
131

Page 334 January 2023


33. Add a Service - Routing Joomla 4 - Developing Extensions

132 return false;


133 }
134
135 public function getCategoriesId($segment, $query)
136 {
137 return $this->getCategoryId($segment, $query);
138 }
139
140 public function getFooId($segment, $query)
141 {
142 if ($this->noIDs) {
143 $dbquery = $this->db->getQuery(true);
144 $dbquery->select($this->db->quoteName('id'))
145 ->from($this->db->quoteName('#__foos_details'))
146 ->where(
147 [
148 $this->db->quoteName('alias') . ' = :alias',
149 $this->db->quoteName('catid') . ' = :catid',
150 ]
151 )
152 ->bind(':alias', $segment)
153 ->bind(':catid', $query['id'], ParameterType::INTEGER);
154 $this->db->setQuery($dbquery);
155
156 return (int) $this->db->loadResult();
157 }
158
159 return (int) $segment;
160 }
161
162 private function getCategories(array $options = []):
CategoryInterface
163 {
164 $key = serialize($options);
165
166 if (!isset($this->categoryCache[$key])) {
167 $this->categoryCache[$key] = $this->categoryFactory->
createCategory($options);
168 }
169
170 return $this->categoryCache[$key];
171 }
172 }

33.1.2. Modified files

33.1.2.1. administrator/components/com_foos/ services/provider.php

In the service provider we register the service.

January 2023 Page 335


Joomla 4 - Developing Extensions 33. Add a Service - Routing

administrator/components/com_foos/services/provider.php

1 use FooNamespace\Component\Foos\Administrator\Extension\FoosComponent;
2 use FooNamespace\Component\Foos\Administrator\Helper\
AssociationsHelper;
3 use Joomla\CMS\Association\AssociationExtensionInterface;
4 +use Joomla\CMS\Component\Router\RouterFactoryInterface;
5 +use Joomla\CMS\Extension\Service\Provider\RouterFactory;
6
7 public function register(Container $container)
8 $container->registerServiceProvider(new CategoryFactory('\\
FooNamespace\\Component\\Foos'));
9 $container->registerServiceProvider(new MVCFactory('\\
FooNamespace\\Component\\Foos'));
10 $container->registerServiceProvider(new
ComponentDispatcherFactory('\\FooNamespace\\Component\\Foos'
));
11 + $container->registerServiceProvider(new RouterFactory('\\
FooNamespace\\Component\\Foos'));
12
13 $container->set(
14 ComponentInterface::class,
15 function (Container $container) {
16 $component->setMVCFactory($container->get(
MVCFactoryInterface::class));
17 $component->setCategoryFactory($container->get(
CategoryFactoryInterface::class));
18 $component->setAssociationExtension($container->get(
AssociationExtensionInterface::class));
19 + $component->setRouterFactory($container->get(
RouterFactoryInterface::class));
20
21 return $component;
22 }

The lines $container->registerServiceProvider (new RouterFactory('\\Joomla


\\Component\\Foos')) and $component->setRouterFactory ($container->get(
RouterFactoryInterface::class)) are added.

33.1.2.2. administrator/components/com_foos/ src/Extension/FoosComponent.php

We implement RouterServiceInterface and use RouterServiceTrait so that these files are


available.
administrator/components/com_foos/src/Extension/FoosComponent.php

1
2 use FooNamespace\Component\Foos\Administrator\Service\HTML\Icon;
3 use Psr\Container\ContainerInterface;

Page 336 January 2023


33. Add a Service - Routing Joomla 4 - Developing Extensions

4 use Joomla\CMS\Helper\ContentHelper;
5 +use Joomla\CMS\Component\Router\RouterServiceInterface;
6 +use Joomla\CMS\Component\Router\RouterServiceTrait;
7
8 -class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface,
AssociationServiceInterface
9 +class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface,
AssociationServiceInterface, RouterServiceInterface
10 {
11 use CategoryServiceTrait;
12 use AssociationServiceTrait;
13 use HTMLRegistryAwareTrait;
14 + use RouterServiceTrait;

33.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.
Copy the files in the components folder into the components folder of your Joomla 4 installation.

Install your component as described in part one, after you have copied all the files. Joomla will update
the namespaces for you during the installation. Since a new file has been added, this is necessary.

2. Activate the setting search engine friendly URLs in the global configuration.

January 2023 Page 337


Joomla 4 - Developing Extensions 33. Add a Service - Routing

Figure 33.1.: Search engine friendly URLs in the global configuration of Joomla

5. create a menu item for a foo element

Figure 33.2.: Search Engine Friendly URLs in Joomla - Create Menu Item

4. check the URLs with which the menu item is called up in the frontend. Instead of http://
localhost/ single-foo-astrid?view=foo&id=2, http://localhost/ single-foo
-astrid should appear - depending on how you named your menu items. In the case of
categories, the improvement is even more obvious.

Page 338 January 2023


33. Add a Service - Routing Joomla 4 - Developing Extensions

Note: The URL http://localhost/ single-foo-astrid?view=foo&id=2 is technically


still present and accessible.

33.3. Links

Routing in com_contact3

3
github.com/joomla/joomla-cms/pull/27693

January 2023 Page 339


Joomla 4 - Developing Extensions 34. Dependency Injection

34. Dependency Injection

Dependency Injection (DI) sounds complicated and the synonym introducing dependencies does not
really sound positive. At first glance, program code should be as flexible as possible. In other words,
not dependent on anything else. Nobody likes to be dependent. The word has a negative touch.

Complicated and negative? On closer inspection, neither applies. With the help of a practical example,
the advantages will become clear.

The explanations in this chapter are an excursus. In other words, the code described here is not
included in the final version of the boilerplate.

34.1. Dependency Injection in Joomla

34.1.1. Step 1 - Add the function displayDirection()

The initial situation: Imagine you want to make the directions for each item in your component
individually describable.

For impatient people: View the changed program code in the Diff Viewa .
a
codeberg.org/astrid/j4examplecode/compare/t27..t27a1

34.1.1.1. administrator/components/com_foos/src/Extension/FoosComponent.php

So that every direction can be managed from one place, you start the call in the file administrator
/components/com_foos/src/Extension/FoosComponent.php. This file uses a container, or
rather the interface ContainerInterface.

administrator/components/com_foos/src/Extension/FoosComponent.php

1 use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
2 use FooNamespace\Component\Foos\Administrator\Service\HTML\
AdministratorService;
3 use FooNamespace\Component\Foos\Administrator\Service\HTML\Icon;
4 +use FooNamespace\Component\Foos\Administrator\Service\HTML\Direction;

Page 341 January 2023


Joomla 4 - Developing Extensions 34. Dependency Injection

5 use Psr\Container\ContainerInterface;
6 use Joomla\CMS\Helper\ContentHelper;
7 use Joomla\CMS\Component\Router\RouterServiceInterface;
8 public function boot(ContainerInterface $container)
9 {
10 $this->getRegistry()->register('foosadministrator', new
AdministratorService);
11 $this->getRegistry()->register('fooicon', new Icon($container->
get(SiteApplication::class)));
12 + $this->getRegistry()->register('foodirection', new Direction())
;
13 }

34.1.1.2. administrator/components/com_foos/src/Service/HTML/Direction.php

We print the directions as text using the displayDirection method of the Direction class.

administrator/components/com_foos/src/Service/HTML/Direction.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc.
All rights reserved.
7 + * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
11 +
12 +\defined('_JEXEC') or die;
13 +
14 +/**
15 + * Directions Helper
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +class Direction
20 +{
21 + /**
22 + * Service constructor
23 + *
24 + * @since __DEPLOY_VERSION__
25 + */
26 + public function __construct()
27 + {
28 + }

Page 342 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

29 +
30 + /**
31 + * Method to generate a routing direction
32 + *
33 + * @return string The HTML markup for the create item link
34 + *
35 + * @since __DEPLOY_VERSION__
36 + */
37 + public function displayDirection()
38 + {
39 + return "The route description";
40 + }
41 +}

34.1.1.3. components/com_foos/tmpl/foo/default.php

The template default.php in the directory components/com_foos/tmpl/foo/ is responsible for


the actual output.

components/com_foos/tmpl/ foo/default.php

1 </div>
2 <?php endif; ?>
3
4 +<hr>
5 +<?php echo HTMLHelper::_('foodirection.displayDirection', $this->item,
$tparams); ?>
6 +<hr>
7 +
8 <?php
9 echo $this->item->event->afterDisplayTitle;
10 echo $this->item->event->beforeDisplayContent;

When you call up an item in the frontend, the text you prepared to describe the directions appears.

January 2023 Page 343


Joomla 4 - Developing Extensions 34. Dependency Injection

Figure 34.1.: Joomla 4 - Output Step 1 of the Example on Services and Dependency Injection

Now the thing is that there are different ways to describe it:

• There are digital maps that even offer routing functions.


• The description via text is possible
• You can describe the route with the help of a picture.

For some items you have a descriptive graphic that shows the location. For another item, there is no
graphic. Instead, the address can be easily found via geocoding services and displayed on a digital
map. For other items, the position can only be described by text because insider knowledge is required.
We will work on this problem in step 2.

34.1.2. Schritt 2 - Die Funktion displayDirection() für mehrere Möglichkeiten


verwenden
For impatient people: View the changed program code in the Diff Viewa .
a
codeberg.org/astrid/j4examplecode/compare/t27a1..t27a2

34.1.2.1. administrator/components/com_foos/src/Service/HTML/Direction.php

First, we prepare a class for each description type. Each class can prepare the text for the directions
separately and therefore well arranged. In this step, we next display the description for each type.

administrator/components/com_foos/src/Service/HTML/Direction.php

Page 344 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

1
2 \defined('_JEXEC') or die;
3
4 +use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\
Image;
5 +use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\
Map;
6 +use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\
Text;
7 +
8 class Direction
9 {
10 + protected $directionTool1;
11 + protected $directionTool2;
12 + protected $directionTool3;
13 +
14 class Direction
15 public function __construct()
16 {
17 + $this->directionTool1 = new Image;
18 + $this->directionTool2 = new Map;
19 + $this->directionTool3 = new Text;
20 }
21
22 public function __construct()
23 public function displayDirection()
24 {
25 - return "The route description";
26 + return
27 + $this->directionTool1->findDirection() . "<br>" .
28 + $this->directionTool2->findDirection() . "<br>" .
29 + $this->directionTool3->findDirection();
30 }
31 }

34.1.2.2. administrator/components/com_foos/src/Service/HTML/Directions/Image.php

Below you see the class that is responsible for displaying the image.

administrator/components/com_foos/src/Service/HTML/Directions/Image.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc.
All rights reserved.

January 2023 Page 345


Joomla 4 - Developing Extensions 34. Dependency Injection

7 + * @license GNU General Public License version 2 or later; see


LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\HTML\
Directions;
11 +
12 +\defined('_JEXEC') or die;
13 +
14 +/**
15 + * Content Component HTML Helper
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +class Image
20 +{
21 +
22 + /**
23 + * Service constructor
24 + *
25 + * @param CMSApplication $application The application
26 + *
27 + * @since __DEPLOY_VERSION__
28 + */
29 + public function __construct()
30 + {
31 + }
32 +
33 + /**
34 + * Method to generate a link to the create item page for the given
category
35 + *
36 + * @param object $category The category information
37 + * @param Registry $params The item parameters
38 + * @param array $attribs Optional attributes for the link
39 + *
40 + * @return string The HTML markup for the create item link
41 + *
42 + * @since __DEPLOY_VERSION__
43 + */
44 + public static function findDirection()
45 + {
46 + return "Find direction on Image.";
47 + }
48 +}

Page 346 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

34.1.2.3. administrator/components/com_foos/src/Service/HTML/Directions/Map.php

The most complex is probably the creation of the route via the digital map, which is the task of the
class “Map”.

administrator/components/com_foos/src/Service/HTML/Directions/Map.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc.
All rights reserved.
7 + * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\HTML\
Directions;
11 +
12 +\defined('_JEXEC') or die;
13 +
14 +/**
15 + * Content Component HTML Helper
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +class Map
20 +{
21 +
22 + /**
23 + * Service constructor
24 + *
25 + * @param CMSApplication $application The application
26 + *
27 + * @since __DEPLOY_VERSION__
28 + */
29 + public function __construct()
30 + {
31 + }
32 +
33 + /**
34 + * Method to generate a link to the create item page for the given
category
35 + *
36 + * @param object $category The category information
37 + * @param Registry $params The item parameters
38 + * @param array $attribs Optional attributes for the link
39 + *
40 + * @return string The HTML markup for the create item link

January 2023 Page 347


Joomla 4 - Developing Extensions 34. Dependency Injection

41 + *
42 + * @since __DEPLOY_VERSION__
43 + */
44 + public static function findDirection()
45 + {
46 + return "Find direction with a Map.";
47 + }
48 +}

34.1.2.4. administrator/components/com_foos/src/Service/HTML/Directions/Text.php

The class named “Text” prepares the textual description of the route.

administrator/components/com_foos/src/Service/HTML/Directions/Text.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc.
All rights reserved.
7 + * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\HTML\
Directions;
11 +
12 +\defined('_JEXEC') or die;
13 +
14 +/**
15 + * Content Component HTML Helper
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +class Text
20 +{
21 +
22 + /**
23 + * Service constructor
24 + *
25 + * @param CMSApplication $application The application
26 + *
27 + * @since __DEPLOY_VERSION__
28 + */
29 + public function __construct()
30 + {
31 + }

Page 348 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

32 +
33 + /**
34 + * Method to generate a link to the create item page for the given
category
35 + *
36 + * @param object $category The category information
37 + * @param Registry $params The item parameters
38 + * @param array $attribs Optional attributes for the link
39 + *
40 + * @return string The HTML markup for the create item link
41 + *
42 + * @since __DEPLOY_VERSION__
43 + */
44 + public static function findDirection()
45 + {
46 + return "Find direction via Text Explanation.";
47 + }
48 +}

When you call up an item in the frontend, the text you prepared to describe the directions appears.

Figure 34.2.: Joomla 4 - Output Step 2 of the Example on Services and Dependency Injection

The problem: At the moment, all types of directions are displayed and it is not ensured that this
description exists. Often not every type is available. Sometimes it is also the case that you want to
specify which type is displayed. Instead of making all types available, it would be better if the optimal
directions could be defined. This way it would also be possible to add or remove new types without
having to make changes to the existing code. We will look at how this is possible in step 3.

January 2023 Page 349


Joomla 4 - Developing Extensions 34. Dependency Injection

34.1.3. Step 3 - Select displayDirection() variable and type-safe

For each item, the description of the direction is possible by text, by image or by digital map. It would
be nice if the three types were equally applicable next to each other and if it was ensured that there is
at least one description. Let us look at “interfaces” and “traits” in this context.

An interface is a contract between the implementing class and the calling class. The contract ensures
that each class meets some criteria that the interface implements. We have three options. For each,
we create an interface. An interface is something like a contract that ensures minimum requirements
are met. We then implement these interfaces in the classes. By using a “trait”, we ensure that we don’t
have to rewrite the contract each time. We use standards. This way, our service works as agreed!

More information about traits a and interfaces b .


a
php.net/manual/en/language.oop5.traits.php
b
php.net/manual/en/language.oop5.interfaces.php

For impatient people: View the changed program code in the Diff Viewa .
a
codeberg.org/astrid/j4examplecode/compare/t27a2..t27a3

34.1.3.1. administrator/components/com_foos/src/Extension/FoosComponent.php

In the component class we add everything necessary for the service Direction.

administrator/components/com_foos/src/Extension/FoosComponent.php

1 use Joomla\CMS\Helper\ContentHelper;
2 use Joomla\CMS\Component\Router\RouterServiceInterface;
3 use Joomla\CMS\Component\Router\RouterServiceTrait;
4 +use FooNamespace\Component\Foos\Administrator\Service\Direction\
DirectionServiceInterface;
5 +use FooNamespace\Component\Foos\Administrator\Service\Direction\
DirectionServiceTrait;
6
7 -class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface,
AssociationServiceInterface, RouterServiceInterface
8 +class FoosComponent extends MVCComponent implements
BootableExtensionInterface, CategoryServiceInterface,
AssociationServiceInterface, RouterServiceInterface,
DirectionServiceInterface
9 {
10 use CategoryServiceTrait;
11 use AssociationServiceTrait;
12 use HTMLRegistryAwareTrait;
13 use RouterServiceTrait;

Page 350 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

14 + use DirectionServiceTrait;
15
16 public function boot(ContainerInterface $container)
17 {
18 $this->getRegistry()->register('foosadministrator', new
AdministratorService);
19 $this->getRegistry()->register('fooicon', new Icon($container->
get(SiteApplication::class)));
20 - $this->getRegistry()->register('foodirection', new Direction())
;
21 }

34.1.3.2. administrator/components/com_foos/src/Service/Direction/DirectionExtensionInterface.php

The file DirectionExtensionInterface.php contains the interface DirectionExtensionInterface


which ensures that the function findDirection() is available to all implementing classes. Put
simply, the contract is: if a class implements the interface, then it provides a solution for the containing
functions.

administrator/components/com_foos/src/Service/Direction/DirectionExtensionInterface.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.
org>
7 + * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
11 +
12 +\defined('JPATH_PLATFORM') or die;
13 +
14 +/**
15 + * Direction Extension Interface for the helper classes
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +interface DirectionExtensionInterface
20 +{
21 + /**
22 + * Method to get the direction for a given item.
23 + *
24 + * @return string Direction
25 + *

January 2023 Page 351


Joomla 4 - Developing Extensions 34. Dependency Injection

26 + * @since __DEPLOY_VERSION__
27 + */
28 + public static function findDirection();
29 +}

34.1.3.3. administrator/components/com_foos/src/Service/Direction/DirectionServiceInterface.php

DirectionServiceInterface’ is another interface. It defines which interface the service supports and
how it can be accessed. Specifically, we use DirectionExtensionInterface, which we discussed
in the previous section. We can retrieve this via getDirectionExtension.We will do the latter in a
concrete example below.

administrator/components/com_foos/src/Service/Direction/DirectionServiceInterface.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.
org>
7 + * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
11 +
12 +\defined('JPATH_PLATFORM') or die;
13 +
14 +/**
15 + * The Direction service.
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +interface DirectionServiceInterface
20 +{
21 + /**
22 + * Returns the Directions extension helper class.
23 + *
24 + * @return DirectionExtensionInterface
25 + *
26 + * @since __DEPLOY_VERSION__
27 + */
28 + public function getDirectionExtension():
DirectionExtensionInterface;
29 +}

Page 352 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

34.1.3.4. administrator/components/com_foos/src/Service/Direction/DirectionServiceTrait.php

The trait DirectionServiceTrait provides a standard implementation of the functions


getDirectionExtension and setDirectionExtension, so that our contract is for sure
fulfilled.

administrator/components/com_foos/src/Service/Direction/DirectionServiceTrait.php

1 +<?php
2 +/**
3 + * @package Joomla.Site
4 + * @subpackage com_foos
5 + *
6 + * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.
org>
7 + * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 + */
9 +
10 +namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
11 +
12 +\defined('JPATH_PLATFORM') or die;
13 +
14 +/**
15 + * Trait to implement DirectionServiceInterface
16 + *
17 + * @since __DEPLOY_VERSION__
18 + */
19 +trait DirectionServiceTrait
20 +{
21 + /**
22 + * The direction extension.
23 + *
24 + * @var DirectionExtensionInterface
25 + *
26 + * @since __DEPLOY_VERSION__
27 + */
28 + private $directionExtension = null;
29 +
30 + /**
31 + * Returns the directions extension helper class.
32 + *
33 + * @return DirectionExtensionInterface
34 + *
35 + * @since __DEPLOY_VERSION__
36 + */
37 + public function getDirectionExtension():
DirectionExtensionInterface
38 + {
39 + return $this->directionExtension;

January 2023 Page 353


Joomla 4 - Developing Extensions 34. Dependency Injection

40 + }
41 +
42 + /**
43 + * The direction extension.
44 + *
45 + * @param DirectionExtensionInterface $directionExtension The
extension
46 + *
47 + * @return void
48 + *
49 + * @since __DEPLOY_VERSION__
50 + */
51 + public function setDirectionExtension(DirectionExtensionInterface
$directionExtension)
52 + {
53 + $this->directionExtension = $directionExtension;
54 + }
55 +}

34.1.3.5. administrator/components/com_foos/src/Service/HTML/Directions/Image.php

The class Image should in any case provide the function findDirection. We achieve this by imple-
menting the interface DirectionExtensionInterface.

administrator/components/com_foos/src/Service/HTML/Directions/Image.php

1
2 -namespace FooNamespace\Component\Foos\Administrator\Service\HTML\
Directions;
3 +namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
4
5 \defined('_JEXEC') or die;
6
7 -class Image
8 +class Image implements DirectionExtensionInterface
9 {

34.1.3.6. administrator/components/com_foos/src/Service/HTML/Directions/Map.php

The class Map should also offer the function findDirection. We achieve this by also implementing
the interface DirectionExtensionInterface.

administrator/components/com_foos/src/Service/HTML/Directions/Map.php

1
2 -namespace FooNamespace\Component\Foos\Administrator\Service\HTML\
Directions;

Page 354 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

3 +namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
4
5 \defined('_JEXEC') or die;
6
7 -class Map
8 +class Map implements DirectionExtensionInterface
9 {

34.1.3.7. administrator/components/com_foos/src/Service/HTML/Directions/Text.php

Last but not least, Map shall provide the function findDirection. Therefore, this also implements
the interface DirectionExtensionInterface.

administrator/components/com_foos/src/Service/HTML/Directions/Text.php

1
2 -namespace FooNamespace\Component\Foos\Administrator\Service\HTML\
Directions;
3 +namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
4
5 \defined('_JEXEC') or die;
6
7 -class Text
8 +class Text implements DirectionExtensionInterface
9 {

34.1.3.8. administrator/components/com_foos/src/Service/HTML/Direction.php

We do not need the class administrator/components/com_foos/src/Service/HTML/


Direction.php any further. We delete it.

administrator/components/com_foos/src/Service/HTML/Direction.php

1 -<?php
2 -/**
3 - * @package Joomla.Site
4 - * @subpackage com_foos
5 - *
6 - * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc.
All rights reserved.
7 - * @license GNU General Public License version 2 or later; see
LICENSE.txt
8 - */
9 -
10 -namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
11 -
12 -\defined('_JEXEC') or die;

January 2023 Page 355


Joomla 4 - Developing Extensions 34. Dependency Injection

13 -
14 -use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\
Image;
15 -use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\
Map;
16 -use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\
Text;
17 -
18 -/**
19 - * Directions Helper
20 - *
21 - * @since __DEPLOY_VERSION__
22 - */
23 -class Direction
24 -{
25 - protected $directionTool1;
26 - protected $directionTool2;
27 - protected $directionTool3;
28 -
29 - /**
30 - * Service constructor
31 - *
32 - * @since __DEPLOY_VERSION__
33 - */
34 - public function __construct()
35 - {
36 - $this->directionTool1 = new Image;
37 - $this->directionTool2 = new Map;
38 - $this->directionTool3 = new Text;
39 - }
40 -
41 - /**
42 - * Method to generate a routing direction
43 - *
44 - * @return string The HTML markup for the direction
45 - *
46 - * @since __DEPLOY_VERSION__
47 - */
48 - public function displayDirection()
49 - {
50 - return
51 - $this->directionTool1->findDirection() . "<br>" .
52 - $this->directionTool2->findDirection() . "<br>" .
53 - $this->directionTool3->findDirection();
54 - }
55 -}

Page 356 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

34.1.3.9. components/com_foos/tmpl/foo/default.php

When displaying in the frontend, we can load the component class via $fooComponent =
Factory::getApplication()->bootComponent('com_foos') and dynamically re-set the
interface $fooComponent->setDirectionExtension(new DirectionMap) during runtime.
This way it is possible to use different implementations for the output findDirection(). To
ensure that the method findDirection() is always available, we have implemented the interface
DirectionExtensionInterface in the possible DirectionExtensions DirectionExtension.

components/com_foos/tmpl/foo/default.php

1 use Joomla\CMS\Helper\ContentHelper;
2 use Joomla\CMS\HTML\HTMLHelper;
3 use Joomla\CMS\Language\Text;
4 +use FooNamespace\Component\Foos\Administrator\Service\Direction\Map as
DirectionMap;
5 +use FooNamespace\Component\Foos\Administrator\Service\Direction\Text
as DirectionText;
6 +use FooNamespace\Component\Foos\Administrator\Service\Direction\Image
as DirectionImage;
7
8 $canDo = ContentHelper::getActions('com_foos', 'category', $this->
item->catid);
9 $canEdit = $canDo->get('core.edit') || ($canDo->get('core.edit.own')
&& $this->item->created_by == Factory::getUser()->id);
10
11 </div>
12 <?php endif; ?>
13
14 +<?php
15 + $fooComponent = Factory::getApplication()->bootComponent('com_foos'
);
16 +?>
17 +<hr>
18 +<?php
19 + $fooComponent->setDirectionExtension(new DirectionMap);
20 + echo $fooComponent->getDirectionExtension()->findDirection();
21 +?>
22 <hr>
23 -<?php echo HTMLHelper::_('foodirection.displayDirection', $this->item,
$tparams); ?>
24 +<?php
25 + $fooComponent->setDirectionExtension(new DirectionText);
26 + echo $fooComponent->getDirectionExtension()->findDirection();
27 +?>
28 +<hr>
29 +<?php
30 + $fooComponent->setDirectionExtension(new DirectionImage);
31 + echo $fooComponent->getDirectionExtension()->findDirection();

January 2023 Page 357


Joomla 4 - Developing Extensions 34. Dependency Injection

32 +?>
33 <hr>
34
35 <?php

When you call up an item in the frontend, the text you prepared to describe the directions appears. In
the example, for demonstration purposes, I still display all possible directions. In my opinion, however,
it becomes clear how uncomplicated it is to change the output at runtime or how easy it is to manipulate
it with the help of parameters. Parameters are also the subject of a chapter in this tutorial.

Figure 34.3.: Joomla 4 - Output Step 3 of the Example on Services and Dependency Injection

34.2. Why Dependency Injection?

In this section you see a simple example. It contains the basics of DI. Passing the requirements for a
class to the class via a set method, where the set method typically corresponds to the name of the
property. In our case, this is DirectionExtension: we want to set the extension that outputs the
Direction.

34.3. Why containers?

An Inversion of Control (IoC) container can help manage all parts of the application. Instead of
creating a new DirectionExtension each time, it is much easier to remember how to prepare

Page 358 January 2023


34. Dependency Injection Joomla 4 - Developing Extensions

a DirectionExtension. Since the DirectionExtension in our example does not have many de-
pendencies, the advantages of a container are hard to see. But imagine that every time you create a
DirectionExtension you have to remember to pass the dependencies like impement the interface
DirectionExtensionInterface and provide the method findDirection. With a container, it is
possible to set up everything as if it were a template and leave the creation to the application. This is
even more handy when the dependencies we inject have dependencies within their dependencies.
This can all become very complex. Examples you can find in the ../services/provider.php files,
for example in /administrator/components/com_foos/services/provider.php.

34.4. Links

Dependency injection via Wikipedia[en.wikipedia.org/wiki/dependency_injection] JAB18: Services in Joomla 4 [joomla.digital-


peak.com/images/blog/jab18_services_in_joomla_4.pdf] Implementation of the services in the compo-
nent class on Github[github.com/joomla/joomla-cms/pull/20217] Why dependency injection in Joomla 4 [github.com/joomla-
framework/di/blob/2.0-dev/docs/why-dependency-injection.md]

January 2023 Page 359


Joomla 4 - Developing Extensions 35. Dashboard

35. Dashboard

Extensive Joomla Core extensions have a dashboard in which related functions are displayed. This is
user-friendly because it provides an overview. This way, a user can orientate himself in the extension
without many clicks. In this part, we create such a dashboard for our sample component.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t27...t28

35.1. Step by step

35.1.1. New files

35.1.1.1. administrator/components/com_foos/ presets/foos.xml

In the file administrator/components/com_foos/presets/foos.xml we define what is dis-


played on the dashboard by default.

administrator/components/com_foos/presets/foos.xml

1
2 <?xml version="1.0"?>
3 <menu
4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5 xmlns="urn:joomla.org"
6 xsi:schemaLocation="urn:joomla.org menu.xsd"
7 >
8 <menuitem
9 title="COM_FOOS"
10 type="heading"
11 icon="comment"
12 class="class:comment"
13 >
14 <menuitem
15 title="COM_FOOS"
16 type="component"
17 element="com_foos"

Page 361 January 2023


Joomla 4 - Developing Extensions 35. Dashboard

18 link="index.php?option=com_foos"
19 quicktask="index.php?option=com_foos&amp;view=foo&amp;
layout=edit"
20 quicktask-title="COM_FOOS"
21 />
22
23 <menuitem
24 title="JCATEGORY"
25 type="component"
26 element="com_foos"
27 link="index.php?option=com_categories&amp;extension=
com_foos"
28 quicktask="index.php?option=com_categories&amp;view=
category&amp;layout=edit&amp;extension=com_foos"
29 quicktask-title="JCATEGORY"
30 />
31 </menuitem>
32 </menu>

35.1.2. Modified files

35.1.2.1. administrator/components/com_foos/foos.xml

We modify the XML manifest so that the sidebar in the Joomla administration template knows how to
link to the dashboard.

administrator/components/com_foos/foos.xml

1 </media>
2 <!-- Back-end files -->
3 <administration>
4 - <!-- Menu entries -->
5 - <menu view="foos">COM_FOOS</menu>
6 + <menu img="class:comment">
7 + COM_FOOS
8 + <params>
9 + <dashboard>foos</dashboard>
10 + </params>
11 + </menu>
12 <submenu>
13 + <menu link="option=com_foos">
14 + COM_FOOS
15 + <params>
16 + <menu-quicktask-title>COM_FOOS</menu-quicktask-
title>
17 + <menu-quicktask>index.php?option=com_foos&amp;view=
foo&amp;layout=edit</menu-quicktask>
18 + </params>

Page 362 January 2023


35. Dashboard Joomla 4 - Developing Extensions

19 + </menu>
20 <menu link="option=com_foos">COM_FOOS</menu>
21 - <menu link="option=com_categories&amp;extension=com_foos">
JCATEGORY</menu>
22 + <menu link="option=com_categories&amp;extension=com_foos">
23 + JCATEGORY
24 + <params>
25 + <menu-quicktask-title>JCATEGORY</menu-quicktask-
title>
26 + <menu-quicktask>index.php?option=com_categories&amp
;view=category&amp;layout=edit&amp;extension=com_foos</menu-
quicktask>
27 + </params>
28 + </menu>
29 <menu link="option=com_fields&amp;context=com_foos.foo">
JGLOBAL_FIELDS</menu>
30 <menu link="option=com_fields&amp;view=groups&amp;context=
com_foos.foo">JGLOBAL_FIELD_GROUPS</menu>
31 </submenu>
32
33 <filename>foos.xml</filename>
34 <filename>config.xml</filename>
35 <folder>forms</folder>
36 + <folder>presets</folder>
37 <folder>language</folder>
38 <folder>services</folder>
39 <folder>sql</folder>

35.1.2.2. administrator/components/com_foos/script.php

In the installation script we add the call. With this, we call a Joomla-specific function that makes our
dashboard known in the CMS.

administrator/components/com_foos/script.php

1 use Joomla\CMS\Language\Text;
2 use Joomla\CMS\Log\Log;
3 use Joomla\CMS\Table\Table;
4 +use Joomla\CMS\Installer\InstallerScript;
5
6 -class Com_FoosInstallerScript
7 +class Com_FoosInstallerScript extends InstallerScript
8 {
9 public function install($parent): bool
10 return false;
11 }
12
13 + $this->addDashboardMenu('foos', 'foos');
14 +

January 2023 Page 363


Joomla 4 - Developing Extensions 35. Dashboard

15 return true;
16 }
17
18 public function update($parent): bool
19 {
20 echo Text::_('COM_FOOS_INSTALLERSCRIPT_UPDATE');
21
22 + $this->addDashboardMenu('foo', 'foo');
23 +
24 return true;
25 }

35.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.

Install your component as described in part one, after copying all files. Joomla will update the names-
paces for you during the installation. Since a new file has been added, this is necessary. We have also
added instructions to the installation script.

2. use the dashboard in the backend.

Figure 35.1.: The Joomla Dashboard in a separate component

Page 364 January 2023


35. Dashboard Joomla 4 - Developing Extensions

35.3. Links

Allow 3rd party components to create the dashboard[github.com/joomla/joomla-cms/pull/28027] Joomla Manual [manual.joomla.org/
core-functions/Dashboard]

January 2023 Page 365


Joomla 4 - Developing Extensions 36. Tags

36. Tags

Tags or Keywords are a flexible solution to organise content in Joomla A keyword can be assigned to
many different elements of different content types. Each element can have unlimited tags.

Joomla’s tagging system is used in all core extensions. It is designed to be easily integrated into other
extensions that use standard Joomla design patterns. Using tags in a third-party extension is quite
simple. Using it in your own extension requires the modifications explained in this section.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t28...t29

36.1. Step by step

36.1.1. New files

No new files.

36.1.2. Modified files

36.1.2.1. administrator/components/com_foos/ forms/filter_foos.xml

The form through which the search tools are managed receives an entry for the keywords.

administrator/components/com_foos/forms/filter_foos.xml

1 <option value="*">JALL</option>
2 </field>
3
4 + <field
5 + name="tag"
6 + type="tag"
7 + label="JTAG"
8 + multiple="true"
9 + mode="nested"

Page 367 January 2023


Joomla 4 - Developing Extensions 36. Tags

10 + custom="false"
11 + hint="JOPTION_SELECT_TAG"
12 + onchange="this.form.submit();"
13 + />
14 </fields>
15
16 <fields name="list">

36.1.2.2. administrator/components/com_foos/ forms/foo.xml

In the XML form, we add the form field that contains the information about the tag. Since we use
Joomla Standard, we can use many ready-made functions out-of-the-box.

administrator/components/com_foos/forms/foo.xml

1 label="JFIELD_ORDERING_LABEL"
2 content_type="com_foos.foo"
3 />
4 +
5 + <field
6 + name="tags"
7 + type="tag"
8 + label="JTAG"
9 + class="advancedSelect"
10 + multiple="true"
11 + />
12 </fieldset>
13 <fields name="params" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
14 <fieldset name="display" label="
JGLOBAL_FIELDSET_DISPLAY_OPTIONS">

36.1.2.3. administrator/components/com_foos/script.php

In the installation script, we make sure that our extension is recognised as a separate content type in
Joomla.

administrator/components/com_foos/script.php

1 {
2 echo Text::_('COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT');
3
4 + $this->saveContentTypes();
5 +
6 return true;
7 }
8
9

Page 368 January 2023


36. Tags Joomla 4 - Developing Extensions

10
11 return $id;
12 }
13 +
14 + private function saveContentTypes()
15 + {
16 + $table = Table::getInstance('Contenttype', 'JTable');
17 +
18 + $table->load(['type_alias' => 'com_foos.foo']);
19 +
20 + $tablestring = '{
21 + "special": {
22 + "dbtable": "#__foos",
23 + "key": "id",
24 + "type": "FooTable",
25 + "prefix": "Joomla\\\\Component\\\\Foos\\\\Administrator
\\\\Table\\\\",
26 + "config": "array()"
27 + },
28 + "common": {
29 + "dbtable": "#__ucm_content",
30 + "key": "ucm_id",
31 + "type": "Corecontent",
32 + "prefix": "JTable",
33 + "config": "array()"
34 + }
35 + }';
36 +
37 + $fieldmapping = '{
38 + "common": {
39 + "core_content_item_id": "id",
40 + "core_title": "name",
41 + "core_state": "published",
42 + "core_alias": "alias",
43 + "core_publish_up": "publish_up",
44 + "core_publish_down": "publish_down",
45 + "core_access": "access",
46 + "core_params": "params",
47 + "core_featured": "featured",
48 + "core_language": "language",
49 + "core_ordering": "ordering",
50 + "core_catid": "catid",
51 + "asset_id": "null"
52 + },
53 + "special": {
54 + }
55 + }';
56 +
57 + $contenttype = [];
58 + $contenttype['type_id'] = ($table->type_id) ? $table->type_id :
0;

January 2023 Page 369


Joomla 4 - Developing Extensions 36. Tags

59 + $contenttype['type_title'] = 'Foos';
60 + $contenttype['type_alias'] = 'com_foos.foo';
61 + $contenttype['table'] = $tablestring;
62 + $contenttype['rules'] = '';
63 + $contenttype['router'] = 'RouteHelper::getFooRoute';
64 + $contenttype['field_mappings'] = $fieldmapping;
65 + $contenttype['content_history_options'] = '';
66 +
67 + $table->save($contenttype);
68 +
69 + return;
70 + }
71 }

36.1.2.4. administrator/components/com_foos/ src/Model/FooModel.php

In the model of the element, we insert the tags into the batch processing batch and ensure that the
associated tags are loaded.

administrator/components/com_foos/src/Model/FooModel.php

1 use Joomla\CMS\Language\LanguageHelper;
2 use Joomla\Database\ParameterType;
3 use Joomla\Utilities\ArrayHelper;
4 +use Joomla\CMS\Helper\TagsHelper;
5
6 ... class FooModel extends AdminModel
7 protected $batch_commands = [
8 'assetgroup_id' => 'batchAccess',
9 'language_id' => 'batchLanguage',
10 + 'tag' => 'batchTag',
11 'user_id' => 'batchUser',
12 ];
13
14 ... public function getItem($pk = null)
15 }
16 }
17
18 + // Load item tags
19 + if (!empty($item->id)) {
20 + $item->tags = new TagsHelper;
21 + $item->tags->getTagIds($item->id, 'com_foos.foo');
22 + }
23 +
24 return $item;
25 }

Page 370 January 2023


36. Tags Joomla 4 - Developing Extensions

36.1.2.5. administrator/components/com_foos/ src/Model/FoosModel.php

We change the model of the overview list of our extension in the backend regarding the filters and the
database query.

administrator/components/com_foos/src/Model/FoosModel.php

1 use Joomla\CMS\Language\Associations;
2 use Joomla\CMS\Factory;
3 use Joomla\Utilities\ArrayHelper;
4 +use Joomla\Database\ParameterType;
5
6 ... protected function getListQuery()
7 $query->where($db->quoteName('a.language') . ' = ' . $db->
quote($language));
8 }
9
10 + // Filter by a single or group of tags.
11 + $tag = $this->getState('filter.tag');
12 +
13 + // Run simplified query when filtering by one tag.
14 + if (\is_array($tag) && \count($tag) === 1) {
15 + $tag = $tag[0];
16 + }
17 +
18 + if ($tag && \is_array($tag)) {
19 + $tag = ArrayHelper::toInteger($tag);
20 +
21 + $subQuery = $db->getQuery(true)
22 + ->select('DISTINCT ' . $db->quoteName('content_item_id'
))
23 + ->from($db->quoteName('#__contentitem_tag_map'))
24 + ->where(
25 + [
26 + $db->quoteName('tag_id') . ' IN (' . implode(',
', $query->bindArray($tag)) . ')',
27 + $db->quoteName('type_alias') . ' = ' . $db->
quote('com_foos.foo'),
28 + ]
29 + );
30 +
31 + $query->join(
32 + 'INNER',
33 + '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
34 + $db->quoteName('tagmap.content_item_id') . ' = ' . $db
->quoteName('a.id')
35 + );
36 + } else if ($tag = (int) $tag) {
37 + $query->join(
38 + 'INNER',
39 + $db->quoteName('#__contentitem_tag_map', 'tagmap'),

January 2023 Page 371


Joomla 4 - Developing Extensions 36. Tags

40 + $db->quoteName('tagmap.content_item_id') . ' = ' . $db


->quoteName('a.id')
41 + )
42 + ->where(
43 + [
44 + $db->quoteName('tagmap.tag_id') . ' = :tag',
45 + $db->quoteName('tagmap.type_alias') . ' = ' .
$db->quote('com_foos.foo'),
46 + ]
47 + )
48 + ->bind(':tag', $tag, ParameterType::INTEGER);
49 + }
50 +
51 // Filter by access level.
52 if ($access = $this->getState('filter.access')) {
53 $query->where($db->quoteName('a.access') . ' = ' . (int)
$access);

36.1.2.6. administrator/components/com_foos/ src/View/Foo/HtmlView.php

In the view, we ensure that the keywords matching the language are loaded.

administrator/components/com_foos/src/View/Foo/HtmlView.php

1
2 // Only allow to select categories with All language or
with the forced language.
3 $this->form->setFieldAttribute('catid', 'language', '*,' .
$forcedLanguage);
4 +
5 + // Only allow to select tags with All language or with the
forced language.
6 + $this->form->setFieldAttribute('tags', 'language', '*,' .
$forcedLanguage);
7 }
8
9 $this->addToolbar();

36.1.2.7. administrator/components/com_foos/ tmpl/foos/default_batch_body.php

So that the batch processing can also be used for the tags, we insert a form field. With the help of this
field it is possible to select a keyword that will be assigned to all selected items.

administrator/components/com_foos/tmpl/foos/default_batch_body.php

1 </div>
2 </div>

Page 372 January 2023


36. Tags Joomla 4 - Developing Extensions

3 <?php endif; ?>


4 + <div class="form-group col-md-6">
5 + <div class="controls">
6 + <?php echo LayoutHelper::render('joomla.html.batch.tag'
, []); ?>
7 + </div>
8 + </div>
9 </div>
10 </div>

36.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.

2. install your component as described in part one, after copying all files. As we have changed
things in the installation script, this is necessary.

3. create a tag using the keyword component.

Figure 36.1.: Create a tag in Joomla 4

4. set the just created keyword at a Foo element.

January 2023 Page 373


Joomla 4 - Developing Extensions 36. Tags

Figure 36.2.: A keyword in a custom Joomla 4 extension

5. convince yourself that filtering by keywords works.

Figure 36.3.: A keyword filtering in the list view of a Joomla 4 extension

6. create a menu item that shows all elements that are assigned to a certain keyword and see the
display in the frontend.

Page 374 January 2023


36. Tags Joomla 4 - Developing Extensions

Figure 36.4.: A keyword in a custom Joomla 4 extension via menu item

Figure 36.5.: A keyword in a custom Joomla 4 extension in a frontend view

If you have tagged a Foo element and are now surprised that it is not displayed, first check whether the
Foo element is published. Only published elements are displayed in the frontend.

7. Create a new tag and assign it to several Foo items by batch processing.

January 2023 Page 375


Joomla 4 - Developing Extensions 36. Tags

Figure 36.6.: Assign a keyword in a custom Joomla 4 extension by batch processing - open batch
processing

Figure 36.7.: Assign a keyword in a custom Joomla 4 extension by batch processing - submit form

8. think about how and where you show the keywords in the frontend of your own extension.
com_contact’ provides a parameter that allows the website owner to set whether tags are
displayed. The display is done with the help of the layout joomla.content.tags.

1 <?php if ($tparams->get('show_tags', 1) && !empty($this->item->tags->

Page 376 January 2023


36. Tags Joomla 4 - Developing Extensions

itemTags)) : ?>
2 <div class="com-contact__tags">
3 <?php $this->item->tagLayout = new FileLayout('joomla.content.
tags'); ?>
4 <?php echo $this->item->tagLayout->render($this->item->tags->
itemTags); ?>
5 </div>
6 <?php endif; ?>

You want to tag Joomla elements in order to build a keyword directory. You make the assignment
by opening the element (article, category ...) and entering the keywords on the right. Do you have
any problems? Are not all keywords saved. Do you also notice that not all keywords are displayed
in the selection menu of other components? The reason is that the display of the keywords is
limited to 30. I have searched for documentation. I have not found anything. Maybe it’s because
I’m not searching properly. I sometimes find it easier to look directly in the code: The place that
answers the question is in the file libraries/src/Form/Field/TagField.phpa . The PR, where you can
also read out the reasons for the introduction, is PR 31481b . The workaround is to change the
display mode in the Tags component.
a
github.com/joomla/joomla-cms/blob/ba5fc69400c2fb2a27e56d0b8bec0db10c8705df/libraries/src/form/field/tagfield.php#l136
b
github.com/joomla/joomla-cms/pull/31481

36.3. Links

Using Tags in a Extension1

1
docs.joomla.org/j3.x:using_tags_in_an_extension

January 2023 Page 377


Joomla 4 - Developing Extensions 37. Web Services

37. Web Services

In this part we will take a look at the Joomla 4 API and how to access Joomla 4 content. A programming
interface - API for short (from English application programming interface) - is a program part that
is made available by a software system to other programs for connection to the system. Nowadays,
many online services provide APIs; these are then called web service. The existence of a documented
application programming interface (API) for a Joomla component makes it possible to work together
with others. Either via additional software that uses the API via extension or data becomes usable in
other applications via the API.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t29...t30

37.1. Step by step

37.1.1. New files

37.1.1.1. Component

37.1.1.1.1. api/components/com_foos/src/Controller/FooController.php Create the controller


FooController which inherits from ApiController. Everything necessary is implemented in the
class ApiController. If you have no other requirements, then it is made up. Just overwrite the
following fields for your component:

protected $contentType = 'foos'; and protected $default_view = 'foos';.

• $contentType - is default for $modelName.


• $default_view - is default for $viewName.

1 <?php
2 namespace FooNamespace\Component\Foos\Api\Controller;
3
4 defined('_JEXEC') or die;
5

Page 379 January 2023


Joomla 4 - Developing Extensions 37. Web Services

6 use Joomla\CMS\MVC\Controller\ApiController;
7 use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
8
9 class FooController extends ApiController
10 {
11 protected $contentType = 'foos';
12
13 protected $default_view = 'foos';
14
15 protected function save($recordKey = null)
16 {
17 $data = (array) json_decode($this->input->json->getRaw(), true)
;
18
19 foreach (FieldsHelper::getFields('com_foos.foo') as $field)
20 {
21 if (isset($data[$field->name]))
22 {
23 !isset($data['com_fields']) && $data['com_fields'] =
[];
24
25 $data['com_fields'][$field->name] = $data[$field->name
];
26 unset($data[$field->name]);
27 }
28 }
29
30 $this->input->set('data', $data);
31
32 return parent::save($recordKey);
33 }
34 }

37.1.1.1.2. api/components/com_foos/src/View/Foos/JsonapiView.php Create the interface


JsonapiView that inherits from BaseApiView. Use again ready-made Joomla code. Overwrite the
following fields for your component:

protected $fieldsToRenderItem = ['id', 'alias', 'name', 'catid']; and protected


$fieldsToRenderList = ['id', 'alias', 'name', 'catid'];.

• $fieldsToRenderItem - array of information to display a single item.


• $fieldsToRenderList - Array with contents for listing objects.

1 <?php
2 namespace FooNamespace\Component\Foos\Api\View\Foos;
3
4 defined('_JEXEC') or die;
5

Page 380 January 2023


37. Web Services Joomla 4 - Developing Extensions

6 use Joomla\CMS\MVC\View\JsonApiView as BaseApiView;


7 use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
8
9 class JsonapiView extends BaseApiView
10 {
11 protected $fieldsToRenderItem = ['id', 'alias', 'name', 'catid'];
12
13 protected $fieldsToRenderList = ['id', 'alias', 'name', 'catid'];
14
15 public function displayList(array $items = null)
16 {
17 foreach (FieldsHelper::getFields('com_foos.foo') as $field)
18 {
19 $this->fieldsToRenderList[] = $field->id;
20 }
21
22 return parent::displayList();
23 }
24
25 public function displayItem($item = null)
26 {
27 foreach (FieldsHelper::getFields('com_foos.foo') as $field)
28 {
29 $this->fieldsToRenderItem[] = $field->name;
30 }
31
32 return parent::displayItem();
33 }
34
35 protected function prepareItem($item)
36 {
37 foreach (FieldsHelper::getFields('com_foos.foo', $item, true)
as $field)
38 {
39 $item->{$field->name} = isset($field->apivalue) ? $field->
apivalue : $field->rawvalue;
40 }
41
42 return parent::prepareItem($item);
43 }
44 }

37.1.1.2. Plugin

37.1.1.2.1. plugins/webservices/foos/foos.php In the plugin file we create the class PlgWebservicesFoos


and register in the onBeforeApiRoute method all the routes we need for the web service.

1 <?php
2 defined('_JEXEC') or die;

January 2023 Page 381


Joomla 4 - Developing Extensions 37. Web Services

3
4 use Joomla\CMS\Plugin\CMSPlugin;
5 use Joomla\CMS\Router\ApiRouter;
6
7 class PlgWebservicesFoos extends CMSPlugin
8 {
9 protected $autoloadLanguage = true;
10
11 public function onBeforeApiRoute(&$router)
12 {
13 $router->createCRUDRoutes(
14 'v1/foos',
15 'foo',
16 ['component' => 'com_foos']
17 );
18
19 $router->createCRUDRoutes(
20 'v1/foos/categories',
21 'categories',
22 ['component' => 'com_categories', 'extension' => 'com_foos'
]
23 );
24 }
25 }

37.1.1.2.2. plugins/webservices/foos/foos.xml To install the plug-in, an installation file is neces-


sary. You know this from the component.

1 <?xml version="1.0" encoding="utf-8"?>


2 <extension type="plugin" group="webservices" method="upgrade">
3 <name>plg_webservices_foos</name>
4 <creationDate>[DATE]</creationDate>
5 <author>[AUTHOR]</author>
6 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
7 <authorUrl>[AUTHOR_URL]</authorUrl>
8 <copyright>[COPYRIGHT]</copyright>
9 <license>GNU General Public License version 2 or later;</license>
10 <version>__BUMP_VERSION__</version>
11 <description>PLG_WEBSERVICES_FOOS_XML_DESCRIPTION</description>
12 <files>
13 <filename plugin="foos">foos.php</filename>
14 <folder>language</folder>
15 </files>
16 </extension>

Page 382 January 2023


37. Web Services Joomla 4 - Developing Extensions

37.1.1.3. plugins/webservices/foos/language/en-GB/plg_webservices_foos.ini

I am attaching the language files for the sake of completeness.

1
2 PLG_WEBSERVICES_FOOS="Web Services - Foos"
3 PLG_WEBSERVICES_FOOS_XML_DESCRIPTION="Used to add foos routes to the
API for your website."

37.1.1.4. plugins/webservices/foos/language/en-GB/plg_webservices_foos.sys.ini

I also include the language file, which is mainly responsible for the installation and the creation of the
menu in the dashboard.

1
2 PLG_WEBSERVICES_FOOS="Web Services - Foos"
3 PLG_WEBSERVICES_FOOS_XML_DESCRIPTION="Used to add foos routes to the
API for your website."

37.1.2. Modified files

37.1.3. Component

37.1.3.1. administrator/components/com_foos/foos.xml

In the installation file it is important to include the folder api. Otherwise the files in the subfolder api
will not be copied to the correct directory during an installation.

1 <folder>tmpl</folder>
2 </files>
3 </administration>
4 + <api>
5 + <files folder="api/components/com_foos">
6 + <folder>src</folder>
7 + </files>
8 + </api>
9 <changelogurl>https://codeberg.org/astrid/j4examplecode/raw/branch/
tutorial/changelog.xml</changelogurl>
10 <updateservers>
11 <server type="extension" name="Foo Updates">https://codeberg.
org/astrid/j4examplecode/raw/branch/tutorial/foo_update.xml
</server>

January 2023 Page 383


Joomla 4 - Developing Extensions 37. Web Services

37.1.3.2. Miscellaneous

37.1.3.2.1. Public Api It is possible to declare routes as public via setting a flag. However, this is
risky and can give away sensitive information. You can set public access when registering the route.
If you use Joomla\CMS\Router\ApiRouter::createCRUDRoutes(), pass the fourth argument
with true to enable public GETs.

1 ...
2 $router->createCRUDRoutes(
3 'v1/foos',
4 'foos',
5 ['component' => 'com_foos'],
6 true
7 );
8 ...

Or when you manually instantiate the route use the following code.

1 ...
2 $route = new Joomla\Router\Route(['GET'], 'v1/foos', 'foos.displayList'
, [], ['component' => 'com_foos', 'public' => true]);
3 $router->addRoute($route);
4 ...

For more information see PR 270211 or the joomla.stackexchange.com2 .

API Urls include the word /api. For example http://


37.1.3.2.2. Joomla API and Custom URL
localhost/joomla-cms4/api/index.php/v1/foos. If you need to adress another URL it is pos-
sible to redirect via .htaccess.

1 Redirect 301 /yourCustomFolder /api/v1/foos/index.php/yourCustomFolder

37.2. Test your Joomla component

1. create a new installation. To do this, uninstall your previous installation and copy all files again.
Copy the files in the administrator folder into the administrator folder of your Joomla 4
installation. Copy the files in the api folder into the api folder of your Joomla 4 installation.
Copy the files in the plugin folder into the plugin folder of your Joomla 4 installation. Install
your component and the plugin as described in part one, after copying all files. 2.

2. activate the plugin


1
github.com/joomla/joomla-cms/pull/27021
2
joomla.stackexchange.com/questions/32320/joomla-api-and-credentials

Page 384 January 2023


37. Web Services Joomla 4 - Developing Extensions

Figure 37.1.: Activate Joomla Plugin

3. activate the Plugin Basic Auth

Figure 37.2.: Activate Joomla Plugin Basic Auth

4. the interface now offers you the following query options:

A list of Foos: curl -X GET /api/index.php/v1/foos


A single Foo element: curl -X GET /api/index.php/v1/foos/{foo_id}
Delete a Foo element: curl -X DELETE /api/index.php/v1/foos/{foo_id}

January 2023 Page 385


Joomla 4 - Developing Extensions 37. Web Services

For the following examples, I assume that your installation is located at http://localhost/joomla
-cms4 and that your user and password are admin (Base64: YWRtaW46YWRtaW4=). Change this if
necessary.

37.2.1. curl.haxx.de

For Curl3 you need to change the password to Base64. A website that does this for you is
base64encode.org.

Do you use Curl? The following query will list all the elements:

1curl --location --request GET 'http://localhost/joomla-cms4/api/index.


php/v1/foos' \
2 --header 'Accept: application/vnd.api+json' \
3 --header 'Authorization: Basic YWRtaW46YWRtaW4='

The output format is JSON and looks like this:

1 {
2 "links": {
3 "self": "http://localhost/joomla-cms4/api/index.php/v1/foos"
4 },
5 "data": [
6 {
7 "type": "foos",
8 "id": "2",
9 "attributes": {
10 "id": 2,
11 "name": "Astrid",
12 "catid": 8
13 }
14 },
15 {
16 "type": "foos",
17 "id": "3",
18 "attributes": {
19 "id": 3,
20 "name": "Elmar",
21 "catid": 0
22 }
23 },
24 {
25 "type": "foos",
26 "id": "1",
27 "attributes": {
28 "id": 1,
29 "name": "Nina",
3
curl.haxx.se

Page 386 January 2023


37. Web Services Joomla 4 - Developing Extensions

30 "catid": 0
31 }
32 }
33 ],
34 "meta": {
35 "total-pages": 1
36 }
37 }

Providing the credentials is mandatory. All together the call in a console looks like this:

1 $ curl -X GET http://localhost/t30j4dev/api/index.php/v1/foos --header


'Accept: application/vnd.api+json' --header 'Authorization: Basic
YWRtaW46YWRtaW4='
2 {"links":{"self":"https:\/\/fanyv88.com:443\/http\/localhost\/t30j4dev\/api\/index.php\/v1\/
foos"},"data":[{"type":"foos","id":"2","attributes":{"id":"2","name"
:"Astrid","catid":"0"}},{"type":"foos","id":"3","attributes":{"id":"
3","name":"Elmar","catid":"0"}},{"type":"foos","id":"1","attributes"
:{"id":"1","name":"Nina","catid":"0"}}],"meta":{"total-pages":1}}

37.2.2. postman.com

Do you use postman.com? Then the collection4 might be helpful for you. It contains additional queries
for com_content.

37.2.3. Misc

37.2.3.1. Firefox addon

I like to use the addon restclient5 in Firefox.

37.3. Links

Joomla API Specification6

Integration in Weblinks7

4
github.com/astridx/boilerplate/blob/tutorial/tutorial/component/30/Content%20und%20Foos.postman_collection.json
5
addons.mozilla.org/en-us/firefox/addon/restclient/
6
docs.joomla.org/j4.x:joomla_core_apis
7
github.com/joomla-extensions/weblinks/pull/407

January 2023 Page 387


Joomla 4 - Developing Extensions

Part II.

Plugins

Page 389 January 2023


Joomla 4 - Developing Extensions 38. Plugins

38. Plugins

You have created a plug-in in the previous section. You have probably already configured other plugins
in the plugin manager and know the different types. Plugins cover many different areas in Joomla.
This chapter provides an overview of what plugins are and how they work within Joomla.

In the Joomla Documentationa you will find a list of all plugin groups with all associated events.
Use this list as a quick reference.
a
docs.joomla.org/Plugin/Events

38.1. What is a Joomla plugin?

You already know that there are different types of extensions: Components, modules, templates,
languages and plugins. While components, modules, templates and languages usually cause a direct
output, a plugin typically works in the background. Plugins are versatile. Each plugin has its own
purpose. Let’s organise plugins a little. Even within Joomla, they are divided into plugin groups. It’s
much easier to understand the purpose if you look at each type separately. In this chapter, we will get
an overview of the different types and their special features.

38.2. Plugin types in the Joomla 4 core

The Joomla core comes with a lot of plugins. These are divided into 22 plugin types in Joomla 4.2 and
so is this part of the text. For example, there is a chapter about content plugins and another one about
system plugins.

For an overview of all plugins available in Joomla core and their associated events/events, see
the Joomla documentationa . Check out the code if you need some programming inspiration.
a
docs.joomla.org/Help4.x:Plugins:_Name_of_Plugin

In my opinion, it helps to understand Joomla plugins if you study each type on its own. That is why we
are doing this now. The types or groups are classified as follows:

Page 391 January 2023


Joomla 4 - Developing Extensions 38. Plugins

38.2.1. Action Log

Plugins of the Action Log type record user activities in the Joomla Core extensions of the page to review
them later if needed. If you want to log activities in a third-party extension, create a plugin of this type
for it.

38.2.2. API Authentication

API Authentication type plugins are used to provide authentication for web services in Joomla. Re-
member: You activated a Joomla core plugin of this type in the previous chapter on web services.

38.2.3. Authentication

When someone logs into Joomla, the Joomla application authenticates that user. On most websites,
authentication is performed against the Joomla database. This type of authentication is performed
by the authentication plugin. With an authentication plugin, it is possible to use external services to
authenticate users: Joomla provides an authentication plugin for LDAP, which is used in Windows
domains.
Joomla 3 had plugins for authentication via Gmail on board. Joomla 4 no longer offers thisa . The
technology used by the plugin is no longer state of the art and less secure. Nowadays, applications
should authorize themselves via the OAuth 2.0b protocol with Google.
a
developer.joomla.org/news/724-removal-of-the-gmail-authentication-plugin-as-of-joomla-4-0.html
b
en.wikipedia.org/wiki/oauth

38.2.4. Behaviour

Behavior type plugins are used to enable a specific behavior in the website. Examples in Joomla core
are tagging or versioning of elements.

38.2.5. CAPTCHA

Plugins of this group allow to check forms with a Captcha Check1 (engl. completely automated public
Turing test to tell computers and humans apart), a fully automated public Turing test that detects
whether a human or a machine submits the form. The Joomla core comes with a plugin for Google
reCaptcha2 . Custom captcha methods are easily added.
1
en.wikipedia.org/wiki/captcha
2
google.com/recaptcha/about/

Page 392 January 2023


38. Plugins Joomla 4 - Developing Extensions

Captchas are a nice way to add an individual touch to the website. If it is too much work to create
images that match the topic, you can work with questions. On the website of a fire department
association a possible question would be, about the color of the fire truck.

38.2.6. Content

A content plugin is mostly used to change the content of the article before it is displayed or before it is
saved in the database. Those who have special requirements can use a plugin of this type for custom
functions after the article is saved in the database. Whenever you want to customize the processing of
the content, choosing this type of plugin is perfect.

38.2.7. Editors

Editor plugins convert an HTML textarea element into a JavaScript-based editor. Well known plugins of
this group are TinyMCE and CodeMirror. If no WYSIWYG3 editor plugin is enabled, Joomla displays a
normal HTML textarea. Technically, this is also done via a plugin, namely via Editor | None.

A third party plugin from the Editor group, which is very popular in the Joomla community, is the
JCE-Editora .
a
www.joomlacontenteditor.net/

38.2.8. Editor Button

At the bottom of a Joomla editor, buttons appear in addition to the toolbar - for example, a button to
add a read more link or a button to add a page break. These buttons are generated by plugins of the
editors-xtd type.

38.2.9. Extensions

There are not many plugins in this group, nevertheless it is an interesting group. Whenever a Joomla
extension is installed or removed, it is possible to hook into the installation via a plugin of this group.An
extension plugin does a task during an installation! The Joomla plugin of extension type is used to
clean up update pages. Update pages are URLs that are stored in the extension manager for updating
extensions. Since Joomla 3.2 it is possible for commercial extensions to use this plugin to allow
private downloads with a security key. And last but not least: Extensions - Namespace Updater
automatically creates and updates the file administrator/cache/autoload_psr4.php.
3
en.wikipedia.org/wiki/wysiwyg

January 2023 Page 393


Joomla 4 - Developing Extensions 38. Plugins

38.2.10. Fields

The Fields plugin type allows you to create fields in extensions that support custom fields. For example,
a calendar can be added when creating an item, through which a date is stored with the item, which is
output at a specific location in the content. This makes it easier to output content in the same layout
or to query content in other extensions. For example, a field that stores a geographic coordinate will
display a marker at that position on a digital map in a module.

38.2.11. FileSystem

Plugins of the Filesystem type are used to define one or more local directories for storing files. Do you
want to offer the flexible changing of a directory for your extension. Then check out the Joomla core
plugin Filesystem - Local with which you can set the directory where image files are stored.

38.2.12. Finder

The default search in Joomla 4 is the Search Index or Smart Search component: com_finder. In Joomla
3 this was com_search. The main difference between the two is that com_search searches the content
in real time and may open many different database tables to do so, while com_finder creates index
tables first and then searches only that index. The latter allows for a more efficient and therefore faster
full-text search. The new search index is more complex than the old classic search, which required
no configuration but offered few options. com_finder uses an active index based on stem reduction4 .
Specifically, the PHP library php-stemmer[github.com/wamania/php-stemmer] is applied. The idea
is to increase the performance and quality of the search result by covering multiple syntactic words
with a base form. For example, gardening and garden have related meanings. Each type of content
requires its own Finder plugin. Create a Finder plugin if you want content in your component to be
found,

com_search is still availablea as a decoupled component, and it also requires a separate plugin
for third-party extension content to be found.
a
github.com/joomla-extensions/search

38.2.13. Installer

Do you want to change the installation process of your extension? Then take a look at the installer type
plugins.
4
en.wikipedia.org/wiki/stemming

Page 394 January 2023


38. Plugins Joomla 4 - Developing Extensions

38.2.14. Media Action

Cropping images, changing the size or rotating them is each possible with a core Joomla plugin from
the Media Action. Expand this plugin group if the media or image editing functions are not enough for
you.

38.2.15. Privacy

If your self-programmed extension processes personal data, then plugins of the type Privacy come into
play. Create a plugin of this type and make sure in the code that this data is correctly processed by
Joomla in the core privacy component. This is the only way Joomla can handle user requests for stored
data or deletion requests. For Joomla core extensions the required plugins are available in Joomla 4.

38.2.16. Quick Icon

Use a plugin of the type quickicon to place a quickicon on the dashboard of the Joomla backend.

38.2.17. Sample Data

The Joomla Core Sample Data module provides a unified workflow for adding sample files. Want to
jump in here and make sample files installable for your extension with a click? Then, you probably
guessed it already, a plugin of the type sample files is required.

38.2.18. System

System Plugins performs a wide variety of tasks. This sounds vague, however. To make it a bit more
concrete, examples follow. System plugins can add HTML code, CSS or JavaScript to the Joomla page
after it is generated.Plugins of this type modify Joomla forms before they are generated. With the help
of system plugins alternative error handling is possible. This was only a small part of the possible. You
see, system plugins are very powerful. To be able to fulfill this powerful task, they are called frequently
and therefore need resources. Use them carefully!

Another current example is the keyboard shortcut plugin newly added in Joomla 4.2a
a
github.com/joomla/joomla-cms/pull/38092

January 2023 Page 395


Joomla 4 - Developing Extensions 38. Plugins

38.2.19. Task

Do you have tasks that have to be done again and again? Or tasks for the future that you would like
to plan and definitely must not forget? Since Joomla 4.1, you can automate these with the new task
planner. And what is essential for developers: All Joomla extensions can take advantage of it and
schedule tasks and execute them regularly. Especially if the website host does not allow cron jobs.
It is possible to use the core scheduler to schedule tasks in your own extensions. Task Plugins are
integrated into Joomla via PR 351435 .

38.2.20. Two Factor Authentication

In addition to standard authentication, there is the possibility to achieve additional security by adding
a parallel second authentication.

38.2.21. User

Is there a connection between the data in a component and the users in the Joomla user management?
Technically, this is implemented with a plug-in of the user type. Are you wondering how this works?
Then take a look at the plugin for the contact component, which links a contact to a user.

38.2.22. Web Services

A Web Services plug-in adds the routes of an extension to the website’s API. We practically used this
plugin in the previous part.

38.2.23. Workflow

In workflow management, there are different transitions that can be manipulated using a plugin.

38.3. Examples

38.3.1. Indiewebify Joomla

I have created example plugins that together allow a simple realization of the IndieWeb. I have described
the setup on a website at blog.astrid-guenther.de/en/cassiopeia-joomla-indieweb. This is about the
programming.
5
github.com/joomla/joomla-cms/pull/35143

Page 396 January 2023


38. Plugins Joomla 4 - Developing Extensions

What does Indiewebify mean and what is the IndieWeb?

The IndieWeb allows a person to publish their thoughts and ideas in one place and then share them on
other social websites. It is important to always remain the owner of your own digital content.

What if a social network develops in such a way that you no longer feel comfortable there and therefore
no longer visit it? Or the owner of the website decides to shut it down? All your contributions are lost!

In my opinion, a digital profile and its content should not be an identity owned by an external company.
A person should be the sole owner of the content they share online. And that’s what *IndieWeb_
encourages people to do.

The IndieWeb is a people-centred alternative to the Corporate Web is a quote I took from the
website IndieWeb.orga . The website indiewebify.me/ supported me in the implementation. I first
read about this on the blog chringel.dev.
a
indieweb.org/

1. set up web sign-in To authenticate yourself as the owner of your website using your domain, you
need to set up a way to sign in using IndieAuth. That is, you use your domain to verify yourself as
the owner of your other social profiles. Simply add a rel=me microformat to all your links that
lead to your profiles on other platforms. We do this within the content plugin.

2. add author markup The next step is to provide some basic information about the author on
the website. Often there is already an about me page, but it is not machine readable. The
microformat h-card provides properties that can be parsed. I have added these invisibly to the
markup of the website in combination with the following element. This way the design of the
template is not affected.

3. add content tagging If you want to publish content on the IndieWeb, it needs to be machine-
readable. I added the h-entry microformat. The website IndieWebify.me was a great help in
this step. In this plugin I add the following h-entry properties:

• p-name - the title of the post.


• e-content - the content of the post
• p-author - who wrote the post
• dt-published - when the post was published
• p-summary - the intro of an Joomla article

Now my content is correctly tagged and can be used by IndieWeb.

4. add webmentions What are webmentions? Webmentions are a W3C Recommendation6 for
conversations and interactions on web pages. It is a simple way to notify a URL when it is
6
w3.org/TR/webmention/

January 2023 Page 397


Joomla 4 - Developing Extensions 38. Plugins

mentioned on a web page. Basically, it’s a way to interact with other people’s content from your
own website.

Example: I read a post on another blog and want to respond to it. I can do that by writing a post on
my website and linking to the other post. Then I can send a webmention to the other blog to let them
know that I have reacted to the post from my website. That sounds complicated? Well, it’s just like
most social networks where you respond to a post by commenting or liking it.

There is a simple way to set up webmentions: Webmention.io. It’s a service that handles webmentions
by using web sign-in and adding some endpoints as links to your website. Here in the example we set
the endpoints, which I add to the head of the website via a system plugin.

An alternative to Webmention.io is Go-Jamming by Wouter Groeneveld.

What was missing was a way to display the webmentions. The procedure in the content plugin for
parsing webmentions is currently dynamic. This is not performant. A better solution is to retrieve the
webmentions from time to time and store them in the database.

5. syndication and backfeed A final piece of the puzzle are: POSSE and Backfeed.

POSSE means that you first publish your content on your own website and then post links on other
platforms (Publish on Site, Syndicate Elsewhere). For example, by sharing about your post on Mastodon
and then adding a link to your website.

Backfeed describes the process of pulling the interactions of your POSSE copy to the original post. So
when someone comments on a toot with the link to your post, it is actually redirected to your website
as a webmention.
Working through the 5 points makes a Joomla website a IndieWeb citizen. The plugins described
below are a simple implementation. Web Sign-In can be used via the system plugin, there is
content with microformats via the content plugin and webmentions are sent to and received from
other IndieWeb sites. Syndication is a problematic issue. The process is a bit convoluted and I’m
not sure I’m implementing it properly. You have to publish your own post first, then share the
link, and lastly add that shared link to your own post. This is where the editors-xtd plugin helps.

38.3.2. Fields
The Custom Field is intended to support inserting a Reply-toa element.
a
indieweb.org/in-reply-to

A custom form field, written for a custom field itself, is searched for by default in the /fields subdirec-
tory. You can find the code for this search in the onCustomFieldsGetTypes() function. This is im-

Page 398 January 2023


38. Plugins Joomla 4 - Developing Extensions

plemented in the file administrator/components/com_fields/src/Plugin/FieldsPlugin.


php#L96. I make it easy for myself and extend the UrlField. So I only need to add an empty class. In
the custom field named indieweb a field of type indieweb is expected. If you don‘t implement this,
a simple text field is used as fallback position.

plugins/fields/indieweb/fields/indieweb.php

1
2 <?php
3
4 \defined('JPATH_PLATFORM') or die;
5
6
7 class JFormFieldIndieweb extends Joomla\CMS\Form\Field\UrlField
8 {
9 protected $type = 'indieweb';
10 }

The file plugins/fields/indieweb/indieweb.php is the actual plugin file. It extends


administrator/components/com_fields/src/Plugin/FieldsPlugin.php. Since the
parent class implements all the essential properties, it is sufficient to add or override only its own
specifics. In my case this is a server-side validation.

You would set a client-side validation using $fieldNode->setAttribute('class', '


validate-indieweb');. As explained in the client-side validation part, you would need to add
the JavaScript.

plugins/fields/indieweb/indieweb.php

1
2 <?php
3
4 use Joomla\CMS\Form\Form;
5
6 \defined('_JEXEC') or die;
7
8 class PlgFieldsIndieweb extends \Joomla\Component\Fields\Administrator\
Plugin\FieldsPlugin
9 {
10 public function onCustomFieldsPrepareDom($field, DOMElement $parent
, Form $form)
11 {
12 $fieldNode = parent::onCustomFieldsPrepareDom($field, $parent,
$form);
13
14 if (!$fieldNode) {
15 return $fieldNode;
16 }

January 2023 Page 399


Joomla 4 - Developing Extensions 38. Plugins

17
18 $fieldNode->setAttribute('validate', 'indieweb');
19
20 return $fieldNode;
21 }
22 }

Das XML-Manifest wird für die Installation verwendet. Die Parameter werden später noch einmal für
ein einzelnes Feld implementiert. Hier im Installationsmanifest stehen sie, damit man sie global im
Plugin-Manager setzen kann.
plugins/fields/indieweb/indieweb.xml

1
2 <?xml version="1.0" encoding="utf-8" ?>
3 <extension type="plugin" group="fields" method="upgrade">
4 <name>plg_fields_indieweb</name>
5 <creationDate>[DATE]</creationDate>
6 <author>[AUTHOR]</author>
7 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
8 <authorUrl>[AUTHOR_URL]</authorUrl>
9 <copyright>[COPYRIGHT]</copyright>
10 <license>GNU General Public License version 2 or later;</license>
11 <version>__BUMP_VERSION__</version>
12 <description>PLG_FIELDS_INDIEWEB_XML_DESCRIPTION</description>
13 <files>
14 <filename plugin="indieweb">indieweb.php</filename>
15 <folder>params</folder>
16 <folder>language</folder>
17 <folder>fields</folder>
18 <folder>tmpl</folder>
19 <folder>fields</folder>
20 <folder>rules</folder>
21 </files>
22 <config>
23 <fields name="params">
24 <fieldset name="basic">
25 <field
26 name="schemes"
27 type="list"
28 label="PLG_FIELDS_INDIEWEB_PARAMS_SCHEMES_LABEL"
29 multiple="true"
30 layout="joomla.form.field.list-fancy-select"
31 validate="options"
32 >
33 <option value="http">HTTP</option>
34 <option value="https">HTTPS</option>
35 </field>
36
37 <field
38 name="relative"

Page 400 January 2023


38. Plugins Joomla 4 - Developing Extensions

39 type="radio"
40 label="PLG_FIELDS_INDIEWEB_PARAMS_RELATIVE_LABEL"
41 layout="joomla.form.field.radio.switcher"
42 default="1"
43 filter="integer"
44 >
45 <option value="0">JNO</option>
46 <option value="1">JYES</option>
47 </field>
48 </fieldset>
49 </fields>
50 </config>
51 </extension>

Als nächste sind die Sprachdateien für die Übersetzung der Vollständigkeit halber abgedruckt.

plugins/fields/indieweb/language/en-GB/plg_fields_indieweb.ini

1
2 PLG_FIELDS_INDIEWB="Fields - INDIEWEB"
3 PLG_FIELDS_INDIEWEB_LABEL="INDIEWEB (%s)"
4 PLG_FIELDS_INDIEWEB_PARAMS_RELATIVE_LABEL="Relative URLs"
5 PLG_FIELDS_INDIEWEB_PARAMS_SCHEMES_LABEL="Schemes"
6 PLG_FIELDS_INDIEWEB_PARAMS_SHOW_URL="Show URL"
7 PLG_FIELDS_INDIEWEB_XML_DESCRIPTION="This plugin lets you create new
fields of type 'URL' in any extensions where custom fields are
supported."
8 JVISIT_REPLY_TO_WEBSITE="In reply to website: "
9 JVISIT_REPLY_TO_LINK="In reply to internal link: "

plugins/fields/indieweb/language/en-GB/plg_fields_indieweb.sys.ini

1
2 PLG_FIELDS_INDIEWEB="Fields - INDIEWEB"
3 PLG_FIELDS_INDIEWEB_XML_DESCRIPTION="This plugin lets you create new
fields of type 'URL' in any extensions where custom fields are
supported."

The file plugins/fields/indieweb/params/indieweb.xml contains the parameters that are set


in the field itself when it is created and apply only to this field.

plugins/fields/indieweb/params/indieweb.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <form>
4 <fields name="fieldparams">
5 <fieldset name="fieldparams">
6 <field
7 name="schemes"
8 type="list"

January 2023 Page 401


Joomla 4 - Developing Extensions 38. Plugins

9 label="PLG_FIELDS_INDIEWEB_PARAMS_SCHEMES_LABEL"
10 multiple="true"
11 layout="joomla.form.field.list-fancy-select"
12 validate="options"
13 >
14 <option value="http">HTTP</option>
15 <option value="https">HTTPS</option>
16 </field>
17
18 <field
19 name="relative"
20 type="list"
21 label="PLG_FIELDS_INDIEWEB_PARAMS_RELATIVE_LABEL"
22 filter="integer"
23 validate="options"
24 >
25 <option value="">COM_FIELDS_FIELD_USE_GLOBAL</option>
26 <option value="1">JYES</option>
27 <option value="0">JNO</option>
28 </field>
29
30 <field
31 name="show_url"
32 type="radio"
33 label="PLG_FIELDS_INDIEWEB_PARAMS_SHOW_URL"
34 layout="joomla.form.field.radio.switcher"
35 default="1"
36 filter="integer"
37 >
38 <option value="0">JNO</option>
39 <option value="1">JYES</option>
40 </field>
41 </fieldset>
42 </fields>
43 </form>

The rules for validation belong in the rules directory. This is implemented in the file administrator
/components/com_fields/src/Plugin/FieldsPlugin.php#L96. Again, I made it simple and
copied from the validation of the url field. Primarily, I want to show where the files are inserted so that
they are found correctly by Joomla.

plugins/fields/indieweb/rules/indieweb.php

1
2 <?php
3
4 use Joomla\CMS\Form\Form;
5 use Joomla\CMS\Form\FormRule;
6 use Joomla\CMS\Language\Text;
7 use Joomla\Registry\Registry;

Page 402 January 2023


38. Plugins Joomla 4 - Developing Extensions

8 use Joomla\String\StringHelper;
9 use Joomla\Uri\UriHelper;
10
11 \defined('JPATH_PLATFORM') or die;
12
13 class JFormRuleIndieweb extends FormRule
14 {
15 public function test(\SimpleXMLElement $element, $value, $group =
null, Registry $input = null, Form $form = null)
16 {
17 // If the field is empty and not required, the field is valid.
18 $required = ((string) $element['required'] === 'true' || (
string) $element['required'] === 'required');
19
20 if (!$required && empty($value)) {
21 return true;
22 }
23
24 $urlParts = UriHelper::parse_url($value);
25
26 // See https://www.w3.org/Addressing/URL/url-spec.txt
27 // Use the full list or optionally specify a list of permitted
schemes.
28 if ($element['schemes'] == '') {
29 $scheme = ['http', 'https'];
30 } else {
31 $scheme = explode(',', $element['schemes']);
32 }
33
34 /*
35 if ($urlParts === false || !\array_key_exists('scheme',
$urlParts)) {
36 /*
37 if ($urlParts === false || !$element['relative']) {
38 $element->addAttribute('message', Text::sprintf('
JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value
, implode(', ', $scheme)));
39
40 return false;
41 }
42
43 // The best we can do for the rest is make sure that the
path exists and is valid UTF-8.
44 if (!\array_key_exists('path', $urlParts) || !StringHelper
::valid((string) $urlParts['path'])) {
45 return false;
46 }
47
48 // The internal URL seems to be good.
49 return true;
50 }

January 2023 Page 403


Joomla 4 - Developing Extensions 38. Plugins

51
52 // Scheme found, check all parts found.
53 $urlScheme = (string) $urlParts['scheme'];
54 $urlScheme = strtolower($urlScheme);
55
56 if (\in_array($urlScheme, $scheme) == false) {
57 return false;
58 }
59
60 // For some schemes here must be two slashes.
61 $scheme = ['http', 'https'];
62
63 if (\in_array($urlScheme, $scheme) && substr($value, \strlen(
$urlScheme), 3) !== '://') {
64 return false;
65 }
66
67 // The best we can do for the rest is make sure that the
strings are valid UTF-8
68 // and the port is an integer.
69 if (\array_key_exists('host', $urlParts) && !StringHelper::
valid((string) $urlParts['host'])) {
70 return false;
71 }
72
73 if (\array_key_exists('port', $urlParts) && !\is_int((int)
$urlParts['port'])) {
74 return false;
75 }
76
77 if (\array_key_exists('path', $urlParts) && !StringHelper::
valid((string) $urlParts['path'])) {
78 return false;
79 }
80
81 return true;
82 }
83 }

The file plugins/fields/indieweb/tmpl/indieweb.php is the template for the output in the


frontend.

plugins/fields/indieweb/tmpl/indieweb.php

1
2 <?php
3
4 defined('_JEXEC') or die;
5
6 use Joomla\CMS\Language\Text;
7 use Joomla\CMS\Uri\Uri;

Page 404 January 2023


38. Plugins Joomla 4 - Developing Extensions

8
9 $value = $field->value;
10
11 if ($value == '') {
12 return;
13 }
14
15 $attributes = '';
16
17 $attributes = ' target="_self"';
18
19 if (!Uri::isInternal($value)) {
20 $text = Text::_('JVISIT_REPLY_TO_WEBSITE');
21 } else {
22 $text = Text::_('JVISIT_REPLY_TO_LINK');
23 }
24
25 if ($fieldParams->get('show_url', 0)) {
26 $text = $text . htmlspecialchars($value);
27 }
28
29 echo sprintf(
30 '<div class="u-in-reply-to h-cite"><a class="u-url" href="%s"%s>%s
</a></div>',
31 htmlspecialchars($value),
32 $attributes,
33 $text
34 );

38.3.3. Task
The task plugin is there to fetch Webmention from the website webmention.io at regular time
intervals.

We start with the manifest for the installation. Note that we use namespace here.

plugins/task/indieweb/indieweb.xml

1
2 <?xml version="1.0" encoding="utf-8" ?>
3 <extension type="plugin" group="task" method="upgrade">
4 <name>plg_task_indie_web</name>
5 <author>Astrid Günther</author>
6 <creationDate>[DATE]</creationDate>
7 <author>[AUTHOR]</author>
8 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
9 <authorUrl>[AUTHOR_URL]</authorUrl>
10 <copyright>[COPYRIGHT]</copyright>
11 <license>GNU General Public License version 2 or later;</license>

January 2023 Page 405


Joomla 4 - Developing Extensions 38. Plugins

12 <version>__BUMP_VERSION__</version>
13 <description>PLG_TASK_INDIE_WEB_XML_DESCRIPTION</description>
14 <namespace path="src">Joomla\Plugin\Task\IndieWeb</namespace>
15 <files>
16 <folder plugin="indieweb">services</folder>
17 <file>indieweb.xml</file>
18 <file>webmentions.json</file>
19 <folder>language</folder>
20 <folder>src</folder>
21 </files>
22 <config>
23 <fields name="params">
24 <fieldset name="basic">
25 </fieldset>
26 <fieldset name="WEBMENTION_IO">
27 <field
28 name="token"
29 type="text"
30 label="PLG_TASK_INDIEWEB_WEBMENTION_IO_TOKEN_LABEL"
31 description="
PLG_TASK_INDIEWEB_WEBMENTION_IO_TOKEN_DESC"
32 />
33 </fieldset>
34 </fields>
35 </config>
36 </extension>

I have attached the two language files below for completeness.

plugins/task/indieweb/language/en-GB/plg_task_indieweb.ini

1
2 PLG_TASK_INDIE_WEB="Task - Indieweb"
3 PLG_TASK_INDIE_WEB_DESC="Fetches webmentions on each run."
4 PLG_TASK_INDIE_WEB_ERROR_WEBMENTIONS_PHP_NOTUNWRITABLE="Could not make
configuration.php un-writable."
5 PLG_TASK_INDIE_WEB_ERROR_WEBMENTIONS_PHP_NOTWRITABLE="Could not make
configuration.php writable."
6 PLG_TASK_INDIE_WEB_ERROR_WRITE_FAILED="Could not write to the
configuration file!"
7 PLG_TASK_INDIE_WEB_ROUTINE_END_LOG_MESSAGE="ToggleOffline return code
is: %1$d. Processing Time: %2$.2f seconds."
8 PLG_TASK_INDIE_WEB_TASK_LOG_INDIE_WEB="Webmentions in File %1$s."
9 PLG_TASK_INDIE_WEB_TITLE="Fetches webmentions"
10 PLG_TASK_INDIE_WEB_XML_DESCRIPTION="Offers task routines to fetch
webmentions."

plugins/task/indieweb/language/en-GB/plg_task_indieweb.sys.ini

1
2 PLG_TASK_INDIE_WEB="Task - Indieweb"

Page 406 January 2023


38. Plugins Joomla 4 - Developing Extensions

3 PLG_TASK_INDIE_WEB_XML_DESCRIPTION="Offers task routines to change the


site's offline status."
4
5 ; Web Sign In
6 COM_PLUGINS_WEBMENTION_IO_FIELDSET_LABEL="Webmention.io"
7 PLG_TASK_INDIEWEB_WEBMENTION_IO_LABEL="Webmention.io"
8 PLG_TASK_INDIEWEB_WEBMENTION_IO_TOKEN_LABEL="Token"
9 PLG_TASK_INDIEWEB_WEBMENTION_IO_TOKEN_DESC="<p>Webmention.io.</p>"

In the file plugins/task/indieweb/services/provider.php the service of the extension is regis-


tered. This is implemented in the file plugins/task/indieweb/src/Extension/IndieWeb.php.
This is accessed via the namespace Joomla\Plugin\Task\IndieWeb\Extension\IndieWeb;.
plugins/task/indieweb/services/provider.php

1
2 <?php
3
4 defined('_JEXEC') or die;
5
6 use Joomla\CMS\Extension\PluginInterface;
7 use Joomla\CMS\Factory;
8 use Joomla\CMS\Plugin\PluginHelper;
9 use Joomla\DI\Container;
10 use Joomla\DI\ServiceProviderInterface;
11 use Joomla\Event\DispatcherInterface;
12 use Joomla\Plugin\Task\IndieWeb\Extension\IndieWeb;
13 use Joomla\Utilities\ArrayHelper;
14
15 return new class implements ServiceProviderInterface
16 {
17 public function register(Container $container)
18 {
19 $container->set(
20 PluginInterface::class,
21 function (Container $container) {
22 $plugin = new IndieWeb(
23 $container->get(DispatcherInterface::class),
24 (array) PluginHelper::getPlugin('task', 'indieweb')
,
25 ArrayHelper::fromObject(new JConfig()),
26 JPATH_BASE . '/plugins/task/indieweb/webmentions.
json'
27 );
28 $plugin->setApplication(Factory::getApplication());
29
30 return $plugin;
31 }
32 );
33 }
34 };

January 2023 Page 407


Joomla 4 - Developing Extensions 38. Plugins

The file plugins/task/indieweb/src/Extension/IndieWeb.php does the actual work. The


tasks listed in the TASKS_MAP constant are displayed in the Joomla backend.

plugins/task/indieweb/src/Extension/IndieWeb.php

1
2 <?php
3
4 namespace Joomla\Plugin\Task\IndieWeb\Extension;
5
6 use Exception;
7 use Joomla\CMS\Plugin\CMSPlugin;
8 use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
9 use Joomla\Component\Scheduler\Administrator\Task\Status;
10 use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait;
11 use Joomla\Event\DispatcherInterface;
12 use Joomla\Event\SubscriberInterface;
13 use Joomla\Filesystem\File;
14 use Joomla\Filesystem\Path;
15 use Joomla\Registry\Registry;
16
17 \defined('_JEXEC') or die;
18
19 final class IndieWeb extends CMSPlugin implements SubscriberInterface
20 {
21 use TaskPluginTrait;
22
23 protected const TASKS_MAP = [
24 'plg_task_fetch_webmentions' => [
25 'langConstPrefix' => 'PLG_TASK_INDIE_WEB',
26 ],
27 ];
28
29 protected $autoloadLanguage = true;
30
31 public static function getSubscribedEvents(): array
32 {
33 return [
34 'onTaskOptionsList' => 'advertiseRoutines',
35 'onExecuteTask' => 'alterIndiewebStatus',
36 ];
37 }
38
39 private $webmentionFile;
40
41 public function __construct(DispatcherInterface $dispatcher, array
$config, array $jConfig, string $webmentionFile)
42 {
43 parent::__construct($dispatcher, $config);

Page 408 January 2023


38. Plugins Joomla 4 - Developing Extensions

44
45 $this->webmentionFile = $webmentionFile;
46 }
47
48 public function alterIndiewebStatus(ExecuteTaskEvent $event): void
49 {
50 if (!array_key_exists($event->getRoutineId(), self::TASKS_MAP))
{
51 return;
52 }
53
54 $this->startRoutine($event);
55
56 $exit= $this->writewebmentionFile($this->webmentionFile);
57 $this->logTask(sprintf($this->getApplication()->getLanguage()->
_('PLG_TASK_INDIE_WEB_TASK_LOG_INDIE_WEB'), $this->
webmentionFile));
58
59 $this->endRoutine($event, $exit);
60 }
61
62 private function writewebmentionFile(string $config): int
63 {
64 $file = $this->webmentionFile;
65
66 if (file_exists($file) && Path::isOwner($file) && !Path::
setPermissions($file)) {
67 $this->logTask($this->getApplication()->getLanguage()->_('
PLG_TASK_INDIE_WEB_ERROR_WEBMENTIONS_PHP_NOTWRITABLE'),
'notice');
68 }
69
70 try {
71 $curl = curl_init();
72 curl_setopt($curl, CURLOPT_URL, 'https://webmention.io/api/
mentions.jf2?token=' . $this->params->get('token'));
73 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
74
75 $response = curl_exec($curl);
76
77 if ($response === false) {
78 $curlError = curl_error($curl);
79 curl_close($curl);
80 throw new ApiException('cURL Error: ' . $curlError);
81 }
82
83 $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
84
85 if ($httpCode >= 400) {
86 curl_close($curl);
87 $responseParsed = json_decode($response);

January 2023 Page 409


Joomla 4 - Developing Extensions 38. Plugins

88 throw new ApiException('HTTP Error ' . $httpCode .


89 ' (' . $responseParsed->error->type . '): ' .
$responseParsed->error->message);
90 }
91
92 curl_close($curl);
93
94 File::write($file, $response);
95 } catch (Exception $e) {
96 $this->logTask($this->getApplication()->getLanguage()->_('
PLG_TASK_INDIE_WEB_ERROR_WRITE_FAILED'), 'error');
97
98 return Status::KNOCKOUT;
99 }
100
101 // Invalidates the cached file
102 if (function_exists('opcache_invalidate')) {
103 opcache_invalidate($file);
104 }
105
106 // Attempt to make the file un-writeable.
107 if (Path::isOwner($file) && !Path::setPermissions($file, '0444'
)) {
108 $this->logTask($this->getApplication()->getLanguage()->_('
PLG_TASK_INDIE_WEB_ERROR_WEBMENTIONS_PHP_NOTUNWRITABLE')
, 'notice');
109 }
110
111 return Status::OK;
112 }
113 }

The file plugins/task/indieweb/webmentions.json is not part of the actual program code. It is


downloaded when the task is executed. I have included it here as an example.

plugins/task/indieweb/webmentions.json

1 /* https://codeberg.org/astrid/j4examplecode/raw/branch/t30a/src/
plugins/task/indieweb/webmentions.json */
2 {
3 "type": "feed",
4 "name": "Webmentions",
5 "children": [
6 {
7 "type": "entry",
8 "author": {
9 "type": "card",
10 "name": "Astrid",
11 "photo": "https://webmention.io/avatar/fimidi.com/19be.
jpg",
12 "url": "https://fimidi.com/@astrid"

Page 410 January 2023


38. Plugins Joomla 4 - Developing Extensions

13 },
14 "url": "https://fimidi.com/@astrid/109303891082037165",
15 "published": "2022-11-07T18:16:57+00:00",
16 "wm-received": "2022-11-13T10:32:24Z",
17 "wm-id": 1557987,
18 "wm-source": "https://fimidi.com/web/@astrid
/109303891082037165",
19 "wm-target": "https://astrid-guenther.de/en/
webprogrammierung/imagemap-and-or-advent-calender-for-
joomla",
20 "content": {
21 "html": "<p>I like advent calendars. I am currently in
the process of designing and ...</p>",
22 "text": "I like advent calendars. I am currently in the
process of designing and ..."
23 },
24 "mention-of": "https://astrid-guenther.de/en/
webprogrammierung/imagemap-and-or-advent-calender-for-
joomla",
25 "wm-property": "mention-of",
26 "wm-private": false
27 }
28 ]
29 }

38.3.4. System

For inserting elements in the <head> of the HTML markup we access a system plugin.

This onAfterDispatch event is fired after the framework is loaded and the application initialization
method is called. Here it is possible to insert elements into the document.

plugins/system/indieweb/indieweb.php

1
2 <?php
3
4 use Joomla\CMS\Plugin\CMSPlugin;
5
6 \defined('_JEXEC') or die;
7
8 class PlgSystemIndieweb extends CMSPlugin
9 {
10 protected $app;
11
12 public function onAfterDispatch()
13 {
14 $doc = $this->app->getDocument();

January 2023 Page 411


Joomla 4 - Developing Extensions 38. Plugins

15 $doc->addCustomTag('<link rel="authorization_endpoint" href="'


. $this->params->get('authorization_endpoint', 'https://
indieauth.com/auth') . '" >');
16 $doc->addCustomTag('<link rel="token_endpoint" href="' . $this
->params->get('token_endpoint', 'https://tokens.indieauth.
com/token') . '" >');
17 $doc->addCustomTag('<link rel="webmention" href="' . $this->
params->get('webmention', 'https://webmention.io/example.org
/webmention') . '" >');
18 $doc->addCustomTag('<link rel="pingback" href="' . $this->
params->get('pingback', 'https://webmention.io/example.org/
xmlrpc') . '" >');
19 }
20 }

The installation manifest does not contain any special features.

plugins/system/indieweb/indieweb.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <extension type="plugin" group="system" method="upgrade">
4 <name>plg_system_indieweb</name>
5 <creationDate>[DATE]</creationDate>
6 <author>[AUTHOR]</author>
7 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
8 <authorUrl>[AUTHOR_URL]</authorUrl>
9 <copyright>[COPYRIGHT]</copyright>
10 <license>GNU General Public License version 2 or later;</license>
11 <version>__BUMP_VERSION__</version>
12 <description>PLG_SYSTEM_INDIEWEB_XML_DESCRIPTION</description>
13 <files>
14 <file>indieweb.xml</file>
15 <file plugin="indieweb">indieweb.php</file>
16 <folder>language</folder>
17 </files>
18 <config>
19 <fields name="params">
20 <fieldset name="basic">
21 <field
22 name="authorization_endpoint"
23 type="url"
24 label="
PLG_SYSTEM_INDIEWEB_AUTHORIZATION_ENDPOINT_LABEL
"
25 description="
PLG_SYSTEM_INDIEWEB_AUTHORIZATION_ENDPOINT_DESC"
26 hint="https://indieauth.com/auth"
27 filter="url"
28 validate="url"
29 />

Page 412 January 2023


38. Plugins Joomla 4 - Developing Extensions

30 <field
31 name="token_endpoint"
32 type="url"
33 label="PLG_SYSTEM_INDIEWEB_TOKEN_ENDPOINT_LABEL"
34 description="
PLG_SYSTEM_INDIEWEB_TOKEN_ENDPOINT_DESC"
35 hint="https://tokens.indieauth.com/token"
36 filter="url"
37 validate="url"
38 />
39 <field
40 name="webmention"
41 type="url"
42 label="PLG_SYSTEM_INDIEWEB_WEBMENTIOM_LABEL"
43 description="PLG_SYSTEM_INDIEWEB_WEBMENTIOM_DESC"
44 hint="https://webmention.io/example.org/webmention"
45 filter="url"
46 validate="url"
47 />
48 <field
49 name="pingback"
50 type="url"
51 label="PLG_SYSTEM_INDIEWEB_PINGBACK_LABEL"
52 description="PLG_SYSTEM_INDIEWEB_PINGBACK_DESC"
53 hint="https://webmention.io/example.org/xmlrpc"
54 filter="url"
55 validate="url"
56 />
57 </fieldset>
58 </fields>
59 </config>
60 </extension>

The two language files are included below for completeness.

plugins/system/indieweb/language/en-GB/plg_system_indieweb.ini

1
2 PLG_SYSTEM_INDIEWEB="System - Indieweb"
3 PLG_SYSTEM_INDIEWEB_XML_DESCRIPTION="Inserts meta information in the
header of the website.<ol><li>&lt;link rel='authorization_endpoint'
href='https://eample.org' /&gt;<li>&lt;link rel='token_endpoint'
href='https://eample.org' /&gt;<li>&lt;link rel='webmention' href='
https://eample.org' /&gt;<li>&lt;link rel='pingback' href='https://
eample.org' /&gt;"

plugins/system/indieweb/language/en-GB/plg_system_indieweb.sys.ini

1
2 PLG_SYSTEM_INDIEWEB="System - Indieweb"
3 PLG_SYSTEM_INDIEWEB_XML_DESCRIPTION="Inserts meta information in the

January 2023 Page 413


Joomla 4 - Developing Extensions 38. Plugins

header of the website.<ol><li>&lt;link rel='authorization_endpoint'


href='https://eample.org' /&gt;<li>&lt;link rel='token_endpoint'
href='https://eample.org' /&gt;<li>&lt;link rel='webmention' href='
https://eample.org' /&gt;<li>&lt;link rel='pingback' href='https://
eample.org' /&gt;"
4
5 PLG_SYSTEM_INDIEWEB_AUTHORIZATION_ENDPOINT_LABEL="Authorization
Endpoint"
6 PLG_SYSTEM_INDIEWEB_TOKEN_ENDPOINT_LABEL="Token Endpoint"
7 PLG_SYSTEM_INDIEWEB_WEBMENTIOM_LABEL="Webmention"
8 PLG_SYSTEM_INDIEWEB_PINGBACK_LABEL="Pingback"
9
10 PLG_SYSTEM_INDIEWEB_AUTHORIZATION_ENDPOINT_DESC="An <b><dfn>
authorization endpoint</dfn></b> is an HTTP endpoint that <a href='
https://indieweb.org/Micropub'>micropub</a> or <a href='https://
indieweb.org/IndieAuth'>IndieAuth</a> clients can use to identify a
user or obtain an authorization code (which is then later exchanged
for an access token) to be able to post to their website (https://
indieweb.org/authorization-endpoint).<br>Default: https://indieauth.
com/auth."
11 PLG_SYSTEM_INDIEWEB_TOKEN_ENDPOINT_DESC="A <b><dfn>token endpoint</dfn
></b> is an HTTP endpoint that <a href='https://indieweb.org/
Micropub'>micropub</a> clients can use to obtain an access token
given an authorization code (https://indieweb.org/token-endpoint).<
br>Default: https://tokens.indieauth.com/token."
12 PLG_SYSTEM_INDIEWEB_WEBMENTIOM_DESC="<b><dfn><a href="https://www.w3.
org/TR/webmention/">Webmention</a></dfn></b> is a web standard for
conversations and interactions across the web, a powerful building
block used for a growing distributed network of peer-to-peer <a href
='https://indieweb.org/comment'>comments</a>, <a href='https://
indieweb.org/like'>likes</a>, <a href='https://indieweb.org/repost'>
reposts</a>, and other <a href='https://indieweb.org/responses'>
responses</a> across the web (https://indieweb.org/Webmention).<br>
Default: https://webmention.io/example.org/webmention."
13 PLG_SYSTEM_INDIEWEB_PINGBACK_DESC="<b><dfn>Pingback</dfn></b> is a
legacy <a href="/XML-RPC">XML-RPC</a> based protocol for web sites
to notify other web sites when they've posted a link to them
respectively (https://indieweb.org/pingback).<br>Default: https://
webmention.io/example.org/xmlrpc."

38.3.5. Content

The content plugin adds elements to the HTML markup that meet the minimum syntactic rules of
the IndieWeb. The elements are partially assigned the CSS class hidden and therefore do not
appear in the default template Cassiopeia. I accept the disadvantage that the content appears
twice in the markup. The advantage is that I am not dependent on how a template renders the
content and that the plugin does not affect the appearance of the website.

Page 414 January 2023


38. Plugins Joomla 4 - Developing Extensions

plugins/content/indieweb/indieweb.php

1
2 <?php
3
4 use Joomla\CMS\Factory;
5 use Joomla\CMS\Language\Multilanguage;
6 use Joomla\CMS\Plugin\CMSPlugin;
7 use Joomla\CMS\Router\Route;
8 use Joomla\Component\Contact\Site\Helper\RouteHelper;
9 use Joomla\Database\ParameterType;
10 use Joomla\Registry\Registry;
11
12 \defined('_JEXEC') or die;
13
14 class PlgContentIndieweb extends CMSPlugin
15 {
16 protected $db;
17
18 public function onContentPrepare($context, &$row, $params, $page =
0)
19 {
20 if ($context === 'com_finder.indexer') {
21 return;
22 }
23
24 $allowed_contexts = ['com_content.article', 'com_agadvents.
agadvent'];
25
26 if (!in_array($context, $allowed_contexts)) {
27 return;
28 }
29
30 if (!($params instanceof Registry)) {
31 return;
32 }
33
34 if (!isset($row->id) || !(int) $row->id) {
35 return;
36 }
37
38 if ($context === 'com_content.article') {
39 $indieweb = $this->getIndiewebData($row->created_by);
40 $row->contactid = $indieweb->contactid;
41 $row->webpage = $indieweb->webpage;
42 $row->email = $indieweb->email_to;
43 $row->authorname = $indieweb->name;
44 }
45
46 // Todo Save created_by with agadvent
47 if ($context === 'com_agadvents.agadvent') {

January 2023 Page 415


Joomla 4 - Developing Extensions 38. Plugins

48 $row->webpage = "";
49 $row->email = "";
50 $row->authorname = "Advent";
51 $row->title = $row->name;
52 $row->introtext = '';
53 $row->text = $row->fulltext;
54 }
55
56 $url = $this->params->get('url', 'url');
57
58 $row->indieweb_link = '';
59
60 // Web Sign In
61 $row->text = $row->text . '<div class="hidden"><ul>';
62 $row->text = $row->text . '<li><a rel="me" href="mailto:' .
$row->email . '">' . $row->email . '</a></li>';
63
64 foreach ($this->params->get('websignin') as $websigninitem) {
65 $row->text = $row->text . '<li><a rel="me" href="' .
$websigninitem->websignin_url . '">' . $websigninitem->
websignin_url . '</a></li>';
66 }
67 $row->text = $row->text . '</ul></div>';
68
69
70 // Content
71 $row->text = $row->text . '<article class="hidden h-entry">
72 <h1 class="p-name">' . $row->title . '</h1>
73 <p>Published by
74 <p class="p-author h-card"><a class="u-url u-uid" href="' .
$row->webpage . '">' . $row->authorname . '</a></p> on
75
76 <time class="dt-published" datetime="' . $row->publish_up . '">
' . $row->publish_up . '</time>
77 </p>
78 <p class="p-summary">' . $row->introtext . '</p>
79 <div class="e-content">' . str_replace($row->introtext, '',
$row->text) . '</div>
80 </article>';
81
82
83 $webmention_file = JPATH_BASE . '/plugins/task/indieweb/
webmentions.json';
84 $webmentions = file_get_contents($webmention_file);
85 $webmentions = json_decode($webmentions);
86
87 $webmentions_urls = "";
88 if ($webmentions !== null) {
89 foreach ($webmentions->children as $i => $webmention) {
90 if (str_contains($webmention->{'wm-target'}, $row->
alias)) {

Page 416 January 2023


38. Plugins Joomla 4 - Developing Extensions

91 $webmentions_urls = $webmentions_urls . '<a href="'


. $webmention->{'wm-source'} . '">' .
$webmention->{'wm-source'} . '</a></br>';
92 }
93 }
94 }
95
96 $row->text = $row->text . '<div><b>Webmentions</b><br>' .
$webmentions_urls . '</div>';
97
98
99 // Syndication
100 $syndication_urls = '<div><b>Syndication</b><ol>';
101
102 $regex = '/{loadsyndication\s(.*?)}/i';
103 $matcheslist = [];
104 preg_match_all($regex, $row->text, $matches, PREG_SET_ORDER);
105 if ($matches) {
106 foreach ($matches as $match) {
107 $matcheslist = explode(',', $match[1]);
108 }
109 }
110
111 foreach ($matcheslist as $i => $matche) {
112 $syndication_urls = $syndication_urls . '<li><a class="u-
syndication" rel="syndication" href="' . $matche . '">'
. $matche . '</a></li>';
113 }
114
115 $syndication_urls = $syndication_urls . '</ol></div>';
116 $row->text = $row->text . $syndication_urls;
117 $row->text = preg_replace($regex, '', $row->text);
118
119 // Todo text and fulltext ?
120 if ($context === 'com_agadvents.agadvent') {
121 $row->fulltext = $row->text;
122 }
123 }
124
125 protected function getIndiewebData($userId)
126 {
127 static $indiewebs = [];
128
129 // Note: don't use isset() because value could be null.
130 if (array_key_exists($userId, $indiewebs)) {
131 return $indiewebs[$userId];
132 }
133
134 $db = $this->db;
135 $query = $db->getQuery(true);
136 $userId = (int) $userId;

January 2023 Page 417


Joomla 4 - Developing Extensions 38. Plugins

137
138 $query->select($db->quoteName('contact.id', 'contactid'))
139 ->select(
140 $db->quoteName(
141 [
142 'contact.alias',
143 'contact.catid',
144 'contact.webpage',
145 'contact.email_to',
146 'contact.name',
147 ]
148 )
149 )
150 ->from($db->quoteName('#__contact_details', 'contact'))
151 ->where(
152 [
153 $db->quoteName('contact.published') . ' = 1',
154 $db->quoteName('contact.user_id') . ' = :createdby'
,
155 ]
156 )
157 ->bind(':createdby', $userId, ParameterType::INTEGER);
158
159 if (Multilanguage::isEnabled() === true) {
160 $query->where(
161 '(' . $db->quoteName('contact.language') . ' IN ('
162 . implode(',', $query->bindArray([Factory::getLanguage
()->getTag(), '*'], ParameterType::STRING))
163 . ') OR ' . $db->quoteName('contact.language') . ' IS
NULL)'
164 );
165 }
166
167 $query->order($db->quoteName('contact.id') . ' DESC')
168 ->setLimit(1);
169
170 $db->setQuery($query);
171
172 $indiewebs[$userId] = $db->loadObject();
173
174 return $indiewebs[$userId];
175 }
176 }

Auch das Content Plugin benötigt ein Installations-Manifest.

plugins/content/indieweb/indieweb.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <extension type="plugin" group="content" method="upgrade">

Page 418 January 2023


38. Plugins Joomla 4 - Developing Extensions

4 <name>plg_content_indieweb</name>
5 <author>Astrid Günther</author>
6 <creationDate>[DATE]</creationDate>
7 <author>[AUTHOR]</author>
8 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
9 <authorUrl>[AUTHOR_URL]</authorUrl>
10 <copyright>[COPYRIGHT]</copyright>
11 <license>GNU General Public License version 2 or later;</license>
12 <version>__BUMP_VERSION__</version>
13 <description>PLG_CONTENT_INDIEWEB_XML_DESCRIPTION</description>
14 <files>
15 <file>indieweb.xml</file>
16 <file plugin="indieweb">indieweb.php</file>
17 <folder>language</folder>
18 </files>
19 <config>
20 <fields name="params">
21 <fieldset name="basic">
22 </fieldset>
23 <fieldset name="WebSignIn">
24 <field
25 name="websignin"
26 type="subform"
27 label="PLG_CONTENT_INDIEWEB_WEBSIGNIN_LABEL"
28 description="PLG_CONTENT_INDIEWEB_WEBSIGNIN_DESC"
29 layout="joomla.form.field.subform.repeatable-table"
30 icon="list"
31 multiple="true"
32 default=''
33 >
34 <form repeat="true">
35 <field
36 name="websignin_url"
37 type="url"
38 label="
PLG_CONTENT_INDIEWEB_WEBSIGNIN_URL_LABEL
"
39 hint="mailto:[email protected] or https://
fimidi.com/@username"
40 filter="url"
41 validate="url"
42 size="50"
43 />
44 </form>
45 </field>
46 </fieldset>
47 </fields>
48 </config>
49 </extension>

Below are the two language files necessary for correct translation.

January 2023 Page 419


Joomla 4 - Developing Extensions 38. Plugins

plugins/content/indieweb/language/en-GB/plg_content_indieweb.ini

1
2 PLG_CONTENT_INDIEWEB="Content - Indieweb"
3 PLG_CONTENT_INDIEWEB_XML_DESCRIPTION="Adds visible and invisible
information about the content, the author of the content,
webmentions and syndication links for the indieweb. Requirement: The
user who wrote the post must be connected to a contact."

plugins/content/indieweb/language/en-GB/plg_content_indieweb.sys.ini

1
2 PLG_CONTENT_INDIEWEB="Content - Indieweb"
3 PLG_CONTENT_INDIEWEB_XML_DESCRIPTION="Adds visible and invisible
information about the content, the author of the content,
webmentions and syndication links for the indieweb. Requirement: The
user who wrote the post must be connected to a contact."
4
5 COM_PLUGINS_WEBSIGNIN_FIELDSET_LABEL="Web Sign In"
6 PLG_CONTENT_INDIEWEB_WEBSIGNIN_LABEL="Web Sign In URLs"
7 PLG_CONTENT_INDIEWEB_WEBSIGNIN_URL_LABEL="URL"
8 PLG_CONTENT_INDIEWEB_WEBSIGNIN_DESC="<p>In order to be able to sign in
using your domain name, connect it to your existing identities. You
probably already have many disconnected profiles on the web. </p><p>
Linking between them and your domain name with the rel=me
microformat ensures that i t s easy to see that you on Google/
Twitter/Github/Flickr/Facebook/email are all the same person as your
domain name (https://indieweb.org/How_to_set_up_web_sign-
in_on_your_own_domain).</p><p>The outer container contains the class
hidden, so that the information is inserted hidden on the website
in a template that styles the class with display:none.</p>"

38.3.6. Editor Button


In the content plugin, syndication links are detected and reshaped using a specific pattern in the
content. To make it easier to enter this pattern in the editor, we implement this editor button.

The file media/plg_editors-xtd_indieweb/joomla.asset.json registers the necessary


JavaScript code in the WebAsset manager.

media/plg_editors-xtd_indieweb/joomla.asset.json

1 /* https://codeberg.org/astrid/j4examplecode/raw/branch/t30a/src/media/
plg_editors-xtd_indieweb/joomla.asset.json */
2
3 {
4 "$schema": "https://developer.joomla.org/schemas/json-schema/
web_assets.json",
5 "name": "plg_editors-xtd_indieweb",

Page 420 January 2023


38. Plugins Joomla 4 - Developing Extensions

6 "version": "4.0.0",
7 "description": "Joomla CMS",
8 "license": "GPL-2.0-or-later",
9 "assets": [
10 {
11 "name": "plg_editors-xtd_indieweb.admin-article-indieweb",
12 "type": "script",
13 "uri": "plg_editors-xtd_indieweb/admin-article-indieweb.js",
14 "dependencies": [
15 "core"
16 ],
17 "attributes": {
18 "nomodule": true,
19 "defer": true
20 },
21 "version": "3caf2bd836dad54185a2fbb3c9a625b7576d677c"
22 }
23 ]
24 }

The JavaScript file media/plg_editors-xtd_indieweb/js/admin-article-indieweb.js im-


plements code that inserts a specific text pattern by clicking at the insertion point in the editor. In our
case the text is {loadsyndication testurl,testurl2,testurl3}'.
media/plg_editors-xtd_indieweb/js/admin-article-indieweb.js

1 /* https://codeberg.org/astrid/j4examplecode/raw/branch/t30a/src/media/
plg_editors-xtd_indieweb/js/admin-article-indieweb.js */
2
3 (() => {
4
5 const options = window.Joomla.getOptions('xtd-indieweb');
6
7 window.insertIndieweb = editor => {
8 if (!options) {
9 // Something went wrong!
10 throw new Error('XTD Button \'indieweb\' not properly initialized
');
11 }
12
13 const content = window.Joomla.editors.instances[editor].getValue();
14
15 if (!content) {
16 Joomla.editors.instances[editor].replaceSelection('{
loadsyndication testurl,testurl2,testurl3}');
17 } else if (content && !content.match(/{loadsyndication\s/i)) {
18 Joomla.editors.instances[editor].replaceSelection('{
loadsyndication testurl,testurl2,testurl3}');
19 } else {
20 // @todo replace with joomla-alert
21 alert(options.exists);

January 2023 Page 421


Joomla 4 - Developing Extensions 38. Plugins

22 return false;
23 }
24
25 return true;
26 };
27 })();

The file plugins/editors-xtd/indieweb/indieweb.php inserts the button in the editor for the
event onDisplay.

plugins/editors-xtd/indieweb/indieweb.php

1
2 <?php
3
4 use Joomla\CMS\Language\Text;
5 use Joomla\CMS\Object\CMSObject;
6 use Joomla\CMS\Plugin\CMSPlugin;
7
8 \defined('_JEXEC') or die;
9
10 class PlgButtonIndieweb extends CMSPlugin
11 {
12 protected $autoloadLanguage = true;
13
14 protected $app;
15
16 public function onDisplay($name)
17 {
18 $doc = $this->app->getDocument();
19 $doc->getWebAssetManager()
20 ->registerAndUseScript('plg_editors-xtd_indieweb.admin-
article-indieweb', 'plg_editors-xtd_indieweb/admin-
article-indieweb.min.js', [], ['defer' => true], ['core'
]);
21
22 // Pass some data to javascript
23 $doc->addScriptOptions(
24 'xtd-indieweb',
25 [
26 'exists' => Text::_('PLG_EDITORS-
XTD_INDIEWEB_ALREADY_EXISTS', true),
27 ]
28 );
29
30 $button = new CMSObject();
31 $button->modal = false;
32 $button->onclick = 'insertIndieweb(\'' . $name . '\');return
false;';
33 $button->text = Text::_('PLG_EDITORS-
XTD_INDIEWEB_BUTTON_INDIEWEB');

Page 422 January 2023


38. Plugins Joomla 4 - Developing Extensions

34 $button->name = $this->_type . '_' . $this->_name;


35 $button->icon = 'arrow-down';
36 $button->iconSVG = '<svg viewBox="0 0 32 32" width="24" height
="24"><path d="M32 12l-6-6-10 10-10-10-6 6 16 16z"></path></
svg>';
37 $button->link = '#';
38
39 return $button;
40 }
41 }

The installation manifest lists the information and files necessary for installation.

plugins/editors-xtd/indieweb/indieweb.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <extension type="plugin" group="editors-xtd" method="upgrade">
4 <name>plg_editors-xtd_indieweb</name>
5 <author>Astrid Günther</author>
6 <creationDate>[DATE]</creationDate>
7 <author>[AUTHOR]</author>
8 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
9 <authorUrl>[AUTHOR_URL]</authorUrl>
10 <copyright>[COPYRIGHT]</copyright>
11 <license>GNU General Public License version 2 or later;</license>
12 <version>__BUMP_VERSION__</version>
13 <description>PLG_EDITORS-XTD_INDIEWEB_XML_DESCRIPTION</description>
14 <files>
15 <file>indieweb.xml</file>
16 <file plugin="indieweb">indieweb.php</file>
17 <folder>language</folder>
18 </files>
19 </extension>

Two language files complete the implementation.

plugins/editors-xtd/indieweb/language/en-GB/plg_editors-xtd_indieweb.ini

1
2 PLG_EDITORS-XTD_INDIEWEB="Button - IndieWeb Syndication"
3 PLG_EDITORS-XTD_INDIEWEB_XML_DESCRIPTION="Enables a button which allows
you to insert the <em>IndieWeb Syndication &hellip;</em> link into
an Article. See Content Plugin Indieweb"
4 PLG_EDITORS-XTD_INDIEWEB_ALREADY_EXISTS="There is already a IndieWeb
Syndication link that has been inserted. Only one link is permitted.
"
5 PLG_EDITORS-XTD_INDIEWEB_BUTTON_INDIEWEB="IndieWeb Syndications"

plugins/editors-xtd/indieweb/language/en-GB/plg_editors-xtd_indieweb.sys.ini

January 2023 Page 423


Joomla 4 - Developing Extensions 38. Plugins

1
2 PLG_EDITORS-XTD_INDIEWEB="Button - IndieWeb Syndication"
3 PLG_EDITORS-XTD_INDIEWEB_XML_DESCRIPTION="Enables a button which allows
you to insert the <em>IndieWeb Syndication &hellip;</em> link into
an Article. See Content Plugin Indieweb"

38.4. FAQ about plugins

38.4.1. Activate plugin automatically during installation

Do you want your plugin to be activated automatically during an installation? In that case, add the
following code7 to an installation script.

1 defined('_JEXEC') || die;
2
3 use Joomla\CMS\Factory;
4 use Joomla\CMS\Installer\Adapter\PluginAdapter;
5 use Joomla\CMS\Installer\InstallerScript;
6
7 class plgYourplugintypYourpluginnameInstallerScript extends
InstallerScript
8 {
9 public function postflight($type, PluginAdapter $parent)
10 {
11 // Enable the plugin
12 if ($type === 'install' || $type === 'discover_install') {
13 $db = Factory::getDbo();
14 $query = $db->getQuery(true)
15 ->update('#__extensions')
16 ->set($db->qn('enabled') . ' = 1')
17 ->where($db->qn('type') . ' = ' . $db->q('plugin'))
18 ->where($db->qn('element') . ' = ' . $db->q('yourpluginname'))
19 ->where($db->qn('folder') . ' = ' . $db->q('yourplugintyp'));
20 $db->setQuery($query);
21 try {
22 $db->execute();
23 } catch (\Exception $e) {
24 // var_dump($e);
25 }
26 }
27 }
28 }

7
github.com/dgrammatiko/jailed-fs/blob/main/src/plugins/system/restrictedfs/script.php

Page 424 January 2023


Joomla 4 - Developing Extensions

Part III.

Module

Page 425 January 2023


Joomla 4 - Developing Extensions 39. First Steps

39. First Steps

We create a module.H This is an add-on that extends the display of the actual content. It is used when
a content is not the main content and is displayed in different positions. Besides, it is possible to select
the menu items under which the module is visible.

In Joomla, there are a variety of modules that I use as a guide. For example:

• Menus (mod_menu)
• Login form (mod_login)
• and many more.

This section explains how you create the basic framework for a simple module. In the first step it only
outputs a text. We will build on this in the further course.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t30...t31

39.1. Step by step

In this section we will add a module. There are some basic files that are used in the standard module
development pattern. We create these in this part.

39.1.1. New files

39.1.1.1. Module

39.1.1.1.1. modules/mod_foo/ language/en-GB/en-GB.mod_foo.ini This file provides the texts for


for general translation.

1
2 MOD_FOO="[PROJECT_NAME]"
3 MOD_FOO_XML_DESCRIPTION="Foo Module"

Page 427 January 2023


Joomla 4 - Developing Extensions 39. First Steps

39.1.1.1.2. modules/mod_foo/ language/en-GB/en-GB.mod_foo.sys.ini This file provides the


texts for menu and installation routine.

1
2 MOD_FOO="[PROJECT_NAME]"
3 MOD_FOO_XML_DESCRIPTION="Foo Module"

39.1.1.1.3. modules/mod_foo/ mod_foo.php mod_foo.php is the main entry point into the mod-
ule. The file executes the initialization routines, calls helper routines to collect all the required data,
and calls the template where the module output is displayed.

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Helper\ModuleHelper;
7
8 require ModuleHelper::getLayoutPath('mod_foo', $params->get('layout', '
default'));

In Joomla 3x a line like $ moduleclass_sfx = htmlspecialchars ($ params-> get (


'moduleclass_sfx')); was necessary. This line is no longer required. See PR 17447.

39.1.1.1.4. modules/mod_foo/ mod_foo.xml mod_foo.xml defines the files that are copied by the
installation routine and specifies configuration parameters for the module. You already know this from
the previously created extensions.

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <extension type="module" client="site" method="upgrade">
4 <name>MOD_FOO</name>
5 <creationDate>[DATE]</creationDate>
6 <author>[AUTHOR]</author>
7 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
8 <authorUrl>[AUTHOR_URL]</authorUrl>
9 <copyright>[COPYRIGHT]</copyright>
10 <license>GNU General Public License version 2 or later; see LICENSE
.txt</license>
11 <version>__BUMP_VERSION__</version>
12 <description>MOD_FOO_XML_DESCRIPTION</description>
13
14 <files>
15 <filename module="mod_foo">mod_foo.php</filename>
16 <folder>tmpl</folder>
17 <folder>language</folder>

Page 428 January 2023


39. First Steps Joomla 4 - Developing Extensions

18 <filename>mod_foo.xml</filename>
19 </files>
20 </extension>

39.1.1.1.5. modules/mod_foo/tmpl/default.php default.php is the template. This file takes the


data collected by mod_foo.php and generates the HTML code that is displayed on the page. echo '[
PROJECT_NAME]'; ensures that the name of the project is displayed in the frontend at the position
where the module is published.

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 echo '[PROJECT_NAME]';

Note: In the template file it is possible to use all variables defined in mod_foo.php.

39.1.2. Changed files

There are no changed files.

39.2. Test your Joomla module

1. install your module in Joomla version 4 to test it. In the beginning, the easiest thing to do is to
copy the files manually in place:

Copy the files in the modules folder to the modules folder of your Joomla 4 installation.

2. install your module as described in part one, after you have copied all files. Open the menu
System | Install | Discover. Here you will see an entry for the module you just copied.
Select it and click on the button Install. 3.

Next, test if your module works properly. Open the menu “Content | Site Modules” and click in the
toolbar New.

January 2023 Page 429


Joomla 4 - Developing Extensions 39. First Steps

Figure 39.1.: Test Joomla Module

Figure 39.2.: Test Joomla Module

4. enter a title in the appropriate field and choose a position. In the tab “Menu Assignment” make
sure that the module is displayed on all pages. At the end click the button Save in the toolbar.

Page 430 January 2023


39. First Steps Joomla 4 - Developing Extensions

Figure 39.3.: Create Joomla Module

5. That’s it. Switch to the frontend view and make sure that everything is displayed correctly.

Figure 39.4.: Joomla module in frontend

Alternatively it is possible to insert the module into a post.

January 2023 Page 431


Joomla 4 - Developing Extensions 39. First Steps

Figure 39.5.: Joomla module in frontend

We have a solid basis for the further steps in the development of the module.

39.3. Links

Joomla Dokumentation1

1
docs.joomla.org/j4.x:creating_a_simple_module

Page 432 January 2023


Joomla 4 - Developing Extensions 40. Namespaces and Helper

40. Namespaces and Helper

We add namespace and helper.

For impatient people: View the changed program code in the Diff Viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t31...t32

40.1. Step by step

40.1.1. New files

40.1.1.1. Modules

The logic in the module may be complex. Therefore it is good to structure the code clearly. This is done
by jnnhelper files. We create these in the directory Helper.

40.1.1.1.1. modules/mod_foo/ Helper/FooHelper.php

I named the file FooHelper in general. Good style is to give it a speaking name. Each helper
file has a specific task and it should be named after it. For example, the file that loads the latest
articles is called ArticlesLatestHelper. This way you can see at first sight what is in the file.

To access the file easily, we add the namespace namespace FooNamespace\Module\Foo\Site\


Helper;.

modules/mod_foo/ Helper/FooHelper.php

1
2 <?php
3
4 namespace FooNamespace\Module\Foo\Site\Helper;
5
6 \defined('_JEXEC') or die;
7
8 class FooHelper

Page 433 January 2023


Joomla 4 - Developing Extensions 40. Namespaces and Helper

9 {
10 public static function getText()
11 {
12 return 'FooHelpertest';
13 }
14 }

40.1.2. Modified files

40.1.2.0.1. modules/mod_foo/ mod_foo.php To use the contents of FooHelper in the mod_foo


.php entry point, we import them using use FooNamespace\Module\Foo\Site\Helper\
FooHelper;. Then we call the function FooHelper::getText() and store the result in the variable
$test.

1 \defined('_JEXEC') or die;
2
3 use Joomla\CMS\Helper\ModuleHelper;
4 +use FooNamespace\Module\Foo\Site\Helper\FooHelper;
5 +
6 +$test = FooHelper::getText();
7
8 require ModuleHelper::getLayoutPath('mod_foo', $params->get('layout',
'default'));

40.1.2.0.2. modules/mod_foo/ mod_foo.xml We enter the namespace in the manifest. This way
it will be registered in Joomla during the installation. We also add the new directory so that it is copied
to the right place during installation.

1 <license>GNU General Public License version 2 or later; see LICENSE


.txt</license>
2 <version>__BUMP_VERSION__</version>
3 <description>MOD_FOO_XML_DESCRIPTION</description>
4 -
5 + <namespace>FooNamespace\Module\Foo</namespace>
6 <files>
7 <filename module="mod_foo">mod_foo.php</filename>
8 <folder>tmpl</folder>
9 + <folder>Helper</folder>
10 <folder>language</folder>
11 <filename>mod_foo.xml</filename>
12 </files>

40.1.2.0.3. modules/mod_foo/tmpl/default.php In the layout, we finally access the variable. The


logic for calculating the variable value is encapsulated. This keeps the layout clear. We only insert the

Page 434 January 2023


40. Namespaces and Helper Joomla 4 - Developing Extensions

text $test here. If we want to know more about what is behind $test, we look in the helper.

1 \defined('_JEXEC') or die;
2
3 -echo '[PROJECT_NAME]';
4 +echo '[PROJECT_NAME]' . $test;

40.2. Test your Joomla module

1. install the module in Joomla version 4 to test it:

Copy the files in the modules folder into the modules folder of your Joomla 4 installation.

Install your module as described in part one, after copying all files. Joomla will update the namespaces
for you during the installation. Since a file and namespaces have been added, this is necessary.

2. Check whether the text calculated via the function FooHelper::getText() is displayed in the
frontend.

40.3. Links

Joomla Dokumentation1

1
docs.joomla.org/j4.x:creating_a_simple_module

January 2023 Page 435


Joomla 4 - Developing Extensions 41. Parameter

41. Parameter

Via Parameter, the Joomla module can be flexibly adapted for end users. Parameters are variables
through which Joomla is set to process certain values. In other words, parameters are influencing
factors set externally to the programme. They are used to tell the module externally which data should
be processed and how.

For impatient people: Look at the changed programme code in the Diff Viewa and copy these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t32...t33

41.1. Step by step

41.1.1. New files

In this section we add parameters to the module.

41.1.2. New files

In this part, only files have been changed. There are no new files.

41.1.3. Changed files

41.1.3.1. Module

41.1.3.1.1. modules/mod_foo/ language/en-GB/en-GB.mod_foo.ini The labelling of the parame-


ter in the backend should adapt to the active language. For this reason we use the language file.

Are you wondering about the prefix COM_ in COM_MODULES_FOOPARAMS_FIELDSET_LABEL? The


language string is automatically created by Joomla because a fieldset called fooparams is added.

modules/mod_foo/ language/en-GB/en-GB.mod_foo.ini

Page 437 January 2023


Joomla 4 - Developing Extensions 41. Parameter

1 MOD_FOO="[PROJECT_NAME]"
2 MOD_FOO_XML_DESCRIPTION="Foo Module"
3 +MOD_FOO_FIELD_URL_LABEL="URL"
4 +COM_MODULES_FOOPARAMS_FIELDSET_LABEL="Foo Parameter"

41.1.3.1.2. modules/mod_foo/ mod_foo.php In the module’s initial file modules/mod_foo/


mod_foo.php, we check which value the parameter is set to and store it in a variable. This way,
uncomplicated access is possible later.

modules/mod_foo/ mod_foo.php

1 $test = FooHelper::getText();
2
3 +$url = $params->get('domain');
4 +
5 require ModuleHelper::getLayoutPath('mod_foo', $params->get('layout',
'default'));

41.1.3.1.3. modules/mod_foo/ mod_foo.xml In the manifest we add the new parameter so that it
is editable in the Joomla backend.

modules/mod_foo/ mod_foo.xml

1 <folder>language</folder>
2 <filename>mod_foo.xml</filename>
3 </files>
4 + <config>
5 + <fields name="params">
6 + <fieldset name="fooparams">
7 + <field
8 + name="domain"
9 + type="url"
10 + label="MOD_FOO_FIELD_URL_LABEL"
11 + filter="url"
12 + />
13 + </fieldset>
14 + </fields>
15 + </config>
16 </extension>

Use <fieldset name="basic"> to display the parameters in the first tab that opens immedi-
ately.

In addition to the parameters that a developer inserts into his module, there are standard parameters

Page 438 January 2023


41. Parameter Joomla 4 - Developing Extensions

that Joomla handles itself. For example /administrator/components/com_modules/forms/


advanced.xml.

Figure 41.1.: Joomla Module Parameters

41.1.3.1.4. modules/mod_foo/tmpl/default.php In the module’s own template file, we can now


access the value of the parameter. In the following example, we output the value as text. Usually a
parameter is used in a more complex way, for example within control structures such as if-statements
or loops.

An example of the more complex use of a parameter is a digital map where parameters are used
to enable controls such as locate me or a choice of map type.

modules/mod_foo/tmpl/default.php

1 \defined('_JEXEC') or die;
2
3 -echo '[PROJECT_NAME]' . $test;
4 +echo '[PROJECT_NAME]' . $test . '<br />' . $url;

41.2. Test your Joomla module

1. install the module in Joomla version 4 to test it:

Copy the files in the modules folder into the modules folder of your Joomla 4 installation.

January 2023 Page 439


Joomla 4 - Developing Extensions 41. Parameter

A new installation is not necessary. Continue using the files from the previous part.

2. Check the presence of the parameter in the backend.

Figure 41.2.: Test Joomla Module

3. make sure that the value of the parameter is taken into account in the frontend display.

Figure 41.3.: Test Joomla Module

Page 440 January 2023


41. Parameter Joomla 4 - Developing Extensions

41.3. Links

Joomla Dokumentation1

1
docs.joomla.org/J4.x:Creating_a_Simple_Module

January 2023 Page 441


Joomla 4 - Developing Extensions 42. Installation script

42. Installation script

In this chapter we add an installation script. In the explanations of the component, I described what
you use it for.

For impatient people: Look at the changed program code in the diff viewa and copy these changes
into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t33...t34

42.1. Step by step

In this section we will create a script that will be executed on specific events during the installation.

42.1.1. New fiels

42.1.1.1. Module

42.1.1.1.1. modules/mod_foo/ script.php Using the example of the script file, I show that many
things are applied in the same way in the case of a module as in the case of a component.

You can use many things in the module in the same way as in the component. For example, the
update server, the changelog, help pages.

The point is to clarify the procedure. That’s why this script file only takes care of setting minimum
requirements and outputting texts. There are no limits to your imagination to extend this file.

modules/mod_foo/script.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 use Joomla\CMS\Language\Text;
7 use Joomla\CMS\Log\Log;

Page 443 January 2023


Joomla 4 - Developing Extensions 42. Installation script

8
9 class mod_fooInstallerScript
10 {
11
12 public function __construct()
13 {
14 $this->minimumJoomla = '4.0';
15 $this->minimumPhp = JOOMLA_MINIMUM_PHP;
16 }
17
18 function install($parent)
19 {
20 echo Text::_('MOD_FOO_INSTALLERSCRIPT_INSTALL');
21
22 return true;
23 }
24
25 function uninstall($parent)
26 {
27 echo Text::_('MOD_FOO_INSTALLERSCRIPT_UNINSTALL');
28
29 return true;
30 }
31
32 function update($parent)
33 {
34 echo Text::_('MOD_FOO_INSTALLERSCRIPT_UPDATE');
35
36 return true;
37 }
38
39 function preflight($type, $parent)
40 {
41 // Check for the minimum PHP version before continuing
42 if (!empty($this->minimumPhp) && version_compare(PHP_VERSION,
$this->minimumPhp, '<')) {
43 Log::add(Text::sprintf('JLIB_INSTALLER_MINIMUM_PHP', $this
->minimumPhp), Log::WARNING, 'jerror');
44
45 return false;
46 }
47
48 // Check for the minimum Joomla version before continuing
49 if (!empty($this->minimumJoomla) && version_compare(JVERSION,
$this->minimumJoomla, '<')) {
50 Log::add(Text::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA',
$this->minimumJoomla), Log::WARNING, 'jerror');
51
52 return false;
53 }
54

Page 444 January 2023


42. Installation script Joomla 4 - Developing Extensions

55 echo Text::_('MOD_FOO_INSTALLERSCRIPT_PREFLIGHT');
56
57 return true;
58 }
59
60 function postflight($type, $parent)
61 {
62 echo Text::_('MOD_FOO_INSTALLERSCRIPT_POSTFLIGHT');
63
64 return true;
65 }
66 }

42.1.2. Modified files

42.1.2.1. Module

42.1.2.1.1. src/modules/mod_foo/language/en-GB/en-GB.mod_foo.sys.ini The language strings


for the display of the language strings in the active language, we insert into the en-GB.mod_foo.
sys.ini. Yes, this time the language file with the extension *.sys.ini, because the texts are used
during the installation.

src/modules/mod_foo/language/en-GB/en-GB.mod_foo.sys.ini

1 MOD_FOO="[PROJECT_NAME]"
2 MOD_FOO_XML_DESCRIPTION="Foo Module"
3 + MOD_FOO_INSTALLERSCRIPT_PREFLIGHT="<p>Anything here happens before
the + installation/update/uninstallation of the module</p>"
4 + MOD_FOO_INSTALLERSCRIPT_UPDATE="<p>The module has been updated</p>"
5 + MOD_FOO_INSTALLERSCRIPT_UNINSTALL="<p>The module has been uninstalled
</p>"
6 + MOD_FOO_INSTALLERSCRIPT_INSTALL="<p>The module has been installed</p>
"
7 + MOD_FOO_INSTALLERSCRIPT_POSTFLIGHT="<p>Anything here happens after
the installation/update/uninstallation of the module</p>"

42.1.2.1.2. modules/mod_foo/ mod_foo.xml Finally, we enter the name of the script file in the
manifest so that the installation routine will copy it to the right place and call it.

modules/mod_foo/ mod_foo.xml

1<license>GNU General Public License version 2 or later; see LICENSE.txt


</license>
2 <version>__BUMP_VERSION__</version>
3 <description>MOD_FOO_XML_DESCRIPTION</description>
4 + <scriptfile>script.php</scriptfile>

January 2023 Page 445


Joomla 4 - Developing Extensions 42. Installation script

5 <namespace>FooNamespace\Module\Foo</namespace>
6 <files>
7 <filename module="mod_foo">mod_foo.php</filename

42.2. Test your Joomla modules

1. Create a new Installation. Uninstall your previous installation and copy all files again.

Copy the files in the modules folder into the modules folder of your Joomla 4 installation.

Install your module as described in part one, after you have copied all the files. Joomla will execute
the script file for you during the installation. Convince yourself of this by checking the output of the
language strings.

Figure 42.1.: Testing the Joomla Module - The Installation Script

Page 446 January 2023


42. Installation script Joomla 4 - Developing Extensions

Figure 42.2.: Testing the Joomla Module - The Installation Script

42.3. Links

Joomla Dokumentation1

1
docs.joomla.org/j4.x:creating_a_simple_module

January 2023 Page 447


Joomla 4 - Developing Extensions

Part IV.

Template

Page 449 January 2023


Joomla 4 - Developing Extensions 43. First Steps

43. First Steps

Why should you create your own Joomla template? There are a few good reasons why we should make
this happen!

Especially for extension developers, I think it is essential to know how a Joomla template works.
This makes it possible to integrate the separation of logic and design into the extension.

• Creating our own Joomla template means that we have complete control over every last detail
of the look and feel of the website. We only create code that we like. It is much easier to change
a custom template than a complex Joomla template, where often the different elements are
attached to each other.
• Creating our own template means that we don’t overload the website with functions that we
don’t even use.
• If we want a custom Joomla template that is not used by thousands of other websites, creating
one is an option.
• If you have never created a Joomla template before, you will learn a lot about Joomla while
developing it. You will end up knowing a lot about the interaction of the different elements and
feel more confident.

This is not about learning HTML and CSS. That’s why I will use a ready-made HTML5 template
in this article. Follow my example and you will be able to create a complete Joomla template
yourself in the end. You develop HTML and CSS yourself or use a template like I did here.

A template is responsible for the design of the website. There are two types of templates in Joomla:

• Front-End-Templates and
• back-end templates.

We create a front-end template. This controls the way the website is presented to the user.

The principle for creating a template for the administration area is exactly the same. You create it
in the subdirectory /administrator/templates. You create the front-end template in the folder
/templates.

Page 451 January 2023


Joomla 4 - Developing Extensions 43. First Steps

For impatient people: Take a look at the changed programme code in the Diff-Ansichta and copy
these changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t34...t35

43.1. Step by step

With the template it is also so that you do not reinvent the wheel. You can use many things that Joomla
provides by default. This has advantages. The disadvantage is that individual wishes are more difficult
to implement, or rather Joomla knowledge is a necessary prerequisite. Therefore we start rudimentary.
The main thing is to look behind the functions and understand them.

43.1.1. New files

43.1.1.1. Template

This part will guide you through the necessary steps to create a Joomla template - from scratch.

43.1.1.1.1. templates/facile/component.php The component.php provides the logic for a reduced


version of the site. This means that only the pure view of the component is displayed.

This is ideal for printer-friendly output or display in a modal window.

To further explain: As already mentioned, a component is responsible for the display of the main
content. The entire layout, for example the modules in a sidebar and the navigation are accessories.
The file component.php sets the focus on the main content.

Would you like to see the output of the file component.php? This view is displayed in the
browser if you append tmpl=component to the URL - for example like this: /index.php?tmpl
=component.

We create the file component.php here for the sake of completeness and add the text Component.
This way it is possible to test it later.

templates/facile/component.php

1
2 Component

Page 452 January 2023


43. First Steps Joomla 4 - Developing Extensions

In the Joomla 4 standard template Cassiopeia the file component.php is also implemented. You
can use it as a guide. It loads all essential content to display the area marked in the image below
independently.

Figure 43.1.: Joomla Komponentenbereich im Standardtemplate Cassiopeia

43.1.1.1.2. templates/facile/error.php When website visitors call a page that does not exist, they
receive an error message. Joomla’s error message is generic. It is much better to create your own
individual error page.
In my opinion, a good error page includes:

• Minimalist design: express yourself with simple texts and clear images. Write only what is
necessary. Less is more!
• Link to the homepage: Describe clearly how to reach the homepage and put a link to it. An
additional link, for example in the logo, is helpful. But it should not be the only way to get back
to the homepage.
• A search: Offer the visitor a search field. He will know what he wants to see. A search field is used
because it offers an option to find it. Besides, it keeps him on your website.
• No technical terms: 404 Error is completely meaningless to many people.

In my opinion, the error page should not blame visitors. After all, it’s not their fault if a page
doesn’t exist or an internal server error occurs.

To let you know how and where to implement your error page, I created the file templates/facile/
error.php. This contains nothing more than the word Error. So it is possible to test the page. Let

January 2023 Page 453


Joomla 4 - Developing Extensions 43. First Steps

your imagination run free with the content and design of your own individual error page.

templates/facile/error.php

1
2 Error

In the Joomla 4 standard template Cassiopeia the file error.php is also implemented. You can use it
as a guide. It loads all essential content to display the information in the following screen. In case of an
invalid URL a user will see the following view.

Figure 43.2.: Joomla Error Datei im Standardtemplate Cassiopeia

43.1.1.1.3. templates/facile/index.php The file index.php is the heart. It ensures that everything
works together. The following code snippet shows you a minimal structure.

templates/facile/index.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5 ?>
6
7 <!DOCTYPE html>
8 <html lang="de">
9 <head>
10 <meta charset="utf-8">
11 <meta name="viewport" content="width=device-width, initial-scale
=1.0">

Page 454 January 2023


43. First Steps Joomla 4 - Developing Extensions

12 <title>Titel</title>
13 </head>
14 <body>
15 Hallo Joomla!
16 </body>
17 </html>

The first line \defined('_JEXEC')or die; is written in PHP. The good thing about PHP and HTML
is that it can be written together in one file. We can put PHP statements into an HMTL file, and vice
versa. <?php opens a PHP statement - anywhere - and ?> closes it. With the line \defined('_JEXEC
')or die; we forbid direct access to this file. This is done through the Joomla API with the _JEXEC
command. This statement ensures that the file is accessed from within a Joomla session. If not, the
processing aborts ... or die;. This is how Joomla makes it harder for a hacker to inject malicious
code.

Then we declare the document type1 with <!doctype html>. This ensures that the document is
parsed the same way by different browsers. HTML5 is the simplest and most reliable doctype declara-
tion. This is what we use.
Note that the doctype is simply !DOCTYPE html and not !DOCTYPE html5.

What follows is the smallest possible structure of an HTML page. This page opens with <html> and
ends with </html>. The header starts with <head> and ends with </head>. The body starts with
<body> and ends with </body>.

Enough explanation. This is how the website looks minimally. It does not yet load any content from
Joomla My main point here was to show that the index.php of the active template is responsible for
everything. In our case, this is the file templates/facile/index.php. So far, the responsibility is
limited. Only the (German) greeting Hallo Joomla is displayed on the screen.

43.1.1.1.4. templates/facile/language/en-GB/en-GB.tpl_facile.ini The language file templates


/facile/language/en-GB/en-GB.tpl_facile.ini ensures that the name is displayed in the
backend in the correct language when managing the extensions.

templates/facile/language/en-GB/en-GB.tpl_facile.ini

1
2 TPL_FACILE_XML_DESCRIPTION="Facile is a Joomla 4 template."

43.1.1.1.5. templates/facile/language/en-GB/en-GB.tpl_facile.sys.ini The language file


templates/facile/language/en-GB/en-GB.tpl_facile.sys.ini translates the texts
1
w3.org/qa/2002/04/valid-dtd-list.html

January 2023 Page 455


Joomla 4 - Developing Extensions 43. First Steps

in the menu or during the installation into the correct language.

templates/facile/language/en-GB/en-GB.tpl_facile.sys.ini

1
2 FACILE="Facile - Site template"

43.1.1.1.6. templates/facile/offline.php The file offline.php is called when the maintenance


mode is activated in the backend. You activate this in the global configuration.

Figure 43.3.: Create Joomla Template - Offline Page Backend

To keep the website technically up to date or to integrate new features, it will be revised from
time to time. Mostly these are updates. During the update, display problems may occur. So that
visitors are not irritated by an error message, Joomla has a maintenance mode. If this is active a
special maintenance mode page is shown to the visitor, the offline.php.

The following minimalist code will display a registration form. You could display a short text instead.
The login form allows an administrator to authenticate and then test the site via frontend.

Page 456 January 2023


43. First Steps Joomla 4 - Developing Extensions

Figure 43.4.: Joomla Template erstellen - Offline Seite Frontend

templates/facile/offline.php

1
2 <?php
3
4 defined('_JEXEC') or die;
5
6 use Joomla\CMS\Helper\AuthenticationHelper;
7 use Joomla\CMS\HTML\HTMLHelper;
8 use Joomla\CMS\Language\Text;
9 use Joomla\CMS\Router\Route;
10 use Joomla\CMS\Uri\Uri;
11
12 $twofactormethods = AuthenticationHelper::getTwoFactorMethods();
13 ?>
14
15 <!DOCTYPE html>
16 <html lang="<?php echo $this->language; ?>">
17 <head>
18 <meta name="viewport" content="width=device-width, initial-scale
=1.0">
19 <jdoc:include type="head" />
20 </head>
21 <body>
22 <jdoc:include type="message" />
23 <form action="<?php echo Route::_('index.php', true); ?>" method="
post" id="form-login">
24 <fieldset>
25 <label for="username"><?php echo Text::_('JGLOBAL_USERNAME'

January 2023 Page 457


Joomla 4 - Developing Extensions 43. First Steps

); ?></label>
26 <input name="username" id="username" type="text">
27
28 <label for="password"><?php echo Text::_('JGLOBAL_PASSWORD'
); ?></label>
29 <input name="password" id="password" type="password">
30
31 <?php if (count($twofactormethods) > 1) : ?>
32 <label for="secretkey"><?php echo Text::_('
JGLOBAL_SECRETKEY'); ?></label>
33 <input name="secretkey" autocomplete="one-time-code" id="
secretkey" type="text">
34 <?php endif; ?>
35
36 <input type="submit" name="Submit" value="<?php echo Text::
_('JLOGIN'); ?>">
37
38 <input type="hidden" name="option" value="com_users">
39 <input type="hidden" name="task" value="user.login">
40 <input type="hidden" name="return" value="<?php echo
base64_encode(Uri::base()); ?>">
41 <?php echo HTMLHelper::_('form.token'); ?>
42 </fieldset>
43 </form>
44 </body>
45 </html>

With the login option you have now integrated the necessary function. You surely want to make your
maintenance page more attractive. There is a lot of inspiration available in the Internet. The easiest
way is to orientate yourself on the following example from the standard template Cassiopeia:

Page 458 January 2023


43. First Steps Joomla 4 - Developing Extensions

Figure 43.5.: Joomla Offline.php in default template Cassiopeia

43.1.1.1.7. templates/facile/templateDetails.xml The file templateDetails.xml (note the big


D) is the second most important file after index.php. It contains general information like name and
author and defines everything important for the installation. This is mainly a listing of all folders and
files that belong to the template. These will be unpacked during the installation and stored in the
correct directories.

In the file templateDetails.xml the module positions are usually created and included into the
website via the command jdoc:include in the index.php. We will do this in a later part. Optionally
we can create parameters to make the template customizable via backend. In the further course of
this text I have included logoFile, siteTitle and siteDescription as parameters. But first, let’s
look at a minimal version of templateDetails.xml in the following code snippet.

src/templates/facile/templateDetails.xml

1
2 <?xml version="1.0" encoding="utf-8"?>
3 <extension type="template" client="site" method="upgrade">
4 <name>facile</name>
5 <creationDate>[DATE]</creationDate>
6 <author>[AUTHOR]</author>
7 <authorEmail>[AUTHOR_EMAIL]</authorEmail>
8 <authorUrl>[AUTHOR_URL]</authorUrl>
9 <copyright>[COPYRIGHT]</copyright>
10 <license>GNU General Public License version 2 or later;</license>
11 <version>__BUMP_VERSION__</version>
12 <description>TPL_FACILE_XML_DESCRIPTION</description>

January 2023 Page 459


Joomla 4 - Developing Extensions 43. First Steps

13
14 <files>
15 <filename>component.php</filename>
16 <filename>error.php</filename>
17 <filename>index.php</filename>
18 <filename>offline.php</filename>
19 <filename>templateDetails.xml</filename>
20 <filename>template_preview.png</filename>
21 <filename>template_thumbnail.png</filename>
22 <folder>language</folder>
23 </files>
24 </extension>

What does this code mean exactly? XML documents should start with an XML declaration2 , but they
don’t have to. We create the declaration and specify XML version and charset (utf-8) here <?xml
version="1.0"encoding="utf-8"?>.

The other part of templateDetails.xml contains information for the installation. The type is
template in case of a template. The method="upgrade" allows to install the template at a later
time over a previous version.

What is important about method="upgrade": It installs newer versions of the files. Old files
that are no longer needed, however, remain. So they are not deleted. If you want to specifically
ensure that your extension does not contain unnecessary files for users, this have to be explicitly
implemented in an installation script.

Next comes the general information of the template such as

• Template name,
• creation date,
• author, copyright,
• e-mail address, website,
• version and
• Description)

These will be displayed later in the template manager of the Joomla backend.

After that the installation routine is listed. Folders (<folder>) and files (<filename>) belonging to
the template. The HTML tag <positions> comes afterwards. We will add this later. Each position is
written in a separate line and is now ready to be included in the index.php and is thus selectable via
the module manager in the Joomla backend.

2
en.wikipedia.org/wiki/xhtml#xml_declaration

Page 460 January 2023


43. First Steps Joomla 4 - Developing Extensions

For more information on the templateDetails.xml file, see the Joomla documentation
docs.joomls.orga .
a
en.wikipedia.org/wiki/xhtml#xml_declaration

43.1.1.1.8. template_preview.png und template_thumbnail.png The two PNG src/templates


/facile/template_preview.png and src/templates/facile/template_thumbnail.png
files added in this chapter are the images that will be displayed in the Template Manager.

Figure 43.6.: Create Joomla Template - Images

43.1.2. Changed files

Only files have been added in this section.

43.2. Test your Joomla template

1. install your template in Joomla version 4 to test it. In the beginning, the easiest thing to do is to
copy the files manually in place:

Copy the files from the templates folder into the templates folder of your Joomla 4 installation.

January 2023 Page 461


Joomla 4 - Developing Extensions 43. First Steps

2. install your template as described in part one, after you have copied all files. Open the menu
System | Install | Discover. Here you will see an entry for the template you just copied.
Select it and click on the button “Install”.

Figure 43.7.: Create Joomla Template - The Installation

Next, test whether the template works without errors. Activate the Template Style Facile.

Figure 43.8.: Create Joomla Template - Activate Template Style

Page 462 January 2023


43. First Steps Joomla 4 - Developing Extensions

5. call the URL /index.php. Open the frontend view.

Figure 43.9.: Create Joomla Template - Frontend View

6. test the simple error page. To do this, enter a URL in the address field of the browser that does
not exist. For example call the URL /indexabcxyz.php. You should see the text Error.

Figure 43.10.: Create Joomla Template - Error Page

7. look at the output of the file templates/facile/component.php by typing /index.php?

January 2023 Page 463


Joomla 4 - Developing Extensions 43. First Steps

tmpl=component in the address bar of the browser. You should see the text Component.

43.3. Links

Joomla 4 Template Lightning3

Joomla 4 Template Sloth4

HTML5 UP offers fancy HTML5 website templates5

3
github.com/c-lodder/lightning
4
github.com/dgrammatiko/sloth-pkg
5
html5up.net

Page 464 January 2023


Joomla 4 - Developing Extensions 44. Modul Positions

44. Modul Positions

The template should dynamically display the Joomla content from components, modules and plugins
at different positions. How this goal is achieved in Joomla is the topic of this chapter. So: How are
module positions integrated in the Joomla template.

For impatient people: Take a look at the changed programme code in the Diff-Ansichta and copy
these changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t35...t36

44.1. Step by step

We will proceed step by step. In this part we add the module positions so that Joomla displays content
dynamically. We will take care of the design in the next part.

44.1.1. New files

In this chapter only files have been changed and no new ones have been added.

44.1.2. Changed files

44.1.2.1. Template

So far we have more or less a static website. In this part, we add content dynamically using module
positions.

44.1.2.1.1. templates/facile/component.php I already mentioned it in the previous part: The file


component.php displays only main content. This is the content of type component. Navigation and
content in sidebars are omitted. For testing we had created this file. So far it contained only the static
text Component. Now we extend it by its actual task. A minimal structure looks like this.

templates/facile/component.php

Page 465 January 2023


Joomla 4 - Developing Extensions 44. Modul Positions

1 -Component
2 +<!DOCTYPE html>
3 +<html lang="de">
4 +<head>
5 + <meta name="viewport" content="width=device-width, initial-scale=1"
>
6 + <jdoc:include type="head" />
7 +</head>
8 +<body>
9 + <jdoc:include type="message" />
10 + <jdoc:include type="component" />
11 +</body>
12 +</html>
13 +

The main new entry is <jdoc:include type="component"/>. The command inserts the main
content of the current page.

In addition, we use <jdoc:include type="message"/> and <jdoc:include type="head"/>.


<jdoc:include type="message"/> displays system and error messages that occurred during the
request.

Are you wondering what system and error messages are exactly? In Joomla they are generated
by $this->setMessage(Text::sprintf('MESSAGE_TEXT', $id), 'error'); in an ob-
ject of type BaseControllera . In the file administrator/components/com_content/src/
Controller/DisplayController.php you will find an exampleb . This outputs the text You
are not permitted to use that link to directly access that page. in the fron-
tend. This is exactly where <jdoc:include type="message"/> is inserted in the template.
a
libraries/src/MVC/Controller/BaseController.php#L1066
b
administrator/components/com_content/src/controller/displaycontroller.php#l55

<jdoc:include type="head"/> loads content that requires extensions and includes them via
special commands. These are mainly scripts and styles.

<jdoc:include type="head"/>, and <jdoc:


<jdoc:include type="message"/>
include type="component"/> should appear only once in the <body> element of the
template. More information about the commands can be found in the Joomla documentation
docs.joomla.org/Jdoc_statements/ena .
a
docs.joomla.org/jdoc_statements

44.1.2.1.2. templates/facile/index.php You already know it: The file index.php is the heart of the
template. It makes sure that everything works together. In the previous chapter we had not integrated

Page 466 January 2023


44. Modul Positions Joomla 4 - Developing Extensions

Joomla’s own content. I will make up for this here. A minimal structure, which inserts the Joomla
content, looks like this.

templates/facile/index.php

1 <meta name="viewport" content="width=device-width, initial-scale


=1.0">
2 <title>Titel</title>
3 </head>
4 +
5 <body>
6 - Hallo Joomla
7 - </body>
8 + <header>
9 + <div>
10 + <nav>
11 + <div>
12 + <jdoc:include type="modules" name="topbar" />
13 + </div>
14 +
15 + <div>
16 + <jdoc:include type="modules" name="below-topbar" />
17 + </div>
18 +
19 + <div>
20 + <jdoc:include type="modules" name="menu" />
21 + </div>
22 + </nav>
23 + <div>
24 + <jdoc:include type="modules" name="search" />
25 + </div>
26 + </div>
27 + </header>
28 +
29 + <div>
30 + <jdoc:include type="modules" name="banner" />
31 + </div>
32 +
33 + <div>
34 + <jdoc:include type="modules" name="top-a" />
35 + </div>
36 +
37 + <div>
38 + <jdoc:include type="modules" name="top-b" />
39 + </div>
40 +
41 + <div>
42 + <jdoc:include type="modules" name="sidebar-left" />
43 + </div>
44 +
45 + <div>

January 2023 Page 467


Joomla 4 - Developing Extensions 44. Modul Positions

46 + <jdoc:include type="modules" name="breadcrumbs" />


47 + <jdoc:include type="modules" name="main-top" />
48 + <jdoc:include type="message" />
49 + <main>
50 + <jdoc:include type="component" />
51 + </main>
52 + <jdoc:include type="modules" name="main-bottom" />
53 + </div>
54 +
55 + <div>
56 + <jdoc:include type="modules" name="sidebar-right" />
57 + </div>
58 +
59 + <div>
60 + <jdoc:include type="modules" name="bottom-a" />
61 + </div>
62 +
63 + <div>
64 + <jdoc:include type="modules" name="bottom-b" />
65 + </div>
66 +
67 + <footer>
68 + <jdoc:include type="modules" name="footer" />
69 + </footer>
70 +
71 + <jdoc:include type="modules" name="debug" />
72 +
73 +</body>
74 </html>

Inside the header area Joomla templates load header information with <jdoc: include type
="head"/> via Joomla API. We already use this above in the component.php file. The jdoc:
include command inserts the necessary header information. This way you are on the safe side.
I don‘t use this command in the index.php at the moment, because I want to show, that you can
also choose yourself, what you need.

We can find the jdoc:include command in other places in index.php. For example, we see <jdoc
:include type="message"/>, so the system messages work. Whenever Joomla has something
to tell the website visitor, this line will display it on the screen. For example, when sending an email
through a contact form, you will see the message “Your message was sent successfully”.

Another element to discuss is <jdoc:include type="component"/>. This element inserts the


main content into the site.

The last element worth mentioning is <jdoc:include type="modules"/>. As the name suggests,
this is used to include modules.

So, enough explained. All contents are integrated via module Positions. They are not displayed nicely

Page 468 January 2023


44. Modul Positions Joomla 4 - Developing Extensions

so far. Don’t be scared if you open this version in your browser later. You will see all content in unstyled
form at the moment.
It may be important to you that a module position is only inserted if a module is published under
it, because this makes it easier to prevent the unnecessary setting of HTML elements for a wrapper.
How to achieve this is a topic of the next chapter. Or you want to make it optional in the template
which module position is used. For example, it is important to you that a sidebar can be completely
deactivated.You can achieve this with the help of parameters, which are the subject of the next
but one chapter.

44.1.2.1.3. templates/facile/language/en-GB/en-GB.tpl_facile.sys.ini Via the language files it is


possible to describe the positions exactly. Note the line TPL_FACILE_POSITION_TOP-A="Area
under banner". TOP-A does not mean much to a user. He understands Area under banner.

Figure 44.1.: Create Joomla Template - Name Module Positions

templates/facile/language/en-GB/en-GB.tpl_facile.sys.ini

1
2 FACILE="Facile - Site template"
3 +TPL_FACILE_POSITION_MENU="Menu"
4 +TPL_FACILE_POSITION_SEARCH="Search"
5 +TPL_FACILE_POSITION_BANNER="Banner"
6 +TPL_FACILE_POSITION_TOP-A="Area under banner"
7 +TPL_FACILE_POSITION_TOP-B="Area above the content"
8 +TPL_FACILE_POSITION_MAIN-TOP="Main-top"
9 +TPL_FACILE_POSITION_BREADCRUMBS="Breadcrumbs"

January 2023 Page 469


Joomla 4 - Developing Extensions 44. Modul Positions

10 +TPL_FACILE_POSITION_MAIN-BOTTOM="Main-bottom"
11 +TPL_FACILE_POSITION_SIDEBAR-LEFT="Sidebar-left"
12 +TPL_FACILE_POSITION_SIDEBAR-RIGHT="Sidebar-right"
13 +TPL_FACILE_POSITION_BOTTOM-A="Bottom-a"
14 +TPL_FACILE_POSITION_BOTTOM-B="Bottom-b"
15 +TPL_FACILE_POSITION_FOOTER="Footer"
16 +TPL_FACILE_POSITION_DEBUG="Debug"
17 +TPL_FACILE_POSITION_TOPBAR="Top Bar"
18 +TPL_FACILE_POSITION_BELOW-TOP="Below Top"

44.1.2.1.4. templates/facile/templateDetails.xml In the file templateDetails.xml the module


positions are inserted to be selectable as position when creating a module. So a module is includeable
via the command jdoc:include in the index.php.

src/templates/facile/templateDetails.xml

1
2 <filename>template_thumbnail.png</filename>
3 <folder>language</folder>
4 </files>
5 +
6 + <positions>
7 + <position>topbar</position>
8 + <position>below-top</position>
9 + <position>menu</position>
10 + <position>search</position>
11 + <position>banner</position>
12 + <position>top-a</position>
13 + <position>top-b</position>
14 + <position>main-top</position>
15 + <position>main-bottom</position>
16 + <position>breadcrumbs</position>
17 + <position>sidebar-left</position>
18 + <position>sidebar-right</position>
19 + <position>bottom-a</position>
20 + <position>bottom-b</position>
21 + <position>footer</position>
22 + <position>debug</position>
23 + </positions>
24 </extension>

44.1.3. Changed files

Only files have been added in this section.

Page 470 January 2023


44. Modul Positions Joomla 4 - Developing Extensions

44.2. Test your Joomla template

1. install your template in Joomla version 4 to test it:

Copy the files in the templates folder to the templates folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the ones from the previous part.

3. install the sample data, so that you have the same prerequisites as I have.

Figure 44.2.: Create Joomla Template - Install sample files

4. test now, if the sample files are displayed correctly. Activate the template style Cassiopei and
call the URL joomla-cms4/index.php. How you change a template style, I had shown in the
previous chapter with a picture. Your view should be like in the following picture.

January 2023 Page 471


Joomla 4 - Developing Extensions 44. Modul Positions

Figure 44.3.: Create Joomla Template - View in Cassiopeia

5. next test if our template Facile works without errors. Activate the Template Style Facile and call
the URL joomla-cms4/index.php again. Your view should be like in the following picture.

Figure 44.4.: Create Joomla Template - View Facil ungestyled

You can view the module positions in the frontend. Activate the view in the global configuration in the
backend and call the URL joomla-cms4/index.php?tp=1. The appendage ?tp=1 is crucial.

Page 472 January 2023


44. Modul Positions Joomla 4 - Developing Extensions

Figure 44.5.: Create Joomla Template - Show Module Positions - Backend

Figure 44.6.: Create Joomla Template - Show Module Positions - Frontend

This does not look inviting. I agree with you there. So next we pep up the template with CSS and
JavaScipt and adjust the default views of Joomla.

January 2023 Page 473


Joomla 4 - Developing Extensions 45. Overrides

45. Overrides

In this chapter we will change the output of the extensions in the frontend. In Joomla this is done
using

• overrides,
• alternative overrides,
• layouts and
• module chromes.

The standard output of each Joomla extension can be manipulated via files in the template’s html
folder. Joomla offers different options for this purpose. Overrides, alternative overrides, layouts and
module chromes. Each variant has its purpose.

Overrides are the first choice. If there is already an override for an extension, you create an alternative
override. Layouts override a small area of a view and can be reused in different views. Last but not
least, module chromes offer a variant to use an override in different places slightly modified.

Figure 45.1.: Create Joomla Template - Module Chrome

Page 475 January 2023


Joomla 4 - Developing Extensions 45. Overrides

For impatient people: Take a look at the changed programme code in the Diff-Ansichta and copy
these changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t36...t37

45.1. Step by step

In this section we change the frontend view of com_content/featured, mod_articles_news and


mod_menu. Thereby we use all variants possible in Joomla for overwriting. The template is not finished
with this. There remain a lot of extensions whose view is not adapted.

My Goal: In the end, we will have discussed all override variations so that you can finish the template
or edit your own template according to your needs. Finished will be the home page view of the Joomla
4 blog sample files.

Home page view of the Joomla 4 blog sample in Cassiopeia:

Figure 45.2.: Create Joomla Template - Default Template Cassiopeia

Home page view of the Joomla 4 blog sample in our new template Facile:

Page 476 January 2023


45. Overrides Joomla 4 - Developing Extensions

Figure 45.3.: Create Joomla Template - Template Facile

Overrides can be created comfortably with the help of the template manager. This offers a view that
highlights the differences to Joomla’s own code.

Figure 45.4.: Create Joomla Template - Diff-View

Tip: If you want to change a view only slightly, it is a good idea to take the original view as
a template. Then you can change it. To do this, create a copy of the existing view in the

January 2023 Page 477


Joomla 4 - Developing Extensions 45. Overrides

html directory of the template and edit it. The copy is placed in the template directory,
exactly as the file templates/TEMPLATE_NAME/html/EXTENSION_NAME/VIEW_NAME/
FILE_NAME.php. For example, if you want to change the feature view of com_content,
then copy the file components/com_content/views/feature/tmpl/default.php
to templates/TEMPLATE_NAME/html/com_content/feature/default.php. Simi-
larly, if you want to change the appearance of the mod_article_latest module. Copy
modules/tmpl/mod_articles_news/default.php to templates/TEMPLATE_NAME/
html/mod_articles_news/default.php. Joomla 4 includes the standard frontend template
Cassiopeia. Cassipeia uses template overrides to create the dropdown menu. You can use this as
an example. Open the directory \template\cassiopeia. In the template folder, you will find a
subdirectory called html.

45.1.1. New files

45.1.1.1. Assets directory

I took the design from the HTML5 UP template TXT1 . This tutorial is about Joomla. Explanations
about HTML, SCSS and CSS would go beyond the scope of this post. Therefore I leave them out and
concentrate on Joomla.

45.1.1.1.1. Override com_content/featured/ (including Layout Override) The blog sample


files use the components/com_content/tmpl/com_content/featured/ view as the start page.
The code of this view is complex, at least in Joomla core. I don’t need many of these complex features,
so I’ll limit myself to the essentials. Take a look at the code below. Basically, I go through all the items
with the featured/ property and display them using the default_item.php subtemplate. Joomla
provides me with all the properties of an article in the variable $this->items.

Joomla uses templates and subtemplates like $this->loadTemplate('item') or layouts


like LayoutHelper::render('joomla.content.intro_image', $this->item); to
structure the code clearly. I had already mentioned this with the frontend views of the categories
in the tutorial part for the component. In the following, we will take a practical look at these
functions again. Nachfolgend sehen wir uns diese Funktionen noch einmal praktisch an.

templates/facile/html/com_content/featured/default.php

1
2 <?php
3 defined('_JEXEC') or die;
1
html5up.net/txt

Page 478 January 2023


45. Overrides Joomla 4 - Developing Extensions

4 ?>
5
6 <div>
7 <h1>
8 <?php echo $this->escape($this->params->get('page_heading'));
?>
9 </h1>
10
11 <?php if (!empty($this->items)) : ?>
12 <?php foreach ($this->items as $key => &$item) : ?>
13 <div>
14 <?php
15 $this->item = & $item;
16 echo $this->loadTemplate('item');
17 ?>
18 </div>
19 <?php endforeach; ?>
20 <?php endif; ?>
21 </div>

The file /templates/facile/html/com_content/featured/default.php is an override. It


calls a subtemplate using echo $this->loadTemplate('item');.

templates/facile/html/com_content/featured/default_item.php

The subtemplate templates/facile/html/com_content/featured/default_item.php

• displays an image using the joomla.content.intro_image layout,


• then creates a linked headline and
• outputs the intro text below it.

1
2 <?php
3
4 defined('_JEXEC') or die;
5
6 use Joomla\CMS\Router\Route;
7 use Joomla\Component\Content\Site\Helper\RouteHelper;
8 use Joomla\CMS\Layout\LayoutHelper;
9 ?>
10
11 <?php echo LayoutHelper::render('joomla.content.intro_image', $this->
item); ?>
12
13 <div>
14 <h2>
15 <a href="<?php echo Route::_(RouteHelper::getArticleRoute($this
->item->slug, $this->item->catid, $this->item->language));
?>">
16 <?php echo $this->escape($this->item->title); ?>

January 2023 Page 479


Joomla 4 - Developing Extensions 45. Overrides

17 </a>
18 </h2>
19
20 <?php echo $this->item->introtext; ?>
21 </div>

Joomla first searches for files in the template directory. Therefore we create our own layout later.
We will save it under templates/facile/html/layouts/joomla/content/intro_image.php.
Our own layout shows the image in the correct size. Since the file layouts/joomla/content/
intro_image.php exists directly in the Joomla root directory, it would otherwise be used for the
display. If we had no special requirements, we could make it easy and use this Joomla own layout
layouts/joomla/content/intro_image.php.

If the file templates/facile/html/layouts/joomla/content/intro_image.php did


not exist, next the directory layouts/ joomla/content/ would be searched and the file
intro_image.php there would be used for display.

templates/facile/html/layouts/joomla/content/intro_image.php

The layout joomla.content.intro_image is used in many places in Joomla.

In addition to the override of entire views, Joomla supports the override of smaller code segments,
so-called layouts. Layouts are used by Joomla in various places. For example, to generate the
code that creates the search and sort filters in list views or when displaying post information (such
as author, creation date. . . ) above or below a post.

Because our template is built differently and expects different CSS elements, the display of the image via
joomla.content.intro_image is not optimal. Therefore we overwrite the layout in our template.
Because we want to reuse this, we do it in a way that we can also access our layout in other places via
echo LayoutHelper::render('joomla.content.intro_image', $this->item);. For this
we create the file templates/facile/html/layouts/joomla/content/intro_image.php.

1
2 <?php
3 defined('_JEXEC') or die;
4
5 use Joomla\CMS\HTML\HTMLHelper;
6 use Joomla\Component\Content\Site\Helper\RouteHelper;
7 use Joomla\CMS\Router\Route;
8
9 $images = json_decode($displayData->images);
10 $img = HTMLHelper::cleanImageURL($images->image_intro);
11 $alt = empty($images->image_intro_alt) && empty($images->
image_intro_alt_empty) ? '' : 'alt="'. htmlspecialchars($images->
image_intro_alt, ENT_COMPAT, 'UTF-8') .'"';

Page 480 January 2023


45. Overrides Joomla 4 - Developing Extensions

12 ?>
13
14 <a href="<?php echo Route::_(RouteHelper::getArticleRoute($displayData
->slug, $displayData->catid, $displayData->language)); ?>" class="
image featured">
15 <img src="<?php echo htmlspecialchars($img->url, ENT_COMPAT, 'UTF-8');
?>" alt="<?php echo $alt; ?>" />
16 </a>

Again for comparison: The original Joomla-own file of the layout joomla.content.
intro_image is located in the directory layouts/ joomla/content/intro_image.php.
The special file for our template is saved under templates/facile/html/ + layouts/joomla
/content/intro_image.php.

45.1.1.1.2. Override via Module Chrome mod_articles_news At the top of the home page,
the Joomla Blog sample data displays the module mod_articles_news. We create a standard
override analogous to the view of the main articles in com_content/featured/, in which we in-
clude the items in a subtemplate. The code of the two files mod_articles_news/_item.php and
mod_articles_news/default.php can be found below. These only support the necessary func-
tions and are therefore clearly compact for learning.

templates/facile/html/mod_articles_news/_item.php also contains a layout. joomla


.content.readmore contains the code that creates a readmore link. This is a function that is
used in many views and is therefore a good example of reusability.

templates/facile/html/mod_articles_news/_item.php

1
2 <?php
3
4 defined('_JEXEC') or die;
5
6 use Joomla\CMS\Layout\LayoutHelper;
7 ?>
8
9 <div class="col-4 col-12-medium col-12-small">
10 <section class="box feature">
11 <a href="<?php echo $item->link; ?>" class="image featured"><
img src="<?php echo $item->imageSrc; ?>" alt="<?php echo
$item->imageAlt; ?>"/></a>
12
13 <h3><a href="<?php echo $item->link; ?>"><?php echo $item->
title; ?></a></h3>
14
15 <p>

January 2023 Page 481


Joomla 4 - Developing Extensions 45. Overrides

16 <?php echo $item->introtext; ?>


17 <?php echo LayoutHelper::render('joomla.content.readmore',
['item' => $item, 'params' => $item->params, 'link' =>
$item->link]); ?>
18 </p>
19 </section>
20 </div>

templates/facile/html/mod_articles_news/default.php

1
2 <?php
3
4 defined('_JEXEC') or die;
5
6 use Joomla\CMS\Helper\ModuleHelper;
7
8 if (empty($list)) {
9 return;
10 }
11
12 ?>
13 <div>
14 <div class="row">
15 <!-- Feature -->
16 <?php foreach ($list as $item) : ?>
17 <?php require ModuleHelper::getLayoutPath('
mod_articles_news', '_item'); ?>
18 <?php endforeach; ?>
19 </div>
20 </div>

The override to the module “mod_articles_news” should be displayed in the upper area with a large
headline. On a subpage, it should appear with small headline in the sidebar. We could create a solution
with an alternative override. This variant is the subject of the next section. However, a lot of program
code would be written via alternativen Override redundantly. Actually, only the first line with the
heading is different. And here Joomlas module Chromes comes into play. We create a file in the
directory templates/facile/html/layouts/chromes/ which only contains the different code
and otherwise embeds the module exactly as it is. The latter is taken care of by echo $module->
content;. We can name the modules chrome file anything we want. I have chosen hr.php as name.
In the index.php at the end of this section you can see how to make sure that the hr.php file is
integrated in the header of the page but not in the sidebar.

templates/facile/html/layouts/chromes/hr.php

1
2 <?php
3 defined('_JEXEC') or die;

Page 482 January 2023


45. Overrides Joomla 4 - Developing Extensions

4 $module = $displayData['module'];
5 ?>
6
7 <section class="box features">
8 <h2 class="major"><span>News</span></h2>
9 <?php echo $module->content; ?>
10 </section>

45.1.1.2. The alternative override mod_menu.

There are requirements where the design of a module varies greatly in different places. In this case it
is necessary to create two different files. The file default.php is actually the override. If we create
another file in the directory next to default.php, this is an alternative override. A use case is a menu.
In the header, the main menu often looks quite different from the one in the footer. In our template the
main menu is implemented in the file default.php and the footer menu in the file bottom.php.

Note: The two files differ slightly. In the bottom.php file, the <ul> element must be given the
class menu so that no list item symbols are displayed in the frontend view. This could also be
handled via a Chrome module.

templates/facile/html/mod_menu/default.php

1
2 <?php
3 defined('_JEXEC') or die;
4
5 use Joomla\CMS\Helper\ModuleHelper;
6 ?>
7
8 <ul>
9 <?php foreach ($list as $i => &$item) {
10 $itemParams = $item->getParams();
11 $class = '';
12
13 if ($item->id == $active_id) {
14 $class .= ' current';
15 }
16
17 echo '<li class="' . $class . '">';
18
19 require ModuleHelper::getLayoutPath('mod_menu', 'default_url');
20
21 // The next item is deeper.
22 if ($item->deeper) {
23 echo '<ul>';
24 }
25 // The next item is shallower.

January 2023 Page 483


Joomla 4 - Developing Extensions 45. Overrides

26 else if ($item->shallower) {
27 echo '</li>';
28 echo str_repeat('</ul></li>', $item->level_diff);
29 }
30 // The next item is on the same level.
31 else {
32 echo '</li>';
33 }
34 }
35 ?></ul>

templates/facile/html/mod_menu/bottom.php

1
2 <?php
3 defined('_JEXEC') or die;
4
5 use Joomla\CMS\Helper\ModuleHelper;
6 ?>
7
8 <ul class="menu">
9 <?php foreach ($list as $i => &$item) {
10 $itemParams = $item->getParams();
11 $class = '';
12
13 if ($item->id == $active_id) {
14 $class .= ' current';
15 }
16
17 echo '<li class="' . $class . '">';
18
19 require ModuleHelper::getLayoutPath('mod_menu', 'default_url');
20
21 // The next item is deeper.
22 if ($item->deeper) {
23 echo '<ul>';
24 }
25 // The next item is shallower.
26 else if ($item->shallower) {
27 echo '</li>';
28 echo str_repeat('</ul></li>', $item->level_diff);
29 }
30 // The next item is on the same level.
31 else {
32 echo '</li>';
33 }
34 }
35 ?></ul>

Page 484 January 2023


45. Overrides Joomla 4 - Developing Extensions

45.1.2. Modified files

45.1.2.0.1. templates/facile/index.php The following index.php is adapted to the newly added


CSS styles and now outputs a more appealing design in the frontend.

The line <jdoc:include type="modules"name="top-a"style="hr"/> ensures that the


Chrome hr module is added at this point to display the module.

templates/facile/index.php

1 <!DOCTYPE html>
2 <html lang="de">
3
4 <head>
5 - <meta charset="utf-8">
6 - <meta name="viewport" content="width=device-width, initial-scale
=1.0">
7 - <title>Titel</title>
8 + <meta charset="utf-8">
9 + <meta name="viewport" content="width=device-width, initial-scale
=1.0">
10 + <link rel="stylesheet" href="<?php echo $templatePath; ?>/assets/
css/main.css" />
11 + <title>Titel</title>
12 </head>
13
14 -<body>
15 - <header>
16 - <div>
17 - <nav>
18 - <div>
19 - <jdoc:include type="modules" name="menu" />
20 - </div>
21 - </nav>
22 - <div>
23 - <jdoc:include type="modules" name="search" />
24 - </div>
25 - </div>
26 - </header>
27 -
28 - <div>
29 - <jdoc:include type="modules" name="banner" />
30 - </div>
31 -
32 - <div>
33 - <jdoc:include type="modules" name="top-a" />
34 - </div>
35 -
36 - <div>
37 - <jdoc:include type="modules" name="top-b" />

January 2023 Page 485


Joomla 4 - Developing Extensions 45. Overrides

38 - </div>
39 -
40 - <div>
41 - <jdoc:include type="modules" name="sidebar-left" />
42 - </div>
43 -
44 - <div>
45 - <jdoc:include type="modules" name="breadcrumbs" />
46 - <jdoc:include type="modules" name="main-top" />
47 - <jdoc:include type="message" />
48 - <main>
49 - <jdoc:include type="component" />
50 - </main>
51 - <jdoc:include type="modules" name="main-bottom" />
52 - </div>
53 -
54 - <div>
55 - <jdoc:include type="modules" name="sidebar-right" />
56 - </div>
57 -
58 - <div>
59 - <jdoc:include type="modules" name="bottom-a" />
60 - </div>
61 -
62 - <div>
63 - <jdoc:include type="modules" name="bottom-b" />
64 - </div>
65 -
66 - <footer>
67 - <jdoc:include type="modules" name="footer" />
68 - </footer>
69 -
70 - <jdoc:include type="modules" name="debug" />
71 -
72 +<body class="homepage is-preload">
73 + <div id="page-wrapper">
74 +
75 + <?php if ($this->countModules('menu', true)) : ?>
76 + <nav id="nav">
77 + <jdoc:include type="modules" name="menu" />
78 + </nav>
79 + <?php endif; ?>
80 +
81 + <section id="main">
82 + <div class="container">
83 + <div class="row gtr-200">
84 + <div class="row">
85 +
86 + <?php if ($this->countModules('top-a', true)) :
?>
87 + <jdoc:include type="modules" name="top-a" style

Page 486 January 2023


45. Overrides Joomla 4 - Developing Extensions

="hr" />
88 + <?php endif; ?>
89 +
90 + <?php if ($this->countModules('sidebar-left',
true)) : ?>
91 + <div class="col-3 col-12-medium">
92 + <div class="sidebar">
93 + <jdoc:include type="modules" name="
sidebar-left" style="none" />
94 + </div>
95 + </div>
96 + <?php endif; ?>
97 +
98 + <div class="col-6 col-12-medium imp-medium">
99 + <div class="content">
100 +
101 + <?php if ($this->countModules('search',
true)) : ?>
102 + <section id="search">
103 + <jdoc:include type="modules" name="
breadcrumbs" style="none" />
104 + </section>
105 + <?php endif; ?>
106 +
107 + <?php if ($this->countModules('search',
true)) : ?>
108 + <section id="search">
109 + <jdoc:include type="modules" name="
search" style="none" />
110 + </section>
111 + <?php endif; ?>
112 +
113 + <jdoc:include type="modules" name="main
-top" style="none" />
114 + <jdoc:include type="message" />
115 + <main>
116 + <jdoc:include type="component" />
117 + </main>
118 +
119 + <jdoc:include type="modules" name="main
-bottom" style="none" />
120 +
121 + </div>
122 + </div>
123 +
124 + <?php if ($this->countModules('sidebar-right',
true)) : ?>
125 + <div class="col-3 col-12-medium">
126 + <div class="sidebar">
127 + <jdoc:include type="modules" name="
sidebar-right" style="none" />

January 2023 Page 487


Joomla 4 - Developing Extensions 45. Overrides

128 + </div>
129 + </div>
130 + <?php endif; ?>
131 +
132 + <?php if ($this->countModules('bottom-a', true)
) : ?>
133 + <jdoc:include type="modules" name="bottom-a"
style="none" />
134 + <?php endif; ?>
135 + </div>
136 + </div>
137 + </div>
138 + </section>
139 +
140 + <footer id="footer">
141 + <?php if ($this->countModules('footer', true)) : ?>
142 + <div id="copyright">
143 + <jdoc:include type="modules" name="footer" />
144 + </div>
145 + <?php endif; ?>
146 + </footer>
147 +
148 + <jdoc:include type="modules" name="debug" />
149 +
150 + <script src="<?php echo $templatePath; ?>/assets/js/jquery.min.
js"></script>
151 + <script src="<?php echo $templatePath; ?>/assets/js/jquery.
dropotron.min.js"></script>
152 + <script src="<?php echo $templatePath; ?>/assets/js/jquery.
scrolly.min.js"></script>
153 + <script src="<?php echo $templatePath; ?>/assets/js/browser.min
.js"></script>
154 + <script src="<?php echo $templatePath; ?>/assets/js/breakpoints
.min.js"></script>
155 + <script src="<?php echo $templatePath; ?>/assets/js/util.js"></
script>
156 + <script src="<?php echo $templatePath; ?>/assets/js/main.js"></
script>
157 +
158 + </div>
159 </body>
160
161 </html>

Tip: To avoid adding elements unnecessarily it is good practice to check if a module position is used
in the Joomla installation. This is done with $this->countModules('NAME_DER_POSITIONS
', true).

Page 488 January 2023


45. Overrides Joomla 4 - Developing Extensions

I deleted the banner module, because I want to add a banner later using parameters.

45.2. Test your Joomla template

1. install your template in Joomla version 4 to test it:

Copy the files in the templates folder to the templates folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the ones from the previous part.

We have installed the sample data in the previous chapter. If you have not done so, please do it now so
that the modules shown in the next image are available on the homepage of the Joomla installation.

Figure 45.5.: Create Joomla Template - Filter Module Positions

2. open the module Bottom Menu and choose as layout bottom. For the module Main Menu
Blog replace the layout Dropdown with the default layout Default from Module.

January 2023 Page 489


Joomla 4 - Developing Extensions 45. Overrides

Figure 45.6.: Create Joomla Template - Alternate Override - Bottom

3. open the module mod_articles_news (Articles - Newsflash) with the name Latest
Posts, which is shown in the header of the frontend. In the explanations of index.php you have
learned that a module Chrome is activated via the parameter style="hr" in <jdoc:include
type="modules"name="top-a"style="hr"/>. But you can also set this in the backend. The
next picture shows you how to do this in the Advanced tab via the Module Style parameter.

Figure 45.7.: Create Joomla Template - Module Chrome

Page 490 January 2023


45. Overrides Joomla 4 - Developing Extensions

4. Play with the different possibilities. Create different types of overrides and test the output in the
frontend.

All override files in the directory templates/facile/html/com_content/article are available


for selection in the tab Options in the selection field Layout when creating an article.

Figure 45.8.: Different options in Joomla Template Overrides creation | When creating an article

When creating a menu item, you only have the overrides to choose from, for which you have created
an XML file.

January 2023 Page 491


Joomla 4 - Developing Extensions 45. Overrides

Figure 45.9.: Different possibilities with the Joomla Template Overrides creation | When creating a
menu item

Attention: You will not get an error message if you create an XML file but the related PHP file is miss-
ing due to a typo. There is also no hint if you create two XML files with the same title. Joomla
pretends in this case that there is only one of them. In the next screen, the file names are all cor-
rect. However, the title “COM_CONTENT_ARTICLE_VIEW_DEFAULT_TITLE” already exists. When I
created an article, the override was only offered for selection after I changed the language string to
COM_CONTENT_ARTICLE_VIEW_DEFAULT_MEINSPRECHENDERNAME_TITLE.

Page 492 January 2023


45. Overrides Joomla 4 - Developing Extensions

Figure 45.10.: Pitfalls in Joomla Template Overrides Creation

January 2023 Page 493


Joomla 4 - Developing Extensions 46. Parameter and Variables

46. Parameter and Variables

Parameters make the template flexibly configurable in the backend. Perhaps a colour selection should
be possible? The standard template Cassiopeia offers, among others, logoFile, siteTitle and
siteDescription as parameters. We add a banner and social media icons.

For impatient people: Take a look at the changed programme code in the Diff Viewa and copy
these changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t37...t38

46.1. Step by step

In this section we will look at parameters and see that they add content relatively statically. This is a
drawback. The benefit is that it is not complicated to use.

46.1.1. New files

In this chapter only the image templates/facile/images/banner.jpg is added, which is dis-


played in the banner via CSS. I took this from the demo images of html5up.net and it is from un-
splash.com.

46.1.2. Modified files

46.1.2.0.1. templates/facile/index.php We add code to the file templates/facile/index.php


so that a banner and social media icons can be inserted via parameters.
In HTML some characters, for example the less than sign < or the greater-than-sign >, have a
special meaning. They are therefore written as HTML entitiesa if they are to be displayed as text.
For example, &lt; is written instead of < and &gt; is written instead of >. When processing
strings, it may happen that they contain such specific characters. These must then be converted
so that they can be further processed as plain text. We offer in the template a text input option
via a form in the backend. However, we do not want HTML code to be injected via this. This is

Page 495 January 2023


Joomla 4 - Developing Extensions 46. Parameter and Variables

a security risk. To prevent this, we use the function htmlspecialchars()b . This ensures that the
characters that have a special meaning in HTML are converted to plain text.
a
en.wikipedia.org/wiki/list_of_xml_and_html_character_entity_references
b
www.php.net/manual/en/function.htmlspecialchars.php

Take a look at the code snippet below. I think the HTML code is self-explanatory. We add HTML
markup, which is only displayed if a certain parameter is set. For example, for the footer via the query
$this->params->get('showFooter'). What is displayed then also depends on the values for the
parameters filled in by the user in the backend.

templates/facile/index.php

1 </nav>
2 <?php endif; ?>
3
4 + <?php if ($this->params->get('showBanner')) : ?>
5 + <section id="banner">
6 + <div class="content">
7 + <h2><?php echo htmlspecialchars($this->params->get('
bannerTitle')); ?></h2>
8 + <p><?php echo htmlspecialchars($this->params->get('
bannerDescription')); ?></p>
9 + <a href="#main"
10 + class="button scrolly"><?php echo htmlspecialchars
($this->params->get('bannerButton')); ?></a>
11 + </div>
12 + </section>
13 + <?php endif; ?>
14 +
15 <section id="main">
16 <div class="container">
17 <div class="row gtr-200">
18
19 </section>
20
21 <footer id="footer">
22 + <?php if ($this->params->get('showFooter')) : ?>
23 + <div class="col-12">
24 + <section>
25 + <?php
26 + $fieldValues = $this->params->get('
showFooterTouchFields');
27 +
28 + if (empty($fieldValues))
29 + {
30 + return;
31 + }
32 +
33 + $html = '<ul class="contact">';

Page 496 January 2023


46. Parameter and Variables Joomla 4 - Developing Extensions

34 +
35 + foreach ($fieldValues as $value)
36 + {
37 + $html .= '<li><a class="icon brands ' .
$value->touchsubicon . '" href="' . $value->touchsuburl . '"><span
class="label">' . $value->touchsubname . '</span></a></li>';
38 +
39 + }
40 +
41 + $html .= '</ul>';
42 +
43 + echo $html;
44 +
45 + ?>
46 + </section>
47 + </div>
48 + <?php endif; ?>
49 +
50 +
51 <?php if ($this->countModules('footer', true)) : ?>
52 <div id="copyright">
53 <jdoc:include type="modules" name="footer" />

Tip: You would like to optionally define in the template which module position is used. For exam-
ple, is it important for you that a sidebar can be completely deactivated? Then create the parame-
ter showSidebarLeft and extend the line <?php if ($this->countModules('sidebar-
left', true)): ?>. In the end, this should become <?php if ($this->countModules('
sidebar-left', true)&& $this->params->get('showSidebarLeft')): ?>.

46.1.2.0.2. templates/facile/language/en-GB/tpl_facile.ini We use the language files for the la-


bels of our backend form. This way the texts can be translated into different languages.

templates/facile/language/en-GB/tpl_facile.ini

1 TPL_FACILE_XML_DESCRIPTION="Facile is a Joomla 4 template."


2 +;params
3 +TPL_FACILE_BANNER_FIELDSET_LABEL="Banner"
4 +TPL_FACILE_BANNER_FIELDSET_DESC="Please copy banner image file to /
templates/facile/images/banner.jpg"
5 +TPL_FACILE_BANNER_LABEL="Show Banner"
6 +TPL_FACILE_BANNER_TITLE="Title text"
7 +TPL_FACILE_BANNER_TAGLINE="Tagline text"
8 +TPL_FACILE_BANNER_BUTTON="Button text"
9 +TPL_FACILE_FOOTER_FIELDSET_LABEL="Social Footer"
10 +TPL_FACILE_FOOTER_FIELDSET_DESC="For example: https://fontawesome.com/
icons/facebook?style=brands"
11 +TPL_FACILE_FOOTER_LABEL="Show Social Footer"
12 +TPL_FACILE_GET_IN_TOUCH="Social Link"

January 2023 Page 497


Joomla 4 - Developing Extensions 46. Parameter and Variables

13 +TPL_FACILE_GET_IN_TOUCH_SUBNAME="Name"
14 +TPL_FACILE_GET_IN_TOUCH_SUBICON="Icon"
15 +TPL_FACILE_GET_IN_TOUCH_SUBURL="URL"

46.1.2.0.3. templates/facile/templateDetails.xml Since Joomla supports us in creating the form


fields, it is enough to define the fields via an XML file.

In order to display one form field in dependency to another, we use showon. showon="showBanner
:1" ensures that the current field is only shown if the showBanner field has the value 1.

The field of type type="subform" provides the possibility to flexibly define the number of values in
the backend form. Thus, with one form field it is possible to insert either only one link to Facebook or
to display many social media channels.

templates/facile/templateDetails.xml

1 <position>footer</position>
2 <position>debug</position>
3 </positions>
4 + <config>
5 + <fields name="params">
6 + <fieldset name="banner" label="
TPL_FACILE_BANNER_FIELDSET_LABEL" description="
TPL_FACILE_BANNER_FIELDSET_DESC">
7 + <field
8 + name="showBanner"
9 + type="radio"
10 + label="TPL_FACILE_BANNER_LABEL"
11 + layout="joomla.form.field.radio.switcher"
12 + default="0"
13 + filter="integer"
14 + >
15 + <option value="0">JNO</option>
16 + <option value="1">JYES</option>
17 + </field>
18 +
19 + <field
20 + name="bannerTitle"
21 + type="text"
22 + default="Welcome to the Joomla version of TXT by
HTML5 UP"
23 + label="TPL_FACILE_BANNER_TITLE"
24 + filter="string"
25 + showon="showBanner:1"
26 + />
27 +
28 + <field
29 + name="bannerDescription"
30 + type="text"

Page 498 January 2023


46. Parameter and Variables Joomla 4 - Developing Extensions

31 + default="A free responsive site template built on


HTML5, CSS3, and some other stuff"
32 + label="TPL_FACILE_BANNER_TAGLINE"
33 + filter="string"
34 + showon="showBanner:1"
35 + />
36 +
37 + <field
38 + name="bannerButton"
39 + type="text"
40 + default="Alright let's go"
41 + label="TPL_FACILE_BANNER_BUTTON"
42 + filter="string"
43 + showon="showBanner:1"
44 + />
45 + </fieldset>
46 + <fieldset name="footer" label="
TPL_FACILE_FOOTER_FIELDSET_LABEL" description="
TPL_FACILE_FOOTER_FIELDSET_DESC">
47 + <field
48 + name="showFooter"
49 + type="radio"
50 + label="TPL_FACILE_FOOTER_LABEL"
51 + layout="joomla.form.field.radio.switcher"
52 + default="0"
53 + filter="integer"
54 + >
55 + <option value="0">JNO</option>
56 + <option value="1">JYES</option>
57 + </field>
58 +
59 + <field
60 + name="showFooterTouchFields"
61 + type="subform"
62 + label="TPL_FACILE_GET_IN_TOUCH"
63 + multiple="true"
64 + max="10"
65 + showon="showFooter:1"
66 + >
67 + <form>
68 + <field
69 + name="touchsubname"
70 + type="text"
71 + label="TPL_FACILE_GET_IN_TOUCH_SUBNAME"
72 + />
73 + <field
74 + name="touchsubicon"
75 + type="text"
76 + label="TPL_FACILE_GET_IN_TOUCH_SUBICON"
77 + />
78 + <field

January 2023 Page 499


Joomla 4 - Developing Extensions 46. Parameter and Variables

79 + name="touchsuburl"
80 + type="url"
81 + label="TPL_FACILE_GET_IN_TOUCH_SUBURL"
82 + size="30"
83 + filter="url"
84 + validate="url"
85 + />
86 + </form>
87 + </field>
88 + </fieldset>
89 + </fields>
90 + </config>
91 </extension>

The Joomla documentation docs.joomla.org/Form_field provides an overview of all possible


form fields.

46.2. Test your Joomla template

1. install your template in Joomla version 4 to test it:

Copy the files in the templates folder to the templates folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the ones from the previous part. In any case you
should make sure that the template style Facile is active. In my examples the Blog sample files are
installed.

2. activate the banner in the template style of Facile and see the result in the frontend.

Page 500 January 2023


46. Parameter and Variables Joomla 4 - Developing Extensions

Figure 46.1.: Create Joomla Template - Banner via parameters in the frontend

Figure 46.2.: Create Joomla Template - Banner via parameters in the backend

3. activate the social media display in the template style of Facile and see the result in the frontend.

January 2023 Page 501


Joomla 4 - Developing Extensions 46. Parameter and Variables

Figure 46.3.: Create Joomla Template - Social Media Backend

Figure 46.4.: Create Joomla Template - Social Media Frontend

I use the icons fa-facebook-f for Facebook and fa-twitter for Twitter. I can do this because
the template integrates Facile Font Awesomea . See /templates/facile/assets/webfonts.
a
fontawesome.com/v5/search?m=free

Page 502 January 2023


Joomla 4 - Developing Extensions 47. Web Asset Manager

47. Web Asset Manager

There is a lot to consider when loading styles and stylesheets in the frontend. Performance plays a role
and possibly the order in which files are loaded. In Joomla, there were often conflicts and cumbersome
workarounds. Joomla 4 changes this with the concept of web assets.

I think it is important to understand that the Joomla Web Assets Manager manages all assets in a
Joomla installation. It does not apply assets specifically for a template. If an extension is loaded
and it needs assets, it can also use the Web Assets Manager. But: It does not have to. Assets can still
be included via Joomla\CMS\HTML\HTMLHelper - for example via HTMLHelper::_('jquery
.framework');. The advantage of the Webassets Manager is that it ensures that assets are not
loaded twice if two extension use the same asset file. And the assets are loaded in the defined
order. This prevents conflicts. What I think is especially noteworthy for template developers is that
when other Joomla extensions include their assets via HTMLHelper, these assets are appended
after the Web Asset Manager assets. This results in overriding styles that are set in the template.
See in this context (Issue 35706)https://github.com/joomla/joomla-cms/issues/35706.

For impatient people: Look at the changed programme code in the Diff Viewa and transfer these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t38...t39

47.1. Step by step

In this section, we are not adding a new function. We are merely rebuilding. We change the way we
integrate the JavaScript and CSS files. From now on, a Joomla-specific function will be used for this,
which offers many advantages.

In the chapter explaining how to use the database in the frontend, I had already written that you
can integrate web assets via the joomla.asset.json file. Here I show how to use the Web Asset
Manager without joomla.asset.json.

Page 503 January 2023


Joomla 4 - Developing Extensions 47. Web Asset Manager

47.1.1. New files

In this chapter only one file has been changed.

47.1.2. Modified files

In the file templates/facile/index.php we change the way JavaScript and CSS is included. We
replaced the <script> tags in the footer and the <link rel="stylesheet".. /> in the header.
Instead of them we use the Joomla Web Asset Manager. This makes it necessary to use the <jdoc:
include type="styles"/> and <jdoc:include type="styles"/> tags. We give control here.
Joomla does work for us in return. If we configure the assets correctly Joomla loads everything
optimized and conflict free.

Because we use <jdoc:include type="metas"/>, we no longer need the line <title>title


</title>. Joomla now uses the site name as title. This name is set during installation and can
be edited at any time via global configuration.

47.1.2.0.1. templates/facile/index.php The following code snippet shows you the changes in the
file templates/facile/index.php.

templates/facile/index.php

1 \defined('_JEXEC') or die;
2 +
3 +use Joomla\CMS\HTML\HTMLHelper;
4 +
5 $templatePath = 'templates/' . $this->template;
6 +$wa = $this->getWebAssetManager();
7 +$wa->registerAndUseStyle('main', $templatePath . '/assets/css/main.css
');
8 +HTMLHelper::_('jquery.framework');
9 +$wa->registerAndUseScript('dropotron', $templatePath . '/assets/js/
jquery.dropotron.min.js', [], ['defer' => true], []);
10 +$wa->registerAndUseScript('scrolly', $templatePath . '/assets/js/
jquery.scrolly.min.js', [], ['defer' => true], []);
11 +$wa->registerAndUseScript('browser', $templatePath . '/assets/js/
browser.min.js', [], ['defer' => true], []);
12 +$wa->registerAndUseScript('breakpoints', $templatePath . '/assets/js/
breakpoints.min.js', [], ['defer' => true], []);
13 +$wa->registerAndUseScript('util', $templatePath . '/assets/js/util.js'
, [], ['defer' => true], []);
14 +$wa->registerAndUseScript('main', $templatePath . '/assets/js/main.js'
, [], ['defer' => true], []);
15 ?>
16

Page 504 January 2023


47. Web Asset Manager Joomla 4 - Developing Extensions

17 <!DOCTYPE html>
18 <html lang="de">
19
20 <head>
21 - <meta charset="utf-8">
22 + <jdoc:include type="metas" />
23 <meta name="viewport" content="width=device-width, initial-scale
=1.0">
24 - <link rel="stylesheet" href="<?php echo $templatePath; ?>/assets/
css/main.css" />
25 - <title>Titel</title>
26 + <jdoc:include type="styles" />
27 + <jdoc:include type="scripts" />
28 </head>
29
30 <body class="homepage is-preload">
31 @@ -137,15 +149,6 @@ class="button scrolly"><?php echo htmlspecialchars
($this->params->get('bannerBut
32 </footer>
33
34 <jdoc:include type="modules" name="debug" />
35 -
36 - <script src="<?php echo $templatePath; ?>/assets/js/jquery.min
.js"></script>
37 - <script src="<?php echo $templatePath; ?>/assets/js/jquery.
dropotron.min.js"></script>
38 - <script src="<?php echo $templatePath; ?>/assets/js/jquery.
scrolly.min.js"></script>
39 - <script src="<?php echo $templatePath; ?>/assets/js/browser.
min.js"></script>
40 - <script src="<?php echo $templatePath; ?>/assets/js/
breakpoints.min.js"></script>
41 - <script src="<?php echo $templatePath; ?>/assets/js/util.js
"></script>
42 - <script src="<?php echo $templatePath; ?>/assets/js/main.js
"></script>
43 -
44 </div>
45 </body>

Asynchronous loading of web assets leads to an improvement in noticed loading time. External
resources such as JavaScript can be assigned the defer and async attributes when tagged in
the HTML document. If a resource is given the defer attribute, the script will not execute until
the Document Object Model (DOM) has been loaded. By specifying the async attribute, the
JavaScript is loaded and executed asynchronously in the background. This avoids blocking the
rendering to the browser and multiple scripts are loaded and executed in parallel.

January 2023 Page 505


Joomla 4 - Developing Extensions 47. Web Asset Manager

47.2. Test your Joomla template

1. install your template in Joomla version 4 to test it:

Copy the files in the templates folder to the templates folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the files from the previous part, unless you use the
variant with the file joomla.asset.json. The joomla.asset.json has to be registered and this is
done during the installation.

2. no visible new function has to be added. Make sure that the drop down menu works and the
display fine. If it is, then all files are loaded correctly.

47.3. Links

Web Assets1

1
docs.joomla.org/j4.x:web_assets

Page 506 January 2023


Joomla 4 - Developing Extensions 48. Dark Mode

48. Dark Mode

Dark Mode is a hot topic right now. Apple, for example, has integrated dark mode into its operating
systems. Windows and Google have done the same. Dark Mode is in fashion. And not only that. It offers
advantages. Whether darker displays are good for the eyes is debatable. What is clear, however, is that
less light saves energy.

For impatient people: Look at the changed programme code in the Diff Viewa and transfer these
changes to your development version.
a
codeberg.org/astrid/j4examplecode/compare/t39...t40

48.1. Step by step

In this section we will integrate the possibility of switching to a dark mode into the Tempalte Facile. We
do this with the help of a specially created CSS file. Which mode is active, we query via the property
prefers-color-scheme. This recognizes which variant the user has set as desired in the operating
system.

I use the following snippet to have the information displayed in the browser console beforehand. This
way I am sure that the property “prefers-color-scheme” is supported and how it is set.

1 <script>
2 if (window.matchMedia('(prefers-color-scheme)').media !== 'not all'
) {
3 console.log('Dark mode is supported');
4 }
5 if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
6 console.log('Dark mode');
7 } else {
8 console.log('Light mode');
9 }
10 </script>

Are you wondering what window.matchMedia means? window.PLACEHOLDER means that the vari-
able PLACEHOLDER is declared in the global scope. This means that any JavaScript code has access to

Page 507 January 2023


Joomla 4 - Developing Extensions 48. Dark Mode

this variable. The use of window1 is not mandatory. However, window is often used as a convention
to indicate that a variable is global. Global variables should be avoided. Using them is not a good
programming style. It is safer to define your own variables, if possible.

If you want to implement a dark mode, there is an uncomplicated solution: simply display ev-
erything in black and white. The text @media (prefers-color-scheme: dark){ body {
background: #333!important; color: white !important; }} in the CSS file would
do this. A matching color scheme is better in terms of quality.

48.1.1. New files

Added the CSS file templates/assets/css/main.dark.css. This new stylesheet contains the rules
for the dark mode. It differs from templates/assets/css/main.css only in some color codes.

The system messages appear to bright in dark mode. So far we have used these function un-
changed. In Dark Mode I adjust these now. This is the web component joomla-alert. The
appearance is changeable via joomla-alert { ..} in the CSS file.

48.1.2. Changed files

48.1.2.0.1. templates/facile/index.php The file templates/facile/index.php now loads the


CSS file depending on the preferred color scheme. Additionally it integrates a slider in the upper left
corner, which makes the mode switchable by click. The necessary changes can be found in the following
code example.

templates/facile/index.php

1 $templatePath = 'templates/' . $this->template;


2 $wa = $this->getWebAssetManager();
3 -$wa->registerAndUseStyle('main', $templatePath . '/assets/css/main.css
');
4 +$wa->registerAndUseStyle('main_dark', $templatePath . '/assets/css/
main.dark.css', [], ['media' => '(prefers-color-scheme: dark)']);
5 +$wa->registerAndUseStyle('main_light', $templatePath . '/assets/css/
main.css', [], ['media' => '(prefers-color-scheme: no-preference), (
prefers-color-scheme: light)']);
6 HTMLHelper::_('jquery.framework');
7 $wa->registerAndUseScript('dropotron', $templatePath . '/assets/js/
jquery.dropotron.min.js', [], ['defer' => true], []);
8 $wa->registerAndUseScript('scrolly', $templatePath . '/assets/js/
jquery.scrolly.min.js', [], ['defer' => true], []);
9
1
developer.mozilla.org/en-us/docs/web/api/window

Page 508 January 2023


48. Dark Mode Joomla 4 - Developing Extensions

10 <meta name="viewport" content="width=device-width, initial-scale


=1.0">
11 <jdoc:include type="styles" />
12 <jdoc:include type="scripts" />
13 + <script type="module" src="https://unpkg.com/dark-mode-toggle"></
script>
14 </head>
15
16 <body class="homepage is-preload">
17 <div id="page-wrapper">
18 -
19 +
20 + <dark-mode-toggle></dark-mode-toggle>
21 +
22 <?php if ($this->countModules('menu', true)) : ?>
23 <nav id="nav">
24 <jdoc:include type="modules" name="menu" />
25 class="button scrolly"><?php echo htmlspecialchars($this->params->get(
'bannerBut
26
27 <jdoc:include type="modules" name="debug" />
28 </div>
29 -</body>
30 + <script>
31 + /*
32 + if (window.matchMedia('(prefers-color-scheme)').media !== 'not
all') {
33 + console.log('Dark mode is supported');
34 + }
35 + if (window.matchMedia('(prefers-color-scheme: dark)').matches)
{
36 + console.log('Dark mode');
37 + } else {
38 + console.log('Light mode');
39 + }
40 + */
41 + </script>
42 + </body>
43
44 </html>

48.2. Side Note: Dark Mode depending on the position of the sun

An interesting idea is to switch the dark mode depending on the position of the sun at the viewer: As
soon as the sun sets on the viewer’s side, the dark mode should kick in. Not only time and date play a
role, but also the geo position. I found a possible implementation on Codepen.

First of all, CSS variables are set.

January 2023 Page 509


Joomla 4 - Developing Extensions 48. Dark Mode

1 html {
2 --text-color: #2f2f2f;
3 --bg-color: #fff;
4 }
5
6 html[data-theme='dark'] {
7 --text-color: #fff;
8 --bg-color: #2f2f2f;
9 }
10
11 body {
12 colour: var(--text-color);
13 background: var(--bg-color);
14 }

These CSS variables are switched on and off via JavaScript, which queries the time zone.

1 let clientTimes = new Date()


2 let currentTime = clientTimes.getHours() + clientTimes.getMinutes() /
100
3
4 let options = {
5 enableHighAccuracy: true,
6 timeout: 3000,
7 maximumAge: 30000,
8 }
9
10 let success = (pos) => {
11 // Get Location
12 let lat = pos.coords.latitude
13 let long = pos.coords.longitude
14
15 // Get Sunset && Sunrise Time for Location based on SunsetCalc (https
://github.com/mourner/suncalc)
16 let sunTimes = SunCalc.getTimes(new Date(), lat, long)
17 let sunsetTime =
18 sunTimes.sunset.getHours() + sunTimes.sunset.getMinutes() / 100
19 let sunriseTime =
20 sunTimes.sunrise.getHours() + sunTimes.sunrise.getMinutes() / 100
21
22 // Add Data-Attribut to HTMl if sunsetTime < curentTime
23 if (currentTime > sunsetTime || currentTime < sunriseTime) {
24 document.querySelector('html').dataset.theme = 'dark'
25 }
26 }
27
28 let error = (err) => {
29 if (currentTime > 20) {
30 // Set Fallback if GeoLocation is not supported
31 document.querySelector('html').dataset.theme = 'dark'

Page 510 January 2023


48. Dark Mode Joomla 4 - Developing Extensions

32 }
33 }
34
35 navigator.geolocation.getCurrentPosition(success, error, options)

48.3. Test your Joomla template

1. install your template in Joomla version 4 to test it:

Copy the files in the templates folder to the templates folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the ones from the previous part. Your website
should now support dark mode. In the upper left area there should be a switch to toggle the mode.

Figure 48.1.: Create Joomla Template - Dark Mode

48.4. Links

prefers-color-scheme2

dark-mode-toggle-Element3

2
web.dev/prefers-color-scheme
3
github.com/googlechromelabs/dark-mode-toggle

January 2023 Page 511


Joomla 4 - Developing Extensions 49. Favicon

49. Favicon

A favicon is a small icon used to identify a website in a recognizable way. It appears in different ways
on different devices. Most often, you see it as an icon in your favorites when you save the website here
to visit it again more quickly. Almost always the tabs in the browser are marked with the icon.

The size and type of the favicon is expected to be different on different devices. I use the website
realfavicongenerator.net to create the optimal format of my image for the individual devices. I
consider this tool to be tried and tested and the easiest to use. However, there is an alternative
newer approach that is used by the Joomla standard template Cassiopeia. If you prefer to use
the modern SVG format with an ICO file as a fallback layer, you will find a solution that suits you
better under Favicon in Joomla template.

For impatient people: Look at the changed programme code in the diff viewa and transfer these
changes into your development version.
a
codeberg.org/astrid/j4examplecode/compare/t40...t41

49.1. Step by step

In this section we create a recognisable image. In the first step, we choose an image. For the example,
I chose a yellow PNG file. In the next step, we convert it into different formats using the website
realfavicongenerator.net.

Tip: Clear the browser cache if changes to the favicon are not visible during development.

49.1.1. New files

The favicon generator creates 9 files which we copy into our template directory. I put all of them in the
directory templates/facile/favicon_package. These are exactly the files

1. android-chrome-192x192.png
2. android-chrome-512x512.png

Page 513 January 2023


Joomla 4 - Developing Extensions 49. Favicon

3. apple-touch-icon.png
4. browserconfig.xml
5. favicon-16x16.png
6. favicon-32x32.png
7. favicon.ico
8. mstile-150x150.png
9. site.webmanifest

49.1.2. Modified files

49.1.2.0.1. templates/facile/index.php In order for the files to be found, new lines in the file
templates/facile/index.php are required. The variable $templatePath helps me to create the
relativ path.
We introduced the variable $templatePath in the last chapter. It is assigned $templatePath =
'templates/'. $this->template; and thus points to the directory of the current template
in the Joomla directory tree. The entry <link rel="icon"type="image/png"sizes="16
x16"href="<?php echo $templatePath . '/favicon_package'; ?>/favicon-16
x16.png"> thus becomes <link rel="icon"type="image/png"sizes="16x16"href="
/PathToJoomla/templates/facile/favicon_package/favicon-16x16.png"> in the
HTML source code.

templates/facile/index.php

1 <meta name="viewport" content="width=device-width, initial-scale


=1.0">
2 <jdoc:include type="styles" />
3 <jdoc:include type="scripts" />
4 +
5 + <link rel="apple-touch-icon" sizes="180x180"
6 + href="<?php echo $templatePath . '/favicon_package'; ?>/apple-
touch-icon.png">
7 + <link rel="icon" type="image/png" sizes="32x32"
8 + href="<?php echo $templatePath . '/favicon_package'; ?>/
favicon-32x32.png">
9 + <link rel="icon" type="image/png" sizes="16x16"
10 + href="<?php echo $templatePath . '/favicon_package'; ?>/
favicon-16x16.png">
11 + <link rel="manifest" href="<?php echo $templatePath . '/
favicon_package'; ?>/site.webmanifest">
12 + <meta name="msapplication-TileColor" content="#da532c">
13 + <meta name="theme-color" content="#ffffff">
14 +
15 <script type="module" src="https://unpkg.com/dark-mode-toggle"></
script>

Page 514 January 2023


49. Favicon Joomla 4 - Developing Extensions

16 </head>

49.2. Test your Joomla template

1. install your template in Joomla version 4 to test it: Copy the files in the templates folder to the
templates folder of your Joomla 4 installation. A new installation is not necessary. Continue
using the ones from the previous part. Make sure that the favicons are displayed correctly on the
devices. Below you can see a representation in the browser Firefox.

Figure 49.1.: Create Joomla Template - Favicon

49.3. Links

Favicon Generator1

1
realfavicongenerator.net

January 2023 Page 515


Joomla 4 - Developing Extensions 50. Lighthouse

50. Lighthouse

The template is ready. Now you want to make sure that it is technically good and contains no errors.
Then take a look at Lighthouse1 . This is a browser plug-in and an audit tool developed in Google
Chrome with which the loading time of a website can be examined and optimised. In addition to the
structure of HTML, CSS and JavaScript files, it also takes into account the integration of images and the
cache settings of the website.

50.1. Google Lighthouse

The Lighthouse analyses includes

• Performance - How fast does the website load?


• Accessibility - Does the website include barriers for certain people or devices?
• Best Practices - Does the website use modern standards?
• Search Engine Optimisation (SEO) - How well is the website readable by search engines?
• Progressive Web App (PWA) - Does the website offer features of a Web App2 ?

1
developers.google.com/web/tools/lighthouse
2
en.wikipedia.org/wiki/mobile_app

Page 517 January 2023


Joomla 4 - Developing Extensions 50. Lighthouse

Figure 50.1.: Create Joomla Template - Page Speed Analysis with Lighthouse

For each area, the support is calculated as a percentage.

• 0 - 49 (red) = poor
• 50 to 89 (yellow) = medium
• 90 to 100 (green) = good

With Joomla, 100% is achievable in all areas. You can find a concrete example at die-beste-
website.de.

50.1.1. Google Chrome web browser

Lighthouse is included in the Google Chrome web browser by default. Open the website you want to
test and activate Lighthouse:

• Right-click anywhere on the website.


• Select Inspect from the context menu that opens.
• Open the Lighthouse tab.
• Use the Generate Report button.

50.1.2. Create good conditions for the test

• Start the analysis under a good internet connection.


• Run the test in “incognito mode” (privacy mode). This way, any browser extensions do not
influence the test.

Page 518 January 2023


50. Lighthouse Joomla 4 - Developing Extensions

You can use the results and tips from Lighthouse to improve your website. Some of the tips3 come
directly from the Joomla community.

Figure 50.2.: Create Joomla Template - Page Speed Analysis with Lighthouse

50.1.3. Vary results

The results of the Lighthouse analysis vary at different times and under different conditions. Reasons
for this are, for example

• the internet connection


• the device used
• browser extensions
• antivirus software

50.2. Links

Lighthouse4

3
github.com/googlechrome/lighthouse-stack-packs/pull/44/files
4
developers.google.com/web/tools/lighthouse

January 2023 Page 519


Joomla 4 - Developing Extensions

Part V.

This and That

Page 521 January 2023


Joomla 4 - Developing Extensions 51. Package

51. Package

We have created a lot of different extensions. It is annoying to do a separate installation for each one.
This is not reasonable for a user. Moreover, some of these extensions build on each other and it is
important to make sure that everything is installed and nothing has been forgotten. Therefore, in
this concluding chapter I show how different extensions are packed together into one installation
package.

For impatient people: Look at the changed programme code in the Diff Viewa and apply these
changes to your development version.
a
codeberg.org/astrid/j4examplecode/compare/t41...t42

51.1. Step by step

In this section we will create an installation package.

51.1.1. New files

51.1.1.1. Package

51.1.1.1.1. administrator/manifests/ packages/foos/script.php Like the component, a package


can be extended with an optional script. In our case, we check whether the installation requirements
are met in terms of minimum versions for Joomla and PHP.

administrator/manifests/ packages/foos/script.php

1
2 <?php
3
4 \defined('_JEXEC') or die;
5
6 class Pkg_FoosInstallerScript
7 {
8 public function __construct()
9 {

Page 523 January 2023


Joomla 4 - Developing Extensions 51. Package

10 $this->minimumJoomla = '4.0';
11 $this->minimumPhp = JOOMLA_MINIMUM_PHP;
12 }
13 }

51.1.1.1.2. administrator/manifests/ packages/pkg_foos.xml You already know the manifest. In


the case of a package, there is little new here. In the area of the files to be copied, you specify the
installation packages of the extensions that are to be installed.

administrator/manifests/ packages/pkg_foos.xml

1 <?xml version="1.0" encoding="UTF-8" ?>


2 <extension type="package" version="1.0" method="upgrade">
3 <name>pkg_foos</name>
4 <packagename>agosms</packagename>
5 <creationDate>##DATE##</creationDate>
6 <packager>Astrid Günther</packager>
7 <copyright>(C) ##YEAR## Astrid Günther. All rights reserved.</
copyright>
8 <packageremail>[email protected]</packageremail>
9 <packagerurl>www.astrid-guenther.de</packagerurl>
10 <author>Astrid Günther</author>
11 <authorEmail>[email protected]</authorEmail>
12 <authorUrl>www.astrid-guenther.de</authorUrl>
13 <version>##VERSION##</version>
14 <license>GNU General Public License version 2 or later; see LICENSE
.txt</license>
15 <description>PKG_FOOS_XML_DESCRIPTION</description>
16 <scriptfile>script.php</scriptfile>
17 <files>
18 <!-- The id for each extension is the element stored in the DB
-->
19 <file type="component" id="com_foos">com_foos.zip</file>
20 <file type="module" id="mod_foo" client="site">mod_foo.zip</
file>
21 <file type="plugin" id="plg_webservices_foos" group="
webservices">plg_webservices_foos.zip</file>
22 <file type="template" id="tpl_facile" client="site">tpl_facile.
zip</file>
23 </files>
24 <updateservers>
25 <server type="extension" name="Foo Updates">https://codeberg.
org/astrid/j4examplecode/raw/branch/tutorial/foo_update.xml
</server>
26 </updateservers>
27 <dlid prefix="dlid=" suffix="" />
28 </extension>

Page 524 January 2023


51. Package Joomla 4 - Developing Extensions

51.2. Test your Joomla Template

1. make a new installation. To do this, uninstall your previous installation.

2. create a ZIP file for each extension.

3. create a ZIP that contains all ZIP files and the files of this chapter.

4. install the ZIP created in point 3 in Joomla.

5. make sure that all the extensions specified in the files section have been installed.

January 2023 Page 525


Joomla 4 - Developing Extensions 52. Joomla update and change log setup

52. Joomla update and change log setup

You will continue to develop your component. How do you make sure that users always use the latest
version? How do they know about an update? Now that the basic framework of the extension is ready,
it’s important that your users know about enhancements.

In this chapter I will explain how to create and run an update server for your component. If you want to
continue working on the features first, I fully understand. Then just skip this section and come back
when you publish your extension.

Update Server sounds complicated, it’s basically just a URL to an XML file. This URL is inserted in the
extension’s installation manifest. The XML file contains a number of details, including the new version
number and the download URL to the installation file. When Joomla finds an update for an installed
extension, this is displayed in the administration area.

For impatient people: Look at the changed program code in the diff viewa and include these
changes in your development version.
a
codeberg.org/astrid/j4examplecode/compare/t1...t1b

52.1. Step by step

In the current section, two files are added that are stored outside the website. The addresses or URLs
under which these are stored were entered in the previous chapters in the file src/administrator/
components/com_foos/foos.xml.

1<changelogurl>https://codeberg.org/astrid/j4examplecode/raw/branch/
tutorial/changelog.xml</changelogurl>
2 <updateservers>
3 <server type="extension" name="Foo Updates">https://codeberg.org/
astrid/j4examplecode/raw/branch/tutorial/foo_update.xml</server>
4 </updateservers>

Wondering where to save those files? Perhaps an example is appropriate. Go to the repo
https://github.com/astridx/pkg_agadvents. Here you can see the files agadvents-update4.xml
and changelog.xml. If you click on one of the files, you can call the raw version via a button

Page 527 January 2023


Joomla 4 - Developing Extensions 52. Joomla update and change log setup

in the upper right corner. This is raw.githubusercontent.com/astridx/pkg_agadvents/


master/changelog.xml respectively github.com/astridx/pkg_agadvents/blob/master/
agadvents-update4.xml. I have included these addresses in the manifest XML file. In the example
it is a package. But it is the same for a component, a module or a plugin. The XML file can be found at
pkg_agadvents.xml[github.com/astridx/pkg_agadvents/blob/master/j4/pkg_agadvents/src/administrator/manifests/p

52.1.1. New files


The changes concerning the changelog and the Joomla Update Server are only mentioned in this
chapter. In every other chapter you can update the numbers yourself if this is important to you. If
I described this over and over again, it would not only bore you - it would unnecessarily inflate
this text.

52.1.1.1. foo_update.xml (Update Server)

You have told your component in the file administrator/components/com_foos/foos.xml where to find
out about updates. That is in the file foo_update.xml.
Create the file foo_update.xml. The file can be named anything as long as it matches the name you
specified in the installation XML administrator/components/com_foos/foos.xml.
The tag updates surrounds all update elements. Create another update section each time you release
a new version.
If your extension supports other Joomla versions, create separate <update> definitions for each
version.

The value of name will be displayed in the Extension Manager Update view. If you use the same name
as the extension, you avoid confusion:
The value of the description tag is displayed when you hover over the name in the Update view.
The value of the element tag is the installed name of the extension. This should match the value in
the element column in the #__extensions table in your database.
The value of the type tag describes what extension it is, e.g. whether it is a component, a module or a
plugin.
The value of the tag version is the version number for this version. This version number must be
higher than the currently installed version of the extension in order for the available update to be
displayed.
The tag changelogurl is optional and allows to display a link informing about the changes in this
version. This file is also the subject of this chapter.

Page 528 January 2023


52. Joomla update and change log setup Joomla 4 - Developing Extensions

The tag infourl is optional and allows you to display a link that informs about the update or a version
note.

The tag downloads shows all available download locations for an update. The value of the tag
downloadurl is the URL to download the extension. This file can be located anywhere. The attribute
type describes whether it is a full package or an update, and the format. And the attribute format
defines the package type like zip or tar.

The tags maintainer and maintainerurl are self-explanatory.

The tag targetplatform describes the Joomla version for which this update is intended. The value of
the attribute name should always be set to “joomla”: <targetplatform name="joomla"version
="4.*"/>.

If you create your update for a specific Joomla version you can use min_dev_level and
max_dev_level.

Sometimes you want your update to be available for a minimum PHP version. Do this with the tag
php_minimum.

At the end, close all tags </update></updates>.

For plugins, add a tag called folder and a tag called client. These tags are only needed for
plugins.

The tag folder describes the type of plugin. Depending on the plugin type, this can be system
, content or search, for example. The value of the client tag describes the client_id in the
database table #__extensions. The value for plugins is always 0, components are always 1. Modules
and Templates, however, may vary depending on whether it is a frontend 0 or a backend 1 module.

Below you can see the complete file.

foo_update.xml

1
2 <updates>
3 <update>
4 <name>com_foos</name>
5 <description>This is com_foo</description>
6 <element>com_foos</element>
7 <type>component</type>
8 <version>1.0.1</version>
9 <changelogurl>https://codeberg.org/astrid/j4examplecode/raw/
branch/tutorial/changelog.xml</changelogurl>
10 <infourl title="agosms">https://codeberg.org/astrid/
j4examplecode/src/branch/v1.0.1/README.md</infourl>
11 <downloads>

January 2023 Page 529


Joomla 4 - Developing Extensions 52. Joomla update and change log setup

12 <downloadurl type="full" format="zip">https://github.com/


astridx/boilerplate/releases/download/v1.0.1/com_foos
-1.0.1.zip</downloadurl>
13 </downloads>
14 <maintainer>Foo Creator</maintainer>
15 <maintainerurl>http://www.example.com</maintainerurl>
16 <targetplatform name="joomla" version="4.*"/>
17 <php_minimum>7.1</php_minimum>
18 </update>
19 </updates>

Do you like to use a checksum? See the test description in this PR if you don’t know how to do this.
Under Ubuntu Linux it is possible to calculate the checksum via the console with sha256sum -b
myfile.zip or sha284sum -b myfile.zip.

52.1.1.2. changelog.xml (Changelog)

Information on the changelog can be found on Github in PR github.com/joomla/joomla-cms/pull/24026


and the Joomla documentation1 . Below you can see an example file.

changelog.xml

1
2 <changelogs>
3 <changelog>
4 <element>com_foos</element>
5 <type>component</type>
6 <version>1.0.0</version>
7 <note>
8 <item>Initial Version</item>
9 </note>
10 </changelog>
11 <changelog>
12 <element>com_foos</element>
13 <type>component</type>
14 <version>1.0.1</version>
15 <security>
16 <item><![CDATA[<p>No security issues.</p>]]></item>
17 </security>
18 <fix>
19 <item>No fix</item>
20 </fix>
21 <language>
22 <item>English</item>
23 </language>
24 <addition>
1
docs.joomla.org/adding_changelog_to_your_manifest_file/en

Page 530 January 2023


52. Joomla update and change log setup Joomla 4 - Developing Extensions

25 <item>Change log and Update Server added.</item>


26 </addition>
27 <change>
28 <item>No change</item>
29 </change>
30 <remove>
31 <item>No remove</item>
32 </remove>
33 <note>
34 <item>Change log and Update Server added.</item>
35 </note>
36 </changelog>
37 </changelogs>

You don’t know what <![CDATA[ ... ]]> means? The term CDATAa is used in the XML markup
language for various purposes. It indicates that a given part of the document is general characters
rather than program code with a more specific, limited structure. The CDATA section may contain
markup characters (<, > and &). These are not interpreted further by the parser. The use of entities
such as &lt; and &amp; is not necessary.
a
en.wikipedia.org/wiki/cdata

52.1.2. Modified files

52.1.2.1. administrator/components/com_foos/foos.xml

Only the version number has been adjusted. This change is necessary in every new chapter, because a
new function is always added. I do not mention this explicitly in the following.

administrator/components/com_foos/foos.xml

1 <authorUrl>[AUTHOR_URL]</authorUrl>
2 <copyright>[COPYRIGHT]</copyright>
3 <license>GNU General Public License version 2 or later;</license>
4 - <version>1.0.0</version>
5 + <version>1.0.1</version>
6 <description>COM_FOOS_XML_DESCRIPTION</description>
7 <namespace path="src">FooNamespace\Component\Foos</namespace>
8 <scriptfile>script.php</scriptfile>

52.2. Test your Joomla component

1. install your component in Joomla version 4 to test it:

January 2023 Page 531


Joomla 4 - Developing Extensions 52. Joomla update and change log setup

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installa-
tion.
Copy the files in the components folder into the components folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the files from part 1.

2. Next, create another version of the example extension. To do this, change the version number in
the manifest. Before that, it is not possible to test the update server. Because, there is no update
yet. I mention this here anyway, what exactly happens after the creation of the next versions. 3.

3. if everything works, you will see these displays in front of you after the installation, if you click on
the menu System on the left and then select Extension in the section Updates on the right.
The image shows the status after version 23.0.0 was released.

Figure 52.1.: Joomla Update Server

4. so open System | Update | Extension. Here you will be offered the update for your com-
ponent. If this is not the case, click on the button Find Updates.

5. When you open it for the first time you will see the message The Download Key is missing
because you have entered the element dlid in the manifest.

6. Add a download key via System | Update Sites. Click on the name of your component.
Then you will see the text field in which you can enter any value. At the moment, this value is not
checked when the update is retrieved. Save the value.

Page 532 January 2023


52. Joomla update and change log setup Joomla 4 - Developing Extensions

Figure 52.2.: Joomla Update Sites

Figure 52.3.: Joomla Update Sites

7. if you navigate back to System | Update | Extension, you will be able to initiate an update
or view the changelog.

The update was not possible before because the Download Key was not configured.

January 2023 Page 533


Joomla 4 - Developing Extensions 52. Joomla update and change log setup

Click the Find Updates button in the toolbar if the update is no longer displayed.

Figure 52.4.: Joomla Update Server

52.3. Links

Deploying an Update Server2

2
docs.joomla.org/deploying_an_update_server/en

Page 534 January 2023


Joomla 4 - Developing Extensions 53. Form Fields

53. Form Fields

Numerous types of form fields are built into Joomla! The following describes the common standard
types and their parameters.

53.1. Form Field Type Subform

The form field type subform provides a method for using XML forms within another or for reusing
forms within an existing form. When the multiple attribute is set to true, the contained form is
repeatable.

The field has two predefined layouts for displaying the subform as either a table or a div container,
as well as support for custom layouts.

I show an example of an XML field definition for repeatable mode below. Fundamental is the line
multiple="true".

1 <field
2 name="domains"
3 type="subform"
4 label="COM_USERS_CONFIG_FIELD_DOMAINS_LABEL"
5 hiddenLabel="true"
6 multiple="true"
7 layout="joomla.form.field.subform.repeatable-table"
8 formsource="administrator/components/com_users/forms/config_domain.
xml"
9 />

I have not found an example for multiple="false" in Joomla. A use case might be if the number of
subforms depends on a condition. Then multiple="false" can be useful.

Link[docs.joomla.org/Subform_form_field_type]

Page 535 January 2023


Joomla 4 - Developing Extensions 54. Tests

54. Tests

Automated tests are not a special tool for software developers in large projects. Especially for smaller
extensions, automated tests are a help to quickly identify problems. They help to ensure that extensions
work smoothly in newer Joomla versions. The Joomla Core developers want third-party software
developers to test their extensions. This way, bugs are noticed before a user finds them. This requires a
lot of work and is therefore often not done. Especially not if it has to be done manually by humans for
each release. Automatic testing makes it possible to repeat the manual steps for each release without
a human performing the steps themselves. This way, bugs are found before a user encounters them
when accessing the live system.

54.1. Links

https://magazine.joomla.org/all-issues/october-2022/off-to-cyprus-ehm-cypress-how-joomla-does-
its-end-to-end-testing

https://github.com/joomla/joomla-cms/pull/38422

https://www.youtube.com/watch?v=26jL9EVI-98

Page 537 January 2023


Joomla 4 - Developing Extensions

Part VI.

Outro

Page 539 January 2023


Joomla 4 - Developing Extensions 55. We have come to the end

55. We have come to the end

I hope you enjoyed reading it and at the same time learned the basics of programming with Joomla.
If you enjoyed the book, I’d be happy if you shared it with your friends — especially those who are
interested in Joomla. A constructive review helps me provide better content in the future based on
your feedback.

From here, I recommend you turn the sample extension into a real one.

To create your own extension with your own name based on the Boilerplate-Extension, I use the
file duplicate.sha .
a
github.com/astridx/boilerplate/blob/t43/duplicate.sh

Create your own Joomla project. Try out what you have learned and share it on Github. After you
have mastered the basics, I recommend the following to expand your extension and knowledge in a
meaningful way:

• Look at how you use Coding Standards1 and use those


• Write Tests2 for your extension.

Thank you for reading my book.

1
developer.joomla.org/coding-standards/basic-guidelines.html
2
docs.joomla.org/Testing_Joomla_Extensions_with_Codeception

Page 541 January 2023


Joomla 4 - Developing Extensions Index

Index

G ET, 58 database, 63
P OST, 58 prefix, 13
update, 66
access control list, 119 using, 71
actions, 219 dates, 15
alias, 63
Debug Console, 45
alternvative overrides, 475
DEPLOY VERSION, 35
API, 379
design pattern, 34
autoload
factory method, 34
autoload psr4.php, 9
observer, 172

backend form, 287 Registy, 229


batch, 253 DI Container, 33
blank last line, 11 dialog box, 92
bootstrap.tab, 167 DisplayController (Backend), 35
BUMP VERSION, 35 DocBlock, 35, 95

categories Empty State, 78


backend, 143 Event
frontend, 315 onContentAfterDisplay, 171
CDATA, 50, 531 onContentAfterTitle, 171
changelog, 530 onContentBeforeDisplay, 171
checkin and checkout, 247
featured, 265
coding standards, 35
Fields, 500
commercial extensions, 30
showon, 498
configuration (global), 113
subform, 498
custom fields
backend, 163 filtering, 205
frontend, 171 Fontawesome, 14
Cypress, 537 form
JavaScript, 72
dashboard, 361 Form field, 85

Page 543 January 2023


Joomla 4 - Developing Extensions Index

form fields, 535 parameter, 57, 58, 225


subform, 535 useglobal, 228
frontend editing, 291 PHP
alternative syntax PHP, 13
Google Lighthouse, 517
comparison operator, 12
Help Site, 259 single quotes and double quotes, 12
htmlspecialchars, 496 PHP end tag, 12
plugins, 391
images, 14 Action Log, 392
index.html, 11, 40 API Authentication, 392
inline help, 259 Authentication, 392
Input, 58 Behaviour, 392
installation manifest, 27 CAPTCHA, 392
installation script, 30 Content, 393, 414
JavaScript Editor Button, 393, 420
form, 72 Editors, 393
Extensions, 393
language Strings, 101 Fields, 394, 398
layout, 241 FileSystem, 394
Logging, 45 Finder, 394
Installer, 394
menu item
Media Action, 395
backend, 27
Privacy, 395
frontend, 49
Quick Icon, 395
modal, 92
Sample Data, 395
Model-View-Controller, 53
System, 395, 411
modul chromes, 475
Task, 396, 405
module, 427
Two Factor Authentication, 396
helper, 433
User, 396
installation script, 443
Web Services, 396
namespace, 433
Workflow, 396
parameter, 437
publish, 153
multilingual associations, 179
Request, 58
namespace, 10
routing, 47, 184, 331
overrides, 475
Search Engine Friendly (SEF), 47, 331
package, 523 alias, 63
pagination, 237 slug, 269

Page 544 January 2023


Index Joomla 4 - Developing Extensions

searching, 205 seo, pwa, 517


service, 331 positions, 465
administrator, 193, 280 system messages, 466
category, 323 web assets, 503
Icon for frontend editing, 291 tests, 537
provider, 67 toolbar, 219
Search Engine Friendly (SEF), 331
uitab, 167
slug, 269
unpbulish, 153
sorting, 205
Update Server, 528
subtemplates, 478
useglobal
system messages, 466
parameter, 228
tags, 367
validation (client-side), 137
limit, 377
validation (server-side), 129
technical requirements, 11
variable, 57
template, 451
dark mode, 507 web services, 379
error messages, 466 WebAssetManager, 92
favicon, 513 attribute, 94
overrides, alternative overrides, layouts, dependencies, 94
module chomes, 475 joomla.asset.json, 92
parameters, 495 relative, 93
performance, accessibility, best practice, version, 93

January 2023 Page 545

You might also like