For developers
Both project list plugins - Project list and
Project list (single selection) - dispatch two PSR-14 events. They
are the supported way to change what a plugin queries and what it renders, and
they replace the only alternative there used to be: subclassing
Project and re-registering the plugin.
Which classes of this extension are public API, and what that promises, is
stated for all academic extensions on the extension points page of
academic_base.
Both project list plugins also dispatch
Modify of
academic_base when they render, after the list event; that page
describes it.
The two events
| Event | Dispatched | A listener may |
|---|---|---|
\FGTCLB\ | in
list, after the demand is built from the content
element settings and the request and before the projects are
queried | replace the demand |
\FGTCLB\ | in the same action, after the query and before the view variables are assigned | replace the projects, replace the applicable categories, assign further view variables |
One action serves both plugins, so both events fire for both. Which one is
rendering is on the context,
\FGTCLB\,
which
get returns: the request, the site
and its language, the content object of the element, the settings of the
content element and the plugin name.
Neither event fires for a submission of the filter and sorting form: the plugin answers the POST with a redirect to a URL carrying the selection before the demand event, and both events fire on the request that follows, with the selection in the query string (or the route), not in the parsed body. See Feature: Project filter selections have a URL.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use FGTCLB\AcademicProjects\Event\ModifyProjectDemandEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
final class ShowRunningProjectsOnly
{
#[AsEventListener(identifier: 'my-extension/show-running-projects-only')]
public function __invoke(ModifyProjectDemandEvent $event): void
{
if ($event->getPluginControllerActionContext()->getPluginName() !== 'ProjectList') {
return;
}
$event->getDemand()->setActiveState('active');
}
}
The plugin names are the names the plugins were registered with -
Project and
Project - not the content element
types.
Modify carries the view as well, so a listener assigns
values a project template renders:
$event->getView()->assign('projectCount', count($event->getProjects()->toArray()));
Rules worth knowing
Warning
A demand listener widens as easily as it narrows. The demand is the
query here, not a constraint added to one, and almost nothing guards it:
set shows hidden project pages to every
visitor,
set drops the storage restriction the editor chose
in the content element, and
set overrides the editor's
ordering for every element that renders - though only for a value that is
one of the
Sorting constants, since anything else is ignored
without a word. Two listeners that disagree are resolved by the order they
run in - the last one wins.
What a listener cannot undo: the page type is pinned unconditionally,
show reaches the
disabled
flag alone (start- and
endtime,
fe_ and
deleted
stay in effect), and a
uid
tiebreaker is always appended to the ordering.
A replaced demand starts from the defaults.
set is there for
a listener that builds its own demand, and such a demand carries none of what
the plugin put in the one it was handed: the
show choice of
the editor, and the three the visitor can set themselves through the filter
form - the
sorting
, the
active and the
filter - so a replacement resets the ordering, the state
filter and the category filter under them.
show has to travel
with
pages
or it changes what they mean: the repository reads
pages
as the selected project uids when
show is true and
as storage page uids when it is false, so a single-selection element whose
demand is replaced without it turns into a storage-folder restriction. Mutate
the demand where that is enough, and carry
get,
get,
get,
get,
get and
get
over where it is not.
A replaced result is rendered as it is.
set takes whatever
query result a listener hands back, and the ordering of the repository is not
reapplied to it. A listener that builds its own result gives it its own
ordering, or the list is in whatever order the database happens to return -
which is not the same list twice on PostgreSQL.
The categories are not recomputed. They are computed from the queried projects, once, before the list event. A listener that replaces the projects and wants the filter of the plugin to match them sets the categories too:
$event->setProjects($narrowedResult);
$event->setCategories(
$this->categoryRepository->findAllApplicable('projects', ...$narrowedResult->toArray()),
);
find is what the controller itself calls: it keeps every
category of the group and marks the ones no record carries as disabled options.
find returns a bare list instead, so a listener that
reaches for that one drops the disabled options the filter otherwise shows.
Nothing changes in an installation that has no listener: both plugins query and render exactly what they did before.