Skip to content
Snippets Groups Projects
JoblistController.php 29.8 KiB
Newer Older
<?php

/***************************************************************
 *  Copyright notice
 *
 *  (c) sgalinski Internet Services (https://www.sgalinski.de)
 *
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
 *
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/

namespace SGalinski\SgJobs\Controller;

use SGalinski\SgJobs\Controller\Ajax\UploadController;
use SGalinski\SgJobs\Domain\Model\Company;
use SGalinski\SgJobs\Domain\Model\Job;
use SGalinski\SgJobs\Domain\Model\JobApplication;
use SGalinski\SgJobs\Domain\Repository\CompanyRepository;
use SGalinski\SgJobs\Domain\Repository\DepartmentRepository;
use SGalinski\SgJobs\Domain\Repository\ExperienceLevelRepository;
use SGalinski\SgJobs\Domain\Repository\JobApplicationRepository;
use SGalinski\SgJobs\Domain\Repository\JobRepository;
use SGalinski\SgJobs\Service\FileAndFolderService;
use SGalinski\SgMail\Service\MailTemplateService;
use SGalinski\SgSeo\Service\HeadTagService;
use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Country\CountryProvider;
use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Http\ImmediateResponseException;
use TYPO3\CMS\Core\Resource\Exception\AbstractFileOperationException;
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderReadPermissionsException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException;
use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException;
use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException;
use TYPO3\CMS\Extbase\Property\Exception\TypeConverterException;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\ErrorController;
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;

/**
 * The joblist plugin controller
 */
