<?php namespace SGalinski\SgJobs\Controller; /*************************************************************** * 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! ***************************************************************/ use SGalinski\SgJobs\Domain\Model\JobApplication; use SGalinski\SgMail\Service\MailTemplateService; use TYPO3\CMS\Core\Resource\DuplicationBehavior; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Object\ObjectManager; /** * The joblist plugin controller */ class JoblistController extends ActionController { /** * @var \SGalinski\SgJobs\Domain\Repository\CompanyRepository * @inject */ private $companyRepository; /** * @var \SGalinski\SgJobs\Domain\Repository\JobRepository * @inject */ private $jobRepository; /** * Show all job offers and options to manage them * * @return void * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException * @throws \InvalidArgumentException */ public function indexAction() { $storagePid = (int) $this->configurationManager->getConfiguration( ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK )['persistence']['storagePid']; $this->assignFilterValues($storagePid); $this->view->assign('recordPageId', $storagePid); // pagination logic $jobLimit = (int) $this->settings['jobLimit']; $offset = 0; $currentPageBrowserPage = (int) GeneralUtility::_GP('tx_sgjobs_pagebrowser')['currentPage']; if ($currentPageBrowserPage && $jobLimit) { $offset = $currentPageBrowserPage * $jobLimit; } // get all jobs for the current page $jobs = $this->jobRepository->findJobs($storagePid, [], FALSE, $jobLimit, $offset); // get all jobs for the current page $jobsCount = \count($this->jobRepository->findJobs($storagePid, [], FALSE, 0, $offset)); $numberOfPages = ($jobLimit <= 0 ? 0 : ceil($jobsCount / $jobLimit)); $this->view->assign('jobs', $jobs); $this->view->assign('numberOfPages', $numberOfPages); } /** * Renders the application form with an optional job * * @param JobApplication $applyData */ public function applyFormAction(JobApplication $applyData = NULL) { if ($this->request->getOriginalRequest()) { $uploadedFiles = $this->request->getOriginalRequest()->getArguments()['uploadedFiles']; $this->view->assign('uploadedFiles', $uploadedFiles); } $jobId = $this->request->getArguments()['jobData']['uid']; if (!empty($jobId)) { $jobData = $this->jobRepository->findByUid($jobId); $this->view->assign('job', $jobData); } $allowedMimeTypes = $this->settings['allowedMimeTypes']; $this->view->assign('allowedMimeTypes', $allowedMimeTypes); $allowedFileExtensions = $this->settings['allowedFileExtensions']; $this->view->assign('allowedFileExtensions', $allowedFileExtensions); $this->view->assign('applyData', $applyData); } /** * Saves the application send by the applyFormAction * * @param JobApplication $applyData */ public function applyAction(JobApplication $applyData) { try { $this->submitApplicationFiles($applyData); /** @noinspection PhpMethodParametersCountMismatchInspection */ $mailService = $this->objectManager->get( MailTemplateService::class, 'application_mail', 'sg_jobs', $this->getApplicationMailMarkers($applyData) ); $mailService->setIgnoreMailQueue(TRUE); $mailService->setToAddresses($this->settings['applicationEmail']); $mailService->sendEmail(); // redirect to the given page id from the flexform $redirectPageId = (int) $this->configurationManager->getConfiguration( ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK )['settings']['redirectPage']; $uriBuilder = $this->uriBuilder; $uri = $uriBuilder ->reset() ->setTargetPageUid($redirectPageId) ->build(); $this->redirectToUri($uri); } catch (\Exception $exception) { // possible errors, because of wrong mails (maybe log that somewhere? Does this makes sense?) // @TODO handle the exception, possibley redirect to form with error message } } /** * Pre-apply action setup, configures model-property mapping and handles file upload * * @return void * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException * @throws \Exception */ protected function initializeApplyAction() { try { $this->handleFileUpload('coverLetter'); $this->handleFileUpload('cv'); $this->handleFileUpload('certificates'); } catch (\Exception $e) { // possible errors, because of wrong mails // @TODO output them in some way? } $propertyMappingConfiguration = $this->arguments->getArgument('applyData')->getPropertyMappingConfiguration(); $propertyMappingConfiguration->forProperty('coverLetter')->allowAllProperties(); $propertyMappingConfiguration->forProperty('cv')->allowAllProperties(); $propertyMappingConfiguration->forProperty('certificates')->allowAllProperties(); $uploadedFiles = $this->getExistingApplicationFiles($GLOBALS['TSFE']->fe_user->id); $this->request->setArgument('uploadedFiles', $uploadedFiles); } /** * Assign filter values * * @param int $rootPageId */ private function assignFilterValues($rootPageId) { $countries = $this->companyRepository->getAllCountries($rootPageId); $this->view->assign('countries', $countries); $cities = $this->companyRepository->getAllCities($rootPageId); $this->view->assign('cities', $cities); $companies = $this->companyRepository->getAllCompanyNames($rootPageId); $this->view->assign('companies', $companies); $areas = $this->jobRepository->getAllAreas($rootPageId); $this->view->assign('areas', $areas); $functions = $this->jobRepository->getAllFunctions($rootPageId); $this->view->assign('functions', $functions); } /** * Returns the application mail markers * * @param JobApplication $applyData * @return array */ private function getApplicationMailMarkers($applyData): array { 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() ]; } /** * Returns all the files in a folder * * @param string $folder * @return \TYPO3\CMS\Core\Resource\File[] * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException * @throws \Exception * @throws \InvalidArgumentException */ private function getExistingFiles($folder): array { $objectManager = GeneralUtility::makeInstance(ObjectManager::class); $resourceFactory = $objectManager->get(ResourceFactory::class); $storage = $resourceFactory->getStorageObject(1); $folderObject = $storage->getFolder('/Extension/' . $folder); return $storage->getFilesInFolder($folderObject); } /** * Returns all the application files based on a folder * * @param string $folder * @return array * @throws \Exception */ private function getExistingApplicationFiles($folder): array { $files['coverLetter'] = $this->getExistingFiles('temp/' . $folder . '/coverLetter'); $files['cv'] = $this->getExistingFiles('temp/' . $folder . '/cv'); $files['certificates'] = $this->getExistingFiles('temp/' . $folder . '/certificates'); return $files; } /** * Moves the application files from temporary to permanent storage * * @param string $folder * @param JobApplication $applicationData * @return void * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException * @throws \TYPO3\CMS\Core\Exception * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException * @throws \TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException * @throws \RuntimeException * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException * @throws \Exception * @throws \InvalidArgumentException */ private function submitApplicationFiles(JobApplication $applicationData) { $objectManager = GeneralUtility::makeInstance(ObjectManager::class); $resourceFactory = $objectManager->get(ResourceFactory::class); $storage = $resourceFactory->getStorageObject(1); $newName = date('Ymd-His') . '_' . $applicationData->getFirstName(); $sourceFolder = $storage->getFolder('/Extension/temp/' . uniqid('sgjobs-', TRUE)); $subFolders = $storage->getFoldersInFolder($sourceFolder); $fileNames = []; foreach ($subFolders as $subFolder) { $files = $subFolder->getFiles(); foreach ($files as $file) { $currentFile = $file->rename($newName . '_' . $subFolder->getName() . '.pdf'); $fileNames[$subFolder->getName()][] = $currentFile->getName(); $file->moveTo($sourceFolder); } $subFolder->delete(); } $sourceFolder->rename($newName); $sourceFolder = $storage->getFolder('/Extension/temp/' . $newName); if (!$storage->hasFolder('/Extension/JobApplication/')) { $storage->createFolder('/Extension/JobApplication/'); } $targetFolder = $storage->getFolder('/Extension/JobApplication/'); $applicationFile = $storage->createFile($newName . '.csv', $sourceFolder); $applicationFilePath = str_replace('/', '', $storage->getConfiguration()['basePath']) . $applicationFile->getIdentifier(); $this->writeApplicationFile($applicationData, $applicationFilePath, $fileNames); $storage->moveFolder($sourceFolder, $targetFolder); } /** * Writes the application files * * @param JobApplication $data * @param string $filePath * @param array $fileNames * @throws \RuntimeException */ private function writeApplicationFile(JobApplication $data, $filePath, $fileNames) { $certificateNames = ''; $certificatesArr = []; /** @var array[][] $fileNames */ if (isset($fileNames['certificates'])) { foreach ($fileNames['certificates'] as $certificateName) { $certificatesArr[] = $certificateName; } $certificateNames = implode($certificatesArr, ', '); } $dataToInsertArr = [ $data->getJobId(), $data->getFirstName(), $data->getLastName(), $data->getGender(), $data->getCountry(), $data->getBirthDate(), $data->getEducation(), $data->getStreet(), $data->getZip(), $data->getCity(), $data->getNationality(), $data->getPhone(), $data->getEmail(), $fileNames['coverLetter'][0], $fileNames['cv'][0], $certificateNames ]; try { $file = fopen($filePath, 'wb+'); fputcsv($file, $dataToInsertArr); fclose($file); } catch (\RuntimeException $exception) { throw new \RuntimeException($exception->getMessage()); } } /** * Registers an uploaded file for TYPO3 native upload handling. * * @param array &$data * @param string $namespace * @param string $fieldName * @param string $targetDirectory * @return void */ private function registerUploadField(array &$data, $namespace, $fieldName, $targetDirectory = '1:/_temp_/') { if (!\is_array($_FILES[$namespace])) { return; } if (!isset($data['upload'])) { $data['upload'] = []; } $counter = \count($data['upload']) + 1; $keys = array_keys($_FILES[$namespace]); foreach ($keys as $key) { $_FILES['upload_' . $counter][$key] = $_FILES[$namespace][$key]['applyData'][$fieldName]; } $data['upload'][$counter] = [ 'data' => $counter, 'target' => $targetDirectory, ]; } /** * @param string $fieldName * @return array * @throws \UnexpectedValueException * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException * @throws \Exception * @throws \InvalidArgumentException * @throws \TYPO3\CMS\Core\Resource\Exception */ private function handleFileUpload($fieldName = ''): array { $data = []; $namespace = key($_FILES); $applicationId = $GLOBALS['TSFE']->fe_user->id; $objectManager = GeneralUtility::makeInstance(ObjectManager::class); $resourceFactory = $objectManager->get(ResourceFactory::class); $storage = $resourceFactory->getStorageObject(1); if (!$storage->hasFolder('/Extension/temp/' . $applicationId . '/' . $fieldName)) { $storage->createFolder('/Extension/temp/' . $applicationId . '/' . $fieldName); } $targetFalDirectory = '1:/Extension/temp/' . $applicationId . '/' . $fieldName; // Register every upload field from the form: $this->registerUploadField($data, $namespace, $fieldName, $targetFalDirectory); // Initializing: /** @var \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility $fileProcessor */ $fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class); $fileProcessor->setActionPermissions(['addFile' => TRUE]); $fileProcessor->setFileExtensionPermissions($this->settings['allowedFileExtensions'], ''); // Actual upload $fileProcessor->start($data); // This was previously passed as empty string, why and from where? It breaks the upload if it's not set. $fileProcessor->setExistingFilesConflictMode(DuplicationBehavior::REPLACE); $result = $fileProcessor->processData(); $uploadedFiles = []; foreach ((array) $result['upload'] as $files) { /** @var array $files */ foreach ($files as $file) { $uploadedFiles[] = $file; } } return $uploadedFiles; } }