Extending the job board 

These extension points exist:

PSR-14 event PostProcessFluidVariablesEvent
\JWeiland\Jobboard\Event\PostProcessFluidVariablesEvent is dispatched by the actions list and search right before the variables are assigned to the view. Listeners can read the request and the settings and add or replace variables with addFluidVariable() . The variable jobs is a query result ( \TYPO3\CMS\Extbase\Persistence\QueryResultInterface ) that is not executed yet, so a listener can limit it in the database. The page browser is built this way, see \JWeiland\Jobboard\EventListener\AddPaginatorEventListener and Page browser. The options of the search form are built before the event, with aggregate queries of \JWeiland\Jobboard\Service\JobSearchOptionsService .
Templates
All templates, partials and layouts can be overridden, see Templates and frontend assets. This includes the info window of the map, see Info window of a job location.
Info window of the map
With EXT:maps2, the job board renders the info window of job locations in a listener of the PSR-14 event \JWeiland\Maps2\Event\RenderInfoWindowContentEvent , see Info window of job locations.
Job sources
New import sources are classes tagged with api.model , see Connect a job source to the import.
DataHandler hooks and events
The import writes all records with the DataHandler, so the hooks and events of the TYPO3 Core are triggered, for example for search indexing. The job board itself uses the DataHandler hook \JWeiland\Jobboard\Hook\UpdateEffectiveSalaryHook to calculate the effective salary of jobs, see Update the effective salary of the jobs. Write jobs and salary records with the DataHandler as well, or run vendor/bin/typo3 jobboard:salary:update --all afterwards.
Page title
The page title provider can be replaced with TypoScript, see Job title as page title.
Routing
The route enhancer can be copied into your site configuration and changed, see Speaking URLs (route enhancer).
Logging
All messages of the extension use the log channel JWeiland.Jobboard . Configure own writers in $GLOBALS['TYPO3_CONF_VARS']['LOG']['JWeiland']['Jobboard'] .

The address model \JWeiland\Jobboard\Domain\Model\Address extends the model of EXT:tt_address and adds the map location of EXT:maps2. It is mapped to the table tt_address in EXT:jobboard/Configuration/Extbase/Persistence/Classes.php.

Info window of job locations 

When a visitor clicks a marker, EXT:maps2 requests the info window content with an AJAX request and dispatches the PSR-14 event \JWeiland\Maps2\Event\RenderInfoWindowContentEvent with the record of the map location ( getPoiCollectionRecord() ) and the request.

The job board listens to this event with \JWeiland\Jobboard\EventListener\RenderJobLocationInfoWindowContentEventListener (identifier jobboard/renderJobLocationInfoWindowContent). The listener is registered in EXT:jobboard/Configuration/Services.php only if EXT:maps2 is installed. It works like this:

  • If another listener has already set content ( getInfoWindowContent() is not empty), it does nothing.
  • It searches the addresses with this map location and their open jobs, see Info window of a job location. If there is none, it does nothing. Markers of other extensions are therefore not affected, EXT:maps2 renders their info window as usual.
  • Otherwise, it renders Templates/Map/InfoWindow.html and sets the result with setInfoWindowContent() .

To change the HTML of the info window, override the template, see Info window of a job location.

To replace the info window with your own PHP code, register a listener of the same event that runs before the listener of the job board. If your listener sets content, the job board keeps it:

EXT:my_sitepackage/Classes/EventListener/MyJobLocationInfoWindowEventListener.php
<?php

declare(strict_types=1);

/*
 * This file is part of the package vendor/my-sitepackage.
 *
 * For the full copyright and license information, please read the
 * LICENSE file that was distributed with this source code.
 */

namespace Vendor\MySitepackage\EventListener;

use JWeiland\Maps2\Event\RenderInfoWindowContentEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(
    identifier: 'my-sitepackage/job-location-info-window',
    before: 'jobboard/renderJobLocationInfoWindowContent',
)]
final readonly class MyJobLocationInfoWindowEventListener
{
    public function __invoke(RenderInfoWindowContentEvent $event): void
    {
        $poiCollectionUid = (int)($event->getPoiCollectionRecord()['uid'] ?? 0);
        if ($poiCollectionUid === 0) {
            return;
        }

        // Render your own content. As the content is not empty anymore, the
        // listener of the job board does not change it.
        $event->setInfoWindowContent(
            '<p>' . htmlspecialchars('Location ' . $poiCollectionUid) . '</p>',
        );
    }
}
Copied!

A listener that runs after the listener of the job board can read and change the content set by the job board with getInfoWindowContent() and setInfoWindowContent() .

Job properties for templates 

Useful properties of {job} in Fluid:

{job.salaryRangeMin} , {job.salaryRangeMax}
Lowest and highest salary, for both salary modes. In salary mode "Salary grade", these are the lowest and highest positive amount of all steps or fixed amounts of all valid selected grades. In salary mode "Free salary entry", the maximum falls back to the minimum if it is empty. Use these properties instead of evaluating {job.salaryMode} .
{job.hasSalaryRange}
true if minimum and maximum differ.
{job.hasSalaryInformation}
true if the job has any salary information.
{job.effectiveSalaryMin} , {job.effectiveSalaryMax}
The stored effective salary, see Update the effective salary of the jobs. {job.hasEffectiveSalaryInformation} is true if one of them is greater than 0. The list and the detail view use it to decide whether a job is shown.
{job.jobAreas} , {job.salaryGrades} , {job.benefits}
All related records. {job.jobArea} and {job.salaryGrade} return the first record only.
File properties
{job.headerLogos} , {job.tenderFiles} and {job.pdfFiles} are file references. They are always lists, also {benefit.image} . Use <f:for> to render them.
{job.employerAddress}
The address of the employer (EXT:tt_address). The name of the employer is {job.employerAddress.company} , the logo {job.employerAddress.firstImage} . Both may be empty, and the job may have no employer address at all.
{job.isImport} , {salaryGrade.hasSteps}
Boolean values.