ModifyTcaSelectFieldItemsEvent 

\FGTCLB\AcademicBase\Event\ModifyTcaSelectFieldItemsEvent is owned by academic_base, which describes it for every academic extension in its developer chapter. This page says which items of academic_jobs it carries.

When it is dispatched 

Two fields of the job record build their items with an item provider of this extension, and each provider dispatches the event once each time it runs. The option Job Type of the job list offers the job types of the record behind its own None, and dispatches the event with the table and the field of the record:

Field table and field Item provider Shipped items
Job Type tx_academicjobs_domain_model_job, type \FGTCLB\AcademicJobs\Backend\FormEngine\TypeItems 1 Job, 2 Sidejob, 3 Thesis
Employment Type tx_academicjobs_domain_model_job, employment_type \FGTCLB\AcademicJobs\Backend\FormEngine\EmploymentTypeItems 1 Full-time, 2 Part-time
Job Type of the job list tx_academicjobs_domain_model_job, type \FGTCLB\AcademicJobs\Backend\FormEngine\JobListTypeItems , which calls TypeItems 1 Job, 2 Sidejob, 3 Thesis, after 0 None, which the listeners do not get

The providers run in three places:

  • in the backend, when an editor opens the record of a job.
  • in the backend, when an editor opens a Job List content element, for its option Job Type.
  • in the frontend, each time the Job Form renders, to build the choices of its two select fields. The form translates the labels after the listeners ran, so a label may be an LLL: reference.

What this extension passes in 

Method Returns
getParameters() , setParameters() The parameters of the item provider. items holds the items, the ones of this extension as arrays with label and value . table , field , effectivePid , site and TSconfig say where they are built. In the backend row holds the job being edited. In the frontend row is an empty array, and effectivePid is the page of the form.

For the job list, table and field name the field of the job record, and every other parameter is the one TYPO3 passes for the FlexForm field: flexParentDatabaseRow holds the content element, row the values of its FlexForm, effectivePid its page, and config and TSconfig belong to the FlexForm field. Only the job list passes flexParentDatabaseRow , so a listener that means the record or the list alone checks it:

$isJobList = isset($parameters['flexParentDatabaseRow']);
Copied!

In the backend form, TYPO3 v13 and v14 hand the static items of a field to the item provider as \TYPO3\CMS\Core\Schema\Struct\SelectItem objects, and the items a provider adds are arrays. A SelectItem implements \ArrayAccess , so a closure typed array|SelectItem that reads $item['value'] works for both.

The two job fields declare no static items, but a project that adds one in the TCA gets an object in the backend form. The job form of the frontend hands every item over as an array. Such an item comes first, and a shipped item with a value it declares already is left out, see Important: Job type items keep the declared items.

Example: the job types of an institution 

An institution does not publish side jobs, and advertises doctoral positions as a type of their own. The listener removes the one type and adds the other, in the backend and in the job form alike:

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

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use FGTCLB\AcademicBase\Event\ModifyTcaSelectFieldItemsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Schema\Struct\SelectItem;

final class AdaptJobTypes
{
    private const JOB_TYPE_SIDEJOB = 2;
    private const JOB_TYPE_DOCTORAL_POSITION = 10;

    #[AsEventListener(identifier: 'my-extension/adapt-job-types')]
    public function __invoke(ModifyTcaSelectFieldItemsEvent $event): void
    {
        $parameters = $event->getParameters();
        if ($parameters['table'] !== 'tx_academicjobs_domain_model_job'
            || $parameters['field'] !== 'type'
        ) {
            return;
        }
        $parameters['items'] = array_values(array_filter(
            $parameters['items'],
            static fn(array|SelectItem $item): bool => (int)($item['value'] ?? 0) !== self::JOB_TYPE_SIDEJOB,
        ));
        $parameters['items'][] = [
            'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:jobType.doctoralPosition',
            'value' => self::JOB_TYPE_DOCTORAL_POSITION,
        ];
        $event->setParameters($parameters);
    }
}
Copied!

The job list and the job detail name the type of a job with the frontend label jobs.type.<value> of this extension, so the new type needs one, see Labels:

TypoScript setup
plugin.tx_academicjobs._LOCAL_LANG.default.jobs.type.10 = Doctoral position
Copied!

Rules and pitfalls 

Check the table and the field first. The event is dispatched by the item providers of several academic extensions, for fields of other tables and of FlexForms, so a listener returns early for every field it does not mean.

Do not rely on the row. The job form calls the providers without a record and passes an empty row . The event declares row optional, so a listener reads it with a fallback, $parameters['row'] ?? [] , and every field of it as well, for example the page as $row['pid'] ?? $parameters['effectivePid'] . For the job list, that is the page of the content element, because row holds the values of its FlexForm and has no pid . The storage folder of the list is a field of the content element, which flexParentDatabaseRow holds.

Removing an item hides it. A job that already has the value keeps it, and the job form does not check a submitted value against the items.

The job list offers the same types. A type a listener adds to the field type can be chosen for the job list as well, and one it removes is not offered there, see Important: Job list offers the job types of listeners. Give a type a positive integer as its value: the column of the job stores an integer, and the list treats 0 and below as None.

Introduced in