ModifyProjectDemandEvent 

\FGTCLB\AcademicProjects\Event\ModifyProjectDemandEvent hands a listener the demand a project list is about to be queried with. The demand a listener hands back is the one that is queried and assigned to the view as {demand} .

When it is dispatched 

ProjectController::listAction() dispatches the event once per rendering of the Projects and the Selected Projects content elements, after the demand is built from the settings of the content element and the selection of the visitor, and before the projects are queried. ModifyProjectListEvent follows once the projects are queried.

The event does not fire 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 event, and the event fires 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.

What a listener reads and changes 

Method What it does
getDemand() The demand, \FGTCLB\AcademicProjects\Domain\Model\Dto\ProjectDemand . It is an object, so a change to it takes effect without setDemand() .
setDemand() Replaces the demand that is queried.
getPluginControllerActionContext() The context of the rendering, \FGTCLB\AcademicBase\Domain\Model\Dto\PluginControllerActionContextInterface : the request, the site and its language, the content object of the element, the settings of the content element and the plugin name.

What the query reads from the demand:

Getter and setter Meaning
getPages() , setPages() The uids of the field below the options: the pages the projects sit directly in, or the picked project pages. Empty means every project page of the installation.
getShowSelected() , setShowSelected() How the pages are read: true for Selected Projects, where they are the project pages themselves, matched across languages, false for Projects, where they are the parent pages.
getActiveState() , setActiveState() all, active or completed. The setter throws an \InvalidArgumentException for any other value.
getFilterCollection() , setFilterCollection() The selected categories, those of the visitor or the Categories of the element, as a \FGTCLB\CategoryTypes\Collection\FilterCollection , or null for none. A project has to carry every one of them.
getSorting() , setSorting() , and the field and the direction on their own The ordering, one of the values of \FGTCLB\AcademicProjects\Enumeration\SortingOptions , for example tx_academicprojects_start_date desc.
getShowHiddenRecords() , setShowHiddenRecords() Whether hidden project pages are listed, the field Show hidden records of the element.

The plugin names are the names the plugins were registered with, ProjectList and ProjectListSingle , not the content element types.

Example 

A site keeps its main project list to the running projects, whatever state the editor or the visitor chose, and leaves the selections made by hand alone:

EXT:my_extension/Classes/EventListener/ShowRunningProjectsOnly.php
<?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');
    }
}
Copied!

The state select of the form then still offers All and Completed, which change nothing. Switch off Show active state options in the content elements the listener acts on, or check the content element in the listener, through the record on getContentObjectRenderer() of the context.

Rules and pitfalls 

A replaced demand starts from the defaults. setDemand() 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 showHiddenRecords choice of the editor, and the three the visitor can set themselves through the filter form, the sorting , the activeState and the filterCollection , so a replacement resets the ordering, the state filter and the category filter under them. showSelected has to travel with pages or it changes what they mean: the repository reads pages as the selected project uids when showSelected 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 getPages() , getShowSelected() , getShowHiddenRecords() , getActiveState() , getFilterCollection() and getSorting() over where it is not.

A category a listener adds is shown as an active filter. The active filter tags of Active filters, reset link and result count read the demand the list was queried with. A category a listener adds to every request is a tag the visitor cannot remove, since the listener adds it again on the page the tag leads to. The same holds for a state other than all.

A field the project adds to the filter form does not reach the event. The redirect after a submission carries the selection the plugin accepted and nothing else, so a listener cannot read a field of its own from the request that follows.

Introduced in