ModifyProfileQueryEvent 

Narrowing what a plugin shows 

\FGTCLB\AcademicPersons\Event\ModifyProfileQueryEvent and \FGTCLB\AcademicPersons\Event\ModifyContractQueryEvent are dispatched immediately before a plugin query is executed, and a listener adds conditions to it. They are the supported way to make a plugin show fewer records than it would, by a consent flag, a site the profile belongs to or an editorial state.

Event Dispatched for
ModifyProfileQueryEvent the profile query of the list, list-and-detail and card plugins, the letter query of the two list plugins (see Which letters lead somewhere), and the uid lookup of the selected-profiles plugin
ModifyContractQueryEvent the uid lookup of the selected-contracts plugin

When it is dispatched 

ProfileRepository dispatches the event once for every profile query it runs for a plugin, after the query settings and the conditions of the plugin are prepared and before they are applied:

  • List and list-and-detail plugins, list action: once for the profiles of the list, after ModifyProfileDemandEvent. While the content element shows the letter navigation and no profiles are selected by hand, once more for the letters, see Which letters lead somewhere.
  • Card plugin: once for the profiles the card shows, also after ModifyProfileDemandEvent .
  • Selected-profiles plugin: once for the uid lookup of the selected profiles. No demand event precedes it.

The detail view is not covered, see the rules below. ModifyPluginViewEvent follows when the action renders, see ModifyPluginViewEvent.

Code that calls ProfileRepository::findByDemand() or ProfileRepository::findByUids() outside the plugins dispatches the event as well, without a plugin context.

What a listener reads and changes 

Method What it does
getQuery() The Extbase QueryInterface of the profiles, to build the conditions on.
addConstraint() Adds a condition. Every listener may add any number of them.
getConstraints() The conditions the listeners added so far.
getDemand() The list demand the query is built from, after the listeners of ModifyProfileDemandEvent . null for the uid lookup of the selected-profiles plugin, which knows no demand.
getPluginControllerActionContext() The plugin the query is rendered for, see The plugin action context. null where the query has no plugin behind it.

The context carries the plugin name, the settings of the content element, the request, the site, the language and the content object. ModifyContractQueryEvent has the same methods apart from getDemand() .

Example 

A site that lists only the profiles of persons who agreed to be published keeps the agreement in a column of its own, here public_display_consent on the profile table. The listener restricts the two list plugins to those profiles and leaves the card and the selected profiles alone, which an editor fills by hand:

EXT:my_extension/Classes/EventListener/ShowOnlyConsentingProfiles.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use FGTCLB\AcademicPersons\Event\ModifyProfileQueryEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

final class ShowOnlyConsentingProfiles
{
    #[AsEventListener(identifier: 'my-extension/show-only-consenting-profiles')]
    public function __invoke(ModifyProfileQueryEvent $event): void
    {
        // The two list plugins only. The card plugin of the same page keeps everything.
        if (!in_array($event->getPluginControllerActionContext()?->getPluginName(), ['List', 'ListAndDetail'], true)) {
            return;
        }
        $query = $event->getQuery();
        $event->addConstraint($query->equals('publicDisplayConsent', true));
    }
}
Copied!

getPluginName() is the name a plugin is registered with, not its content element type. The six names are listed in The plugin names. The letter query of the list carries the same context as the list query, so the listener narrows the letters in the same way.

Rules and pitfalls 

The collected conditions are combined with a logical AND with the ones the extension builds itself: the storage folders, the organisational unit and function type filters of the content element, the letter filter, hidden records and the language handling. They are applied before pagination. A constraint therefore only ever narrows a result, and it cannot drop a condition the extension put there.

The detail view is not covered: neither event is dispatched for it. It resolves its profile through Extbase argument mapping, and the one finder it does use, for a hidden profile while the plugin's "show hidden records" is on, builds its own query and never goes through the shared path these events sit in. So a profile a listener hides from the lists is still reachable through its own detail URL. A condition that has to hold there as well belongs in an access check, not in these events.

A listener that answers the list query and the letter query differently makes the letter navigation offer letters that lead to an empty list, or hide letters that do not. Answer both calls alike.

A project that narrowed the plugins by subclassing or XCLASSing the repositories moves that code here, see Breaking: The plugins call different repository finders.

Introduced in