class JoblistController extends ActionController {
	// the array key for the error message in the post-array
	public const ERROR_KEY_IN_POST = 'error';
	private const UPLOADED_FILES = [
		'coverLetter',
		'cv',
		'certificate',
	];

Stefan Galinski's avatar
Stefan Galinski committed
	protected $companyRepository;
Stefan Galinski's avatar
Stefan Galinski committed
	protected $jobRepository;
Stefan Galinski's avatar
Stefan Galinski committed
	protected $jobApplicationRepository;
Stefan Galinski's avatar
Stefan Galinski committed
	protected $departmentRepository;
	/**
	 * @var ExperienceLevelRepository
	 */
Stefan Galinski's avatar
Stefan Galinski committed
	protected $experienceLevelRepository;
Stefan Galinski's avatar
Stefan Galinski committed
	protected $fileAndFolderService;
	/**
	 * @var string
	 */
	 * @param CompanyRepository $companyRepository
	public function injectCompanyRepository(CompanyRepository $companyRepository): void {
		$this->companyRepository = $companyRepository;
	/**
	 * Inject the CompanyRepository
	 *
	 * @param FileAndFolderService $fileAndFolderService
	 */
	public function injectFileAndFolderService(FileAndFolderService $fileAndFolderService): void {
		$this->fileAndFolderService = $fileAndFolderService;
	}

	 * @param DepartmentRepository $departmentRepository
	public function injectDepartmentRepository(DepartmentRepository $departmentRepository): void {
		$this->departmentRepository = $departmentRepository;
	}
	/**
	 * Inject the ExperienceLevelRepository
	 *
	 * @param ExperienceLevelRepository $experienceLevelRepository
	 */
	public function injectExperienceLevelRepository(ExperienceLevelRepository $experienceLevelRepository): void {
		$this->experienceLevelRepository = $experienceLevelRepository;
	}

	 * Inject the JobApplicationRepository
	 *
	 * @param JobApplicationRepository $jobApplicationRepository
	 */
	public function injectJobApplicationRepository(JobApplicationRepository $jobApplicationRepository): void {
		$this->jobApplicationRepository = $jobApplicationRepository;
	}

	/**
	public function injectJobRepository(JobRepository $jobRepository): void {
		$this->jobRepository = $jobRepository;
	/**
	 * Initializes the class
	 */
	public function __construct() {
		$extensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sg_jobs'] ?? [];
		if (isset($extensionConfiguration['jobFolderPath']) && $extensionConfiguration['jobFolderPath'] !== '') {
			$this->jobFolderPath = $extensionConfiguration['jobFolderPath'];
		} else {
			$this->jobFolderPath = 'JobApplication';
		}
	}

	/**
	 * Initialize the indexAction to set the currentPageBrowserPage parameter
	 */
	public function initializeIndexAction(): void {
		$parameters = $this->request->getQueryParams()['tx_sgjobs_pagebrowser'] ?? 0;
		if (is_array($parameters)) {
			$currentPageBrowserPage = (int) $parameters['currentPage'] ?: 0;
		}

			$this->request = $this->request->withArgument('currentPageBrowserPage', $currentPageBrowserPage);
	/**
	 * Show all job offers and options to manage them
	 *
	 * @param array $filters
	 * @return ResponseInterface
	 * @throws AspectNotFoundException
	 * @throws PageNotFoundException
Matthias Adrowski's avatar
Matthias Adrowski committed
	public function indexAction(
		array $filters = [],
		int $jobId = NULL,
		int $currentPageBrowserPage = 0
	): ResponseInterface {
			$this->view->assign('selectedCountry', $filters['filterCountry'] ?? '');
			$this->view->assign('selectedCompany', $filters['filterCompany'] ?? '');
			$this->view->assign('selectedLocation', $filters['filterLocation'] ?? '');
			$this->view->assign('selectedDepartment', $filters['filterDepartment'] ?? '');
			$this->view->assign('selectedExperienceLevel', $filters['filterExperienceLevel'] ?? '');
			$this->view->assign('selectedFunction', $filters['filterFunction'] ?? '');
			$this->view->assign('selectedRemote', $filters['filterRemote'] ?? '');
		$filters['filterByDepartment'] = $this->settings['filterByDepartment'] ?? '';
		$filters['filterByExperienceLevel'] = $this->settings['filterByExperienceLevel'] ?? '';
		$filters['filterByLocation'] = $this->settings['filterByLocation'] ?? '';

		foreach ($filters as $name => &$filter) {
			if ($name === 'filterByLocation' || $name === 'filterByExperienceLevel' || $name === 'filterByDepartment') {
				$filter = $filter ? GeneralUtility::trimExplode(',', $filter) : NULL;
				if (!$filter) {
					continue;
				}

				foreach ($filter as &$value) {
					$value = (int) $value;
				}
		$storagePids = GeneralUtility::trimExplode(
			',',
			$this->configurationManager->getConfiguration(
				ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
			)['persistence']['storagePid']
		);
		if ($currentPageBrowserPage > 0 && ExtensionManagementUtility::isLoaded('sg_seo')) {
			$headTagService = GeneralUtility::makeInstance(
				HeadTagService::class,
				FALSE,
				'',
				'',
				'&tx_sgjobs_pagebrowser[currentPage]=' . $currentPageBrowserPage
			);
			$headTagService->execute();
		}

		$this->assignFilterValues($storagePids, $filters);
		$this->view->assign('recordPageIds', $storagePids);
		$jobLimit = (int) $this->settings['jobLimit'];

		if ($jobId) {
			/** @var Job $job */
			$job = $this->jobRepository->findByUid($jobId);
			if (!$job) {
				throw new \InvalidArgumentException('Given Job Id is invalid!');
			}

			if (ExtensionManagementUtility::isLoaded('sg_seo')) {
				$headTagService = GeneralUtility::makeInstance(
					HeadTagService::class,
					TRUE,
					$job->getTitle(),
					$job->getDescription(),
					'&tx_sgjobs_jobapplication[jobId]=' . $jobId
				);
				$headTagService->execute();
			}
			$numberOfPages = 1;
		} else {
			// pagination logic
			$offset = 0;
			if ($currentPageBrowserPage && $jobLimit) {
				$offset = $currentPageBrowserPage * $jobLimit;
			}

			$frontendPluginSettings = $this->configurationManager->getConfiguration(
				ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
			);
			$frontendPluginSettings = $frontendPluginSettings['settings'];

			// get all jobs for the current page
			$ordering = (int) $frontendPluginSettings['orderBy'];
			$jobs = $this->jobRepository->findJobsByFilter($filters, $jobLimit, $offset, $ordering)->toArray();

			// get all jobs for the current page
Stefan Galinski's avatar
Stefan Galinski committed
			$allJobsCount = $this->jobRepository->findJobsByFilter($filters)->count();
			$numberOfPages = (int) ($jobLimit <= 0 ? 0 : \ceil($allJobsCount / $jobLimit));
			if ($numberOfPages !== 0 && $currentPageBrowserPage >= $numberOfPages) {
				/** @var PageRenderer $pageRenderer */
				$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
				// the PageRenderer contains the content of the current request, the result of the following sub-request will
				// be appended to it and we get 2 pages, the current one and the 404 page in one. We flush the PageRenderer
				// content here, so it will contain only the 404 page content
				$pageRenderer->setBodyContent('');
				/** @var ErrorController $errorController */
				$errorController = GeneralUtility::makeInstance(ErrorController::class);
				$response = $errorController->pageNotFoundAction(
					$GLOBALS['TYPO3_REQUEST'],
					'The requested page does not exist',
					['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
				);
				throw new ImmediateResponseException($response);
			}
		$this->view->assign('jobs', $jobs);
		$this->view->assign('limit', $jobLimit);
		$this->view->assign('numberOfPages', $numberOfPages);
		$this->view->assign('data', $this->request->getAttribute('currentContentObject')->data);
	 * Renders the application form with an optional job
	 * @return ResponseInterface
	 * @throws AspectNotFoundException
	 * @throws SiteNotFoundException
	 * @throws InvalidArgumentNameException
Matthias Adrowski's avatar
Matthias Adrowski committed
	public function applyFormAction(
		JobApplication $applyData = NULL,
		string $error = '',
		int $jobId = NULL
	): ResponseInterface {
			$this->view->assign('internalError', $error);
			$this->request = $this->request->withArgument('error', NULL);
		if ($jobId === NULL && $this->settings['disallowUnsolicitedApplication']) {
			$uriBuilder = $this->uriBuilder;
			$uri = $uriBuilder
				->setTargetPageUid($this->settings['offersPage'])
				->build();
			$this->redirectToUri($uri, 0, 301);
		}

		$folderName = NULL;
		try {
			$folderName = $this->request->getArgument('folderName');
		} catch (\Exception $exception) {
			// this happens for the initial call, but works for any follow-up call as the form validation
			// throws you back to this one if something has failed
			$folderName = \md5(\uniqid('sgjobs-', TRUE));
			$this->request = $this->request->withArgument('folderName', $folderName);
		$this->view->assign('folderName', $folderName);

		if ($jobId !== NULL) {
			$job = $this->jobRepository->findByUid($jobId);
				if (ExtensionManagementUtility::isLoaded('sg_seo')) {
					$headTagService = GeneralUtility::makeInstance(
						HeadTagService::class,
						FALSE,
						$job->getTitle(),
						$job->getDescription(),
						'&tx_sgjobs_jobapplication[jobId]=' . $jobId
					);
					$headTagService->execute();
				}

				$enableAutomaticRelatedJobs = (bool) $this->settings['enableAutomaticRelatedJobs'];
				if ($enableAutomaticRelatedJobs) {
					$automaticRelatedJobsLimit = (int) $this->settings['automaticRelatedJobsLimit'];
					$relatedJobs = $this->jobRepository->findRelated($job, $automaticRelatedJobsLimit);
					$this->view->assign('relatedJobs', $relatedJobs);
				}
		} else {
			$storagePids = GeneralUtility::trimExplode(
				',',
				$this->configurationManager->getConfiguration(
					ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
				)['persistence']['storagePid']
			);
			$this->view->assign('companies', $this->companyRepository->getAllCompanies($storagePids));
		// display country options
		$context = GeneralUtility::makeInstance(Context::class);
		$sysLanguageUid = $context->getPropertyFromAspect('language', 'id');
		$site = GeneralUtility::makeInstance(SiteFinder::class)
			->getSiteByPageId($GLOBALS['TSFE']->id)
			->getLanguageById($sysLanguageUid);
		$countries = [];
		if (ExtensionManagementUtility::isLoaded('project_base')) {
			$countryProvider = GeneralUtility::makeInstance(CountryProvider::class);
			$countries = $countryProvider->getAll();
		$this->view->assign('countries', $countries);
		$this->view->assign('sysLanguageUid', $sysLanguageUid);
		$allowedMimeTypes = $this->settings['allowedMimeTypes'];
		$this->view->assign('allowedMimeTypes', $allowedMimeTypes);
		$allowedFileExtensions = $this->settings['allowedFileExtensions'];
		$this->view->assign('allowedFileExtensions', $allowedFileExtensions);
			/** @var JobApplication $applyData */
			$applyData = GeneralUtility::makeInstance(JobApplication::class);
			if ($job) {
				$applyData->setJobId($job->getJobId());
        $this->view->assign('isProjectBaseLoaded', ExtensionManagementUtility::isLoaded('project_base'));
		$this->view->assign('applyData', $applyData);
		$this->view->assign('maxFileSize', $this->settings['allowedMaxFileSize']);
		$this->view->assign('maxFileSizeMb', ((int) $this->settings['allowedMaxFileSize'] / 1000) . ' MByte');
Matthias Adrowski's avatar
Matthias Adrowski committed
			'maxFileSizeMessage',
			LocalizationUtility::translate('error.maxFileSizeMessage', 'sg_jobs')

		// This fixes a bug in the form ViewHelper that wants to serialize a Model with closures in it
		$arguments = $this->request->getArguments();
		if (isset($arguments['applyData']) && $arguments['applyData']) {
			$arguments['applyData'] = (string) $arguments['applyData'];
			$this->request = $this->request->withArguments($arguments);
	 * Writes the application CSV file
	 *
	 * @param JobApplication $applicationData
	 * @param string $folderName
	 * @return void
	 */
	protected function createCsvSummary(JobApplication $applicationData, string $folderName): void {
		$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
		$newName = \date('Ymd-His') . '_' . $applicationData->getJobId() . '-' . $applicationData->getFirstName()
			. '-' . $applicationData->getLastName();
		$storage = $resourceFactory->getStorageObject(1);
		$applicationFilePath = Environment::getPublicPath() . '/' .
			$storage->getConfiguration()['basePath'] . 'JobApplication/' . $folderName . '/' . $newName . '.csv';

		$coverLetter = '';
		$coverLetterObject = $applicationData->getCoverLetter();
		if ($coverLetterObject) {
			$coverLetterObject = $coverLetterObject->getOriginalResource();
			if ($coverLetterObject) {
				$coverLetter = $coverLetterObject->getPublicUrl();
			}
		}

		$cv = '';
		$cvObject = $applicationData->getCv();
		if ($cvObject) {
			$cvObject = $cvObject->getOriginalResource();
			if ($cvObject) {
				$cv = $cvObject->getPublicUrl();
			}
		}

		$certificate = '';
		$certificateObject = $applicationData->getCertificate();
		if ($certificateObject) {
			$certificateObject = $certificateObject->getOriginalResource();
			if ($certificateObject) {
				$certificate = $certificateObject->getPublicUrl();
			}
		}

		$dataToInsertArr = [
			$applicationData->getJobId(),
			$applicationData->getFirstName(),
			$applicationData->getLastName(),
			$applicationData->getGender(),
			$applicationData->getCountry(),
			$applicationData->getBirthDate(),
			$applicationData->getEducation(),
			$applicationData->getStreet(),
			$applicationData->getZip(),
			$applicationData->getCity(),
			$applicationData->getNationality(),
			$applicationData->getPhone(),
			$applicationData->getEmail(),
			$coverLetter,
			$cv,
			$certificate,
			$applicationData->getMessage()
		];

		try {
			GeneralUtility::mkdir_deep(\dirname($applicationFilePath));
			$file = \fopen($applicationFilePath, 'wb+');
			\fputcsv($file, $dataToInsertArr);
			\fclose($file);
		} catch (\RuntimeException $exception) {
			$this->redirect('applyForm', NULL, NULL, ['error' => $exception->getMessage()]);
		}
Stefan Galinski's avatar
Stefan Galinski committed
	/**
	 * Pre-apply action setup, configures model-property mapping and handles file upload
	 *
	 * @throws NoSuchArgumentException
Stefan Galinski's avatar
Stefan Galinski committed
	 */
	protected function initializeApplyAction(): void {
	 * Saves the application send by the applyFormAction
	 *
	 * @param JobApplication $applyData
	 * @return ResponseInterface
	 * @throws ExtensionConfigurationExtensionNotConfiguredException
	 * @throws ExtensionConfigurationPathDoesNotExistException
	 * @throws IllegalObjectTypeException
	public function applyAction(JobApplication $applyData): ResponseInterface {
		$folderName = $this->request->getArgument('folderName');
		$enableApplicantMail = (bool) GeneralUtility::makeInstance(ExtensionConfiguration::class)
			->get('sg_jobs', 'enableApplicantMail');
			$job = $applyData->getJob();
				$applyData->setJobId($job->getJobId());
					/** @var Company $company */
					$applyData->setCompany($company);
				}
				/** @var Company $company */
				$company = $this->companyRepository->findByUid(
					(int) $_POST['tx_sgjobs_jobapplication']['applyData']['company']
				);
				if ($company) {
					$applyData->setCompany($company);
				}
			// look for a configured default job, in case of unsolicited application
			$applyData->setPid($GLOBALS['TSFE']->id);
			if ((!$job || !$applyData->getJobId()) && $applyData->getFirstCompany()) {
				$applyData->setJobId($applyData->getFirstCompany()->getJobId());
			if ($applyData->_isNew()) {
				$this->jobApplicationRepository->add($applyData);
			} else {
				$this->jobApplicationRepository->update($applyData);
			}

			$this->moveTmpFolder($folderName, $applyData);
			$this->createCsvSummary($applyData, $folderName);
			/** @var MailTemplateService $mailService */
			$mailService = GeneralUtility::makeInstance(
Matthias Adrowski's avatar
Matthias Adrowski committed
				MailTemplateService::class,
				'application_mail',
				'sg_jobs',
				$this->getApplicationMailMarkers($applyData)
			// set the pageId of the root page, otherwise the templates could be duplicated
			$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($GLOBALS['TSFE']->id);
			$mailService->setPid($site->getRootPageId());
			if ($enableApplicantMail) {
				/** @var MailTemplateService $applicantMailService */
				$applicantMailService = GeneralUtility::makeInstance(
					MailTemplateService::class,
					'applicant_mail',
					'sg_jobs',
					$this->getApplicationMailMarkers($applyData)
				);

				$applicantMailService->setPid($site->getRootPageId());
			}

			// get email from the job contact, fallback is TS settings
			$contact = NULL;
			if ($job !== NULL) {
				$contact = $job->getContact();
			}

			if ($contact !== NULL) {
				$mailService->setToAddresses($contact->getEmail());
Stefan Galinski's avatar
Stefan Galinski committed
				if (($company !== NULL) && $company->getContact() !== NULL) {
					$mailService->setToAddresses($company->getContact()->getEmail());
Stefan Galinski's avatar
Stefan Galinski committed
			$mailService->setMarkers(
				[
					'application' => $applyData,
				]
			);
			$mailService->setIgnoreMailQueue(TRUE);
			if ($enableApplicantMail) {
				$applicantMailService->setToAddresses($applyData->getEmail());
				$applicantMailService->setIgnoreMailQueue(TRUE);
				$applicantMailService->setMarkers(
					[
						'application' => $applyData,
					]
				);
			}

			// add attachments for each file
			$coverLetter = $applyData->getCoverLetter();
			if ($coverLetter) {
				$mailService->addFileResourceAttachment($coverLetter);

			$cv = $applyData->getCv();
			if ($cv) {
				$mailService->addFileResourceAttachment($cv);

			$certificate = $applyData->getCertificate();
			if ($certificate) {
				$mailService->addFileResourceAttachment($certificate);
Stefan Galinski's avatar
Stefan Galinski committed

			$mailService->sendEmail();
			if ($enableApplicantMail) {
				$applicantMailService->sendEmail();
			}
			$redirectPageUid = (int) $this->settings['redirectPage'];
			if ($redirectPageUid) {
				$contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
					$url = $contentObject->typoLink_URL(['params' => $redirectPageUid]);
		} catch (\Exception $exception) {
			$jobId = $job?->getUid();
			// remove application, since there was an exception (e.g. upload filetype mismatch @see: moveTmpFolder())
			$this->jobApplicationRepository->remove($applyData);
			$this->request = $this->request->withArgument('folderName', $folderName);
			return (new ForwardResponse('applyForm'))->withArguments(
				['applyData' => $applyData, 'error' => $exception->getMessage(), 'jobId' => $jobId]
	 * Assign filter values
	 *
	 * @param array $filters
	protected function assignFilterValues(array $rootPageIds, array $filters = []): void {
		$countries = $this->companyRepository->getAllCountries($rootPageIds, $filters['filterByLocation'] ?? []);
		$this->view->assign('countries', $countries);

		$cities = $this->companyRepository->getAllCities($rootPageIds, $filters['filterByLocation'] ?? []);
		$companies = $this->companyRepository->getAllCompanyNames($rootPageIds, $filters['filterByLocation'] ?? []);
		$this->view->assign('companies', $companies);

		$departments = $this->departmentRepository->findAllByFilter($filters['filterByDepartment'] ?? []);
		$this->view->assign('departments', $departments);
		$experienceLevels = $this->experienceLevelRepository->findAllByFilter(
			$filters['filterByExperienceLevel'] ?? []
		);
		$this->view->assign('experienceLevels', $experienceLevels);
	 * Returns the application mail markers
	 *
	 * @param JobApplication $applyData
	 * @return array
	 */
	protected function getApplicationMailMarkers(JobApplication $applyData): array {
		if ($applyData->getFirstCompany() !== NULL) {
			$location = $applyData->getFirstCompany()->getCity();
		return [
			'salutation' => $applyData->getGender(),
			'firstname' => $applyData->getFirstName(),
			'lastname' => $applyData->getLastName(),
			'street' => $applyData->getStreet(),
			'city' => $applyData->getCity(),
			'country' => $applyData->getCountry(),
			'phone' => $applyData->getPhone(),
			'mobile' => $applyData->getMobile(),
			'email' => $applyData->getEmail(),
			'message' => $applyData->getFirstName(),
			'freetextField1' => $applyData->getFreetextField1(),
			'freetextField2' => $applyData->getFreetextField2(),
			'freetextField3' => $applyData->getFreetextField3(),
			'freetextField4' => $applyData->getFreetextField4(),
			'freetextField5' => $applyData->getFreetextField5()
	/**
	 * Move the temp folder to its proper location
	 *
	 * @param string $folderName
	 * @param JobApplication $applicationData
	 * @throws ExistingTargetFileNameException
	 * @throws ExistingTargetFolderException
	 * @throws InsufficientFolderAccessPermissionsException
	 * @throws InsufficientFolderReadPermissionsException
	 * @throws InsufficientFolderWritePermissionsException
	 * @throws TypeConverterException
	 * @throws AbstractFileOperationException
	protected function moveTmpFolder(string $folderName, JobApplication $applicationData): void {
		$allowedFileExtensions = $this->getAllowedFileExtensions();

		/** @var ResourceFactory $resourceFactory */
		$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
		$storage = $resourceFactory->getStorageObject(1); // fileadmin

		$newFolder = $tempFolder = NULL;
		$folder = $storage->getFolder($this->jobFolderPath);
		if ($folder) {
			$tempFolder = $storage->getFolderInFolder(UploadController::JOB_APPLICATION_TEMP_FOLDER, $folder);
			if (!$storage->hasFolderInFolder($folderName, $folder)) {
				$newFolder = $storage->createFolder($folderName, $folder);
			} else {
				$newFolder = $storage->getFolder($this->jobFolderPath . $folderName);
			}
		}

		if (!$newFolder || !$tempFolder) {
			throw new \RuntimeException('Upload folder can\'t be created (write permissions?)!');

		// Move uploaded files & csv fo real folder and delete the tmp folder
		foreach (self::UPLOADED_FILES as $singleFilePostKey) {
			if (!array_key_exists($singleFilePostKey, $_POST)) {
				throw new TypeConverterException(
					LocalizationUtility::translate(
						'error.TypeConverterException.missing.' . $singleFilePostKey,
						'sg_jobs'
					),
					1399312430
Stefan Galinski's avatar
Stefan Galinski committed
			// get the first uploaded document, should be prevented in the frontend to upload more than one
			$singleUploadedFile = current($_POST[$singleFilePostKey])['path'];
			$filePathInfo = PathUtility::pathinfo($singleUploadedFile);
			if (!GeneralUtility::inList($allowedFileExtensions, strtolower($filePathInfo['extension']))) {
				throw new TypeConverterException(
					LocalizationUtility::translate('error.TypeConverterException.type', 'sg_jobs'),
					1399312430
				);
			}

			$basename = $filePathInfo['basename'];
			$newFilename = strtolower($singleFilePostKey) . '.' . strtolower($filePathInfo['extension']);
			if (array_key_exists($singleUploadedFile, $uploadedFiles)) {
				// the same file was uploaded for different sources
				$usableFile = $this->getFileInFolder(
					$uploadedFiles[$singleUploadedFile] . '.' . strtolower($filePathInfo['extension']),
					$newFolder
				);
				if (!$usableFile) {
					throw new \RuntimeException('File not found (' . $singleFilePostKey . ')!');
				}
				$usableFile = $storage->copyFile($usableFile, $newFolder, $newFilename);
			} elseif (!$newFolder->hasFile($newFilename)) {
				// when we reload, etc. this image might already be moved.
				/** @noinspection PhpUnreachableStatementInspection */
				$singleFileToMove = $storage->getFileInFolder($basename, $tempFolder);
				$usableFile = $storage->moveFile($singleFileToMove, $newFolder, $newFilename);
			} else {
				/** @noinspection PhpUnreachableStatementInspection */
				$usableFile = $this->getFileInFolder($newFilename, $newFolder);
			$fileReference = $this->fileAndFolderService->createFileReferenceFromFalFileObject($usableFile);

			if ($singleFilePostKey === 'coverLetter') {
				$applicationData->setCoverLetter($fileReference);
			}

			if ($singleFilePostKey === 'cv') {
				$applicationData->setCV($fileReference);
			}

			if ($singleFilePostKey === 'certificate') {
				$applicationData->setCertificate($fileReference);
			}

			$uploadedFiles[$singleUploadedFile] = strtolower($singleFilePostKey);
Stefan Galinski's avatar
Stefan Galinski committed

	 * @return File|null
	 */
	protected function getFileInFolder(string $fileName, Folder $folder): ?File {
		return $folder->getFile($fileName);
	 * Returns currently set allowedFiles
	 *
	 * @return mixed
	 */
	protected function getAllowedFileExtensions() {
		return $this->settings['allowedFileExtensions'] ?? $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
	}

	/**
	 * Delete uploaded files in tmp folder
	 *
	 * @param string $folderName
	 */
	protected function deleteTmpFolder(string $folderName): void {
		$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
		$storage = $resourceFactory->getStorageObject(1);
		try {
			$tempFolder = $storage->getFolder('/JobApplication/' . $folderName);
Stefan Galinski's avatar
Stefan Galinski committed
		} catch (\Exception $exception) {
			// the folder is already deleted for some reason
		}
	}

	/**
	 * If for any reason something goes wrong, delete the tmp upload folder
	 *
	public function errorAction(): ResponseInterface {
		if ($this->request->hasArgument('folderName')) {
			$folderName = $this->request->getArgument('folderName');
			$this->deleteTmpFolder($folderName);
		}

		return parent::errorAction();