diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml new file mode 100644 index 0000000..1500d69 --- /dev/null +++ b/.github/workflows/static-analysis.yaml @@ -0,0 +1,33 @@ +name: Static analysis + +on: [ push, pull_request ] + +jobs: + phpstan: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - php-version: 7.4 + - php-version: 8.0 + - php-version: 8.1 + steps: + - uses: actions/checkout@v2 + + - id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}-${{ matrix.php-version }} + restore-keys: | + ${{ runner.os }}-composer- + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: none + + - run: composer install --no-progress + + - run: vendor/bin/phpstan analyse Classes -c phpstan.neon \ No newline at end of file diff --git a/Classes/Command/AnonymizeApplicationsCommand.php b/Classes/Command/AnonymizeApplicationsCommand.php new file mode 100644 index 0000000..64f13e5 --- /dev/null +++ b/Classes/Command/AnonymizeApplicationsCommand.php @@ -0,0 +1,137 @@ +persistenceManager = $persistenceManager; + $this->applicationRepository = $applicationRepository; + $this->applicationFileService = $applicationFileService; + $this->logger = $logger; + + parent::__construct(); + } + + public function configure() + { + $this + ->setDescription("Anonymizes applications that are older than the specified days.") + ->addArgument("days", InputArgument::OPTIONAL, "How old can an application be before is should be deleted?", 90) + ->addOption("withStatus", "s", InputOption::VALUE_NONE, "Should applications only be deleted if they are in an end status?"); + } + + /** + * This is the main method that is called when a task is executed + * Should return TRUE on successful execution, FALSE on error. + * + * @return int Returns TRUE on successful execution, FALSE on error + * @throws InvalidFileNameException + * @throws InsufficientFolderAccessPermissionsException + * @throws InvalidQueryException + */ + public function execute($input, $output): int + { + $anonymizeChars = "***"; + $days = $input->getArgument('days') ?? 90; + $withStatus = $input->getOption('withStatus') ?? false; + + $now = new \DateTime(); + $timestamp = $now->modify("-".$days." days")->getTimestamp(); + + $applications = $this->applicationRepository->findOlderThan($timestamp, $withStatus, true); + + $resultCount = count($applications); + + /* @var Application $application */ + foreach ($applications as $application) + { + // Actual anonymization + deleting application files + + /* @var ApplicationFileService $applicationFileService */ + $fileStorage = $this->applicationFileService->getFileStorage($application); + + $this->applicationFileService->deleteApplicationFolder($this->applicationFileService->getApplicantFolder($application), $fileStorage); + + $application->setFirstName($anonymizeChars); + $application->setLastName($anonymizeChars); + $application->setAddressStreetAndNumber($anonymizeChars); + $application->setAddressAddition($anonymizeChars); + $application->setAddressPostCode(0); + $application->setEmail("anonymized@anonymized.anonymized"); + $application->setPhone($anonymizeChars); + $application->setMessage($anonymizeChars); + $application->setArchived(true); + $application->setSalutation(""); + $application->setSalaryExpectation($anonymizeChars); + $application->setEarliestDateOfJoining(new \DateTime("@0")); + $application->setAnonymized(true); + + $this->applicationRepository->update($application); + } + + if ($resultCount > 0) + { + $this->persistenceManager->persistAll(); + } + + $this->logger->info('[ITX\\Jobapplications\\Task\\AnonymizeApplications]: '.$resultCount.' applications anonymized.'); + $output->writeln("$resultCount applications anonymized."); + + return Command::SUCCESS; + } + } \ No newline at end of file diff --git a/Classes/Command/CleanUpApplicationsCommand.php b/Classes/Command/CleanUpApplicationsCommand.php new file mode 100644 index 0000000..54afb80 --- /dev/null +++ b/Classes/Command/CleanUpApplicationsCommand.php @@ -0,0 +1,114 @@ +persistenceManager = $persistenceManager; + $this->applicationRepository = $applicationRepository; + $this->applicationFileService = $applicationFileService; + $this->logger = $logger; + + parent::__construct(); + } + + public function configure() + { + $this + ->setDescription("Deletes applications that are older than the specified days.") + ->addArgument("days", InputArgument::OPTIONAL, "How old can an application be before is should be deleted?", 90) + ->addOption("withStatus", "s", InputOption::VALUE_NONE, "Should applications only be deleted if they are in an end status?"); + } + + /** + * This is the main method that is called when a task is executed + * Should return TRUE on successful execution, FALSE on error. + * + * @return int Returns TRUE on successful execution, FALSE on error + * @throws InvalidFileNameException + * @throws InsufficientFolderAccessPermissionsException + * @throws InvalidQueryException + * @throws IllegalObjectTypeException + */ + public function execute($input, $output): int + { + $days = $input->getArgument('days') ?? 90; + $withStatus = $input->getOption('withStatus') ?? false; + + $now = new \DateTime(); + $timestamp = $now->modify("-".$days." days")->getTimestamp(); + + $applications = $this->applicationRepository->findOlderThan($timestamp, $withStatus); + + $resultCount = count($applications); + + foreach ($applications as $application) + { + $fileStorage = $this->applicationFileService->getFileStorage($application); + $this->applicationRepository->remove($application); + + $this->applicationFileService->deleteApplicationFolder($this->applicationFileService->getApplicantFolder($application), $fileStorage); + } + + if ($resultCount > 0) + { + $this->persistenceManager->persistAll(); + } + + $this->logger->info('[ITX\\Jobapplications\\Task\\CleanUpApplications]: '.$resultCount.' applications deleted.'); + $output->writeln("$resultCount applications deleted."); + + return Command::SUCCESS; + } + } \ No newline at end of file diff --git a/Classes/Controller/AjaxController.php b/Classes/Controller/AjaxController.php index 5064868..b764d40 100644 --- a/Classes/Controller/AjaxController.php +++ b/Classes/Controller/AjaxController.php @@ -24,6 +24,8 @@ namespace ITX\Jobapplications\Controller; + use Psr\Http\Message\ServerRequestInterface; + use Psr\Http\Message\ResponseInterface; use ITX\Jobapplications\Utility\UploadFileUtility; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Core\Environment; @@ -50,13 +52,12 @@ public function __construct() /** * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response * * @return \Psr\Http\Message\ResponseInterface */ public function uploadAction( - \Psr\Http\Message\ServerRequestInterface $request - ): \Psr\Http\Message\ResponseInterface + ServerRequestInterface $request + ): ResponseInterface { $response = new HtmlResponse(''); @@ -66,7 +67,7 @@ public function uploadAction( $responseContent = ''; /** @var ExtensionConfiguration $extconf */ - $extconf = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class); + $extconf = GeneralUtility::makeInstance(ExtensionConfiguration::class); $extConfLimit = $extconf->get('jobapplications', 'customFileSizeLimit'); $allowedFileTypesString = $extconf->get('jobapplications', 'allowedFileTypes'); $allowedFileTypes = $allowedFileTypesString !== '' ? explode(',', $allowedFileTypesString) : []; @@ -143,8 +144,8 @@ public function uploadAction( } public function revertAction( - \Psr\Http\Message\ServerRequestInterface $request - ): \Psr\Http\Message\ResponseInterface + ServerRequestInterface $request + ): ResponseInterface { $response = new HtmlResponse(''); $body = $request->getBody(); diff --git a/Classes/Controller/ApplicationController.php b/Classes/Controller/ApplicationController.php index bd557e3..7d029ef 100644 --- a/Classes/Controller/ApplicationController.php +++ b/Classes/Controller/ApplicationController.php @@ -26,30 +26,51 @@ ***************************************************************/ use ITX\Jobapplications\Domain\Model\Application; + use ITX\Jobapplications\Domain\Model\Contact; use ITX\Jobapplications\Domain\Model\Posting; use ITX\Jobapplications\Domain\Model\Status; + use ITX\Jobapplications\Domain\Repository\ApplicationRepository; + use ITX\Jobapplications\Domain\Repository\PostingRepository; + use ITX\Jobapplications\Domain\Repository\StatusRepository; + use ITX\Jobapplications\Event\BeforeApplicationPersisted; use ITX\Jobapplications\PageTitle\JobsPageTitleProvider; + use ITX\Jobapplications\Service\ApplicationFileService; use ITX\Jobapplications\Utility\Mail\MailInterface; use ITX\Jobapplications\Utility\Typo3VersionUtility; use ITX\Jobapplications\Utility\UploadFileUtility; + use Psr\Http\Message\ResponseInterface; use Psr\Log\LogLevel; + use Symfony\Component\Mime\Address; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Database\ConnectionPool; + use TYPO3\CMS\Core\Log\LogManager; + use TYPO3\CMS\Core\Mail\FluidEmail; + use TYPO3\CMS\Core\Mail\Mailer; use TYPO3\CMS\Core\Messaging\FlashMessage; + use TYPO3\CMS\Core\Resource\Driver\LocalDriver; + 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\InsufficientFolderWritePermissionsException; + use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException; + use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ResourceStorageInterface; use TYPO3\CMS\Core\Resource\StorageRepository; - use TYPO3\CMS\Core\Utility\DebugUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; + use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; + use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException; + use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager; use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; /** * ApplicationController */ - class ApplicationController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController + class ApplicationController extends ActionController { public const UPLOAD_MODE_FILES = 'files'; public const UPLOAD_MODE_LEGACY = 'legacy'; @@ -59,7 +80,6 @@ class ApplicationController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionCont * applicationRepository * * @var \ITX\Jobapplications\Domain\Repository\ApplicationRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $applicationRepository = null; @@ -70,21 +90,13 @@ class ApplicationController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionCont protected $allowedFileTypesString; /** * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $persistenceManager; /** * @var \ITX\Jobapplications\Service\ApplicationFileService - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $applicationFileService; - /** - * signalSlotDispatcher - * - * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher - * @TYPO3\CMS\Extbase\Annotation\Inject - */ - protected $signalSlotDispatcher; + /** * @var \TYPO3\CMS\Core\Log\Logger */ @@ -93,20 +105,24 @@ class ApplicationController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionCont protected $version; /** * @var \ITX\Jobapplications\Domain\Repository\PostingRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ private $postingRepository; /** * @var \ITX\Jobapplications\Domain\Repository\StatusRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ private $statusRepository; + protected StorageRepository $storageRepository; + + public function __construct(StorageRepository $storageRepository) + { + $this->storageRepository = $storageRepository; + } + /** * initialize create action * adjusts date time format to y-m-d * - * @param void * * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException */ @@ -116,11 +132,11 @@ public function initializeCreateAction(): void ->getPropertyMappingConfiguration()->forProperty('earliestDateOfJoining') ->setTypeConverterOption( DateTimeConverter::class, - \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, + DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d' ); - $this->logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__); + $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); } /** @@ -147,7 +163,7 @@ public function initializeView(ViewInterface $view) public function initializeAction(): void { /** @var ExtensionConfiguration $extconf */ - $extconf = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class); + $extconf = GeneralUtility::makeInstance(ExtensionConfiguration::class); $extConfLimit = $extconf->get('jobapplications', 'customFileSizeLimit'); $this->allowedFileTypesString = $extconf->get('jobapplications', 'allowedFileTypes'); @@ -169,7 +185,7 @@ public function initializeAction(): void * * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException */ - public function newAction(Posting $posting = null): void + public function newAction(Posting $posting = null): ResponseInterface { // Getting posting when Detailview and applicationform are on the same page. @@ -214,6 +230,8 @@ public function newAction(Posting $posting = null): void { $this->view->assign("fileError", 0); } + + return $this->htmlResponse(); } /** @@ -223,7 +241,7 @@ public function newAction(Posting $posting = null): void * @param array $problems * @param int $postingUid */ - public function successAction($firstName, $lastName, $salutation, $problems, $postingUid = -1) + public function successAction($firstName, $lastName, $salutation, $problems, $postingUid = -1): ResponseInterface { $salutationValue = $salutation; @@ -236,6 +254,9 @@ public function successAction($firstName, $lastName, $salutation, $problems, $po $salutation = LocalizationUtility::translate('fe.application.selector.'.$salutation, 'jobapplications'); } + /** @var Posting|null $posting */ + $posting = null; + if ($postingUid !== -1) { $posting = $this->postingRepository->findByUid($postingUid); @@ -247,6 +268,8 @@ public function successAction($firstName, $lastName, $salutation, $problems, $po $this->view->assign('salutation', $salutation); $this->view->assign('problems', $problems); $posting ? $this->view->assign('salutationValue', $salutationValue) : false; + + return $this->htmlResponse(); } private function hasArrayWithIndicesNonEmpty(array $array, array $indices): bool @@ -284,18 +307,15 @@ private function isStringArray(?array $array): bool * @param Application $newApplication * @param Posting|null $posting * - * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException - * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException - * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException - * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException - * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException - * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException - * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException - * @throws \TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException + * @throws ExistingTargetFileNameException + * @throws ExistingTargetFolderException + * @throws InsufficientFolderAccessPermissionsException + * @throws InsufficientFolderWritePermissionsException + * @throws InvalidFileNameException + * @throws StopActionException + * @throws IllegalObjectTypeException */ - public function createAction(\ITX\Jobapplications\Domain\Model\Application $newApplication, \ITX\Jobapplications\Domain\Model\Posting $posting = null): void + public function createAction(Application $newApplication, Posting $posting = null): void { $problemWithApplicantMail = false; $problemWithNotificationMail = false; @@ -336,7 +356,7 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA } // Check which kind of uploads were sent - if ($this->isStringArray($arguments['files'])) + if ($this->isStringArray($arguments['files'] ?? [])) { $uploadMode = self::UPLOAD_MODE_FILES; } @@ -353,9 +373,12 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA $this->redirect("new", "Application", null, ["posting" => $posting]); } - $newApplication->setPosting($posting); + if ($posting instanceof Posting) + { + $newApplication->setPosting($posting); + } - /* @var \ITX\Jobapplications\Domain\Model\Status $firstStatus */ + /* @var Status $firstStatus */ $firstStatus = $this->statusRepository->findNewStatus(); if ($firstStatus instanceof Status) @@ -363,14 +386,10 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA $newApplication->setStatus($firstStatus); } - // SignalSlotDispatcher BeforePostingAssign - $signalArguments = ["application" => $newApplication]; - $signalArguments = $this->signalSlotDispatcher->dispatch(__CLASS__, "BeforeApplicationAdd", $signalArguments); - - if ($signalArguments["application"] instanceof Application) - { - $newApplication = $signalArguments['application']; - } + // Event BeforeApplicationPersisted + /** @var BeforeApplicationPersisted $event */ + $event = $this->eventDispatcher->dispatch(new BeforeApplicationPersisted($newApplication)); + $newApplication = $event->getApplication(); $this->applicationRepository->add($newApplication); $this->persistenceManager->persistAll(); @@ -379,7 +398,7 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA if (!($posting instanceof Posting)) { /** @var Posting $posting */ - $posting = GeneralUtility::makeInstance(\ITX\Jobapplications\Domain\Model\Posting::class); + $posting = GeneralUtility::makeInstance(Posting::class); $posting->setTitle(LocalizationUtility::translate("fe.application.unsolicited.title", "jobapplications")); $newApplication->setPosting($posting); @@ -407,7 +426,7 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA $currentPosting = $newApplication->getPosting(); // Default contact is not available - $contact = GeneralUtility::makeInstance(\ITX\Jobapplications\Domain\Model\Contact::class); + $contact = GeneralUtility::makeInstance(Contact::class); $contact->setEmail($this->settings["defaultContactMailAddress"]); $contact->setFirstName($this->settings["defaultContactFirstName"]); @@ -415,64 +434,23 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA $contact = ($currentPosting->getContact() ?: $contact); - // Get and translate labels - $salutation = LocalizationUtility::translate("fe.application.selector.".$newApplication->getSalutation(), "jobapplications"); - $salary = $newApplication->getSalaryExpectation() ? LocalizationUtility::translate("tx_jobapplications_domain_model_application.salary_expectation", "jobapplications").": ".$newApplication->getSalaryExpectation()."
" : ""; - $dateOfJoining = $newApplication->getEarliestDateOfJoining() ? - LocalizationUtility::translate("tx_jobapplications_domain_model_application.earliest_date_of_joining", "jobapplications") - .": ".$newApplication->getEarliestDateOfJoining()->format(LocalizationUtility::translate("date_format", "jobapplications"))."
" : ""; - $nameLabel = LocalizationUtility::translate("tx_jobapplications_domain_model_location.name", "jobapplications").": "; - $emailLabel = LocalizationUtility::translate("tx_jobapplications_domain_model_application.email", "jobapplications").": "; - $phoneLabel = LocalizationUtility::translate("tx_jobapplications_domain_model_application.phone", "jobapplications").": "; - $addressLabel = LocalizationUtility::translate("tx_jobapplications_domain_model_location.address", "jobapplications").": "; - $additionalAddress = $newApplication->getAddressAddition() ? $newApplication->getAddressAddition().'
' : ""; - $messageLabel = LocalizationUtility::translate("tx_jobapplications_domain_model_application.message", "jobapplications").": "; - $message = $newApplication->getMessage() ? '

'.$messageLabel.'
'.$newApplication->getMessage() : ""; - - $phoneLine = $newApplication->getPhone() !== '' ? $phoneLabel.$newApplication->getPhone().'
' : ''; - - $addressChunk = ""; - if ($newApplication->getAddressStreetAndNumber() - || $newApplication->getAddressPostCode() - || $newApplication->getAddressCity() - || $newApplication->getAddressCountry() - ) - { - $addressChunk = $addressLabel.'
'.$newApplication->getAddressStreetAndNumber().'
' - .$additionalAddress. - $newApplication->getAddressPostCode().' '.$newApplication->getAddressCity() - .'
'.$newApplication->getAddressCountry(); - } + /** @var Mailer $mailer */ + $mailer = GeneralUtility::makeInstance(Mailer::class); // Send mail to Contact E-Mail or/and internal E-Mail if ($this->settings["sendEmailToContact"] === "1" || $this->settings['sendEmailToInternal'] !== "") { - /** @var \ITX\Jobapplications\Utility\Mail\MailInterface $mail */ - if ($this->version >= 10) - { - $mail = GeneralUtility::makeInstance(\ITX\Jobapplications\Utility\Mail\MailFluid::class); - $mail->setTemplate('JobsNotificationMail'); - } - else - { - $mail = GeneralUtility::makeInstance(\ITX\Jobapplications\Utility\Mail\MailMessage::class); - } + $mail = GeneralUtility::makeInstance(FluidEmail::class); + $mail->setTemplate('JobsNotificationMail'); - $mail->setContentType($this->settings['emailContentType']); + $mail->format($this->settings['emailContentType']); // Prepare and send the message $mail - ->setSubject(LocalizationUtility::translate("fe.email.toContactSubject", 'jobapplications', [0 => $currentPosting->getTitle()])) - ->setFrom([$this->settings["emailSender"] => $this->settings["emailSenderName"]]) - ->setReply([$newApplication->getEmail() => $newApplication->getFirstName()." ".$newApplication->getLastName()]) - ->setContent('

'. - $nameLabel.$salutation.' '.$newApplication->getFirstName().' '.$newApplication->getLastName().'
'. - $emailLabel.$newApplication->getEmail().'
'. - $phoneLine. - $salary. - $dateOfJoining.'
'. - $addressChunk - .$message.'

', ['application' => $newApplication, 'settings' => $this->settings, 'currentPosting' => $currentPosting]); + ->subject(LocalizationUtility::translate("fe.email.toContactSubject", 'jobapplications', [0 => $currentPosting->getTitle()])) + ->from(new Address($this->settings["emailSender"], $this->settings["emailSenderName"])) + ->replyTo(new Address($newApplication->getEmail(), $newApplication->getFirstName()." ".$newApplication->getLastName())) + ->assignMultiple(['application' => $newApplication, 'settings' => $this->settings, 'currentPosting' => $currentPosting]); foreach ($legacyUploadfiles as $fileArray) { @@ -480,7 +458,7 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA { if ($file instanceof FileInterface) { - $mail->addAttachment($file->getForLocalProcessing(false)); + $mail->attachFromPath($file->getForLocalProcessing(false)); } } } @@ -489,31 +467,28 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA { if ($file instanceof FileInterface) { - $mail->addAttachment($file->getForLocalProcessing(false)); + $mail->attachFromPath($file->getForLocalProcessing(false)); } } //Figure out who the email will be sent to and how - if ($this->settings['sendEmailToInternal'] != "" && $this->settings['sendEmailToContact'] == "1") + if ($this->settings['sendEmailToInternal'] !== "" && $this->settings['sendEmailToContact'] === '1') { - $mail->setTo([$contact->getEmail() => $contact->getFirstName().' '.$contact->getLastName()]); - $mail->setBlindcopies([$this->settings['sendEmailToInternal']]); + $mail->to(new Address($contact->getEmail(), $contact->getFirstName().' '.$contact->getLastName())); + $mail->bcc(new Address($this->settings['sendEmailToInternal'])); } - else if ($this->settings['sendEmailToContact'] != "1" && $this->settings['sendEmailToInternal'] != "") + else if ($this->settings['sendEmailToContact'] !== '1' && $this->settings['sendEmailToInternal'] !== "") { - $mail->setTo([$this->settings['sendEmailToInternal'] => 'Internal']); + $mail->to(new Address($this->settings['sendEmailToInternal'], 'Internal')); } - else if ($this->settings['sendEmailToContact'] == "1" && $this->settings['sendEmailToInternal'] != "1") + else if ($this->settings['sendEmailToContact'] === '1' && $this->settings['sendEmailToInternal'] !== '1') { - $mail->setTo([$contact->getEmail() => $contact->getFirstName()." ".$contact->getLastName()]); + $mail->to(new Address($contact->getEmail(), $contact->getFirstName()." ".$contact->getLastName())); } try { - if (!$mail->send()) - { - throw new \RuntimeException('Failed to send mail!'); - } + $mailer->send($mail); } catch (\Exception $e) { @@ -525,54 +500,24 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA // Now send a mail to the applicant if ($this->settings["sendEmailToApplicant"] === "1") { - /** @var MailInterface $mail */ - if ($this->version >= 10) - { - $mail = GeneralUtility::makeInstance(\ITX\Jobapplications\Utility\Mail\MailFluid::class); - $mail->setTemplate('JobsApplicantMail'); - } - else - { - $mail = GeneralUtility::makeInstance(\ITX\Jobapplications\Utility\Mail\MailMessage::class); - } + $mail = GeneralUtility::makeInstance(FluidEmail::class); + $mail->setTemplate('JobsApplicantMail'); - $mail->setContentType($this->settings['emailContentType']); + $mail->format($this->settings['emailContentType']); //Template Messages $subject = $this->settings['sendEmailToApplicantSubject']; $subject = str_replace("%postingTitle%", $currentPosting->getTitle(), $subject); - $body = $this->settings["sendEmailToApplicantText"]; - switch ((int)$newApplication->getSalutation()) - { - case 3: - case 0: - $salutation = ""; - break; - case 1: - $salutation = LocalizationUtility::translate("fe.application.selector.mr", "jobapplications"); - break; - case 2: - $salutation = LocalizationUtility::translate("fe.application.selector.mrs", "jobapplications"); - break; - } - $body = str_replace("%applicantSalutation%", $salutation, $body); - $body = str_replace("%applicantFirstName%", $newApplication->getFirstName(), $body); - $body = str_replace("%applicantLastName%", $newApplication->getLastName(), $body); - $body = str_replace("%postingTitle%", $currentPosting->getTitle(), $body); - $mail - ->setSubject($subject) - ->setFrom([$this->settings["emailSender"] => $this->settings["emailSenderName"]]) - ->setTo([$newApplication->getEmail() => $newApplication->getFirstName()." ".$newApplication->getLastName()]) - ->setContent($body, ['application' => $newApplication, 'settings' => $this->settings]); + ->subject($subject) + ->from(new Address($this->settings["emailSender"], $this->settings["emailSenderName"])) + ->to(new Address($newApplication->getEmail(), $newApplication->getFirstName()." ".$newApplication->getLastName())) + ->assignMultiple(['application' => $newApplication, 'settings' => $this->settings]); try { - if (!$mail->send()) - { - throw new \RuntimeException('Failed to send mail!'); - } + $mailer->send($mail); } catch (\Exception $e) { @@ -595,7 +540,7 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA $this->uriBuilder->setTargetPageUid((int)$this->settings['successPage']); - if (\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SSL')) + if (GeneralUtility::getIndpEnv('TYPO3_SSL')) { $this->uriBuilder->setAbsoluteUriScheme('https'); } @@ -626,14 +571,13 @@ public function createAction(\ITX\Jobapplications\Domain\Model\Application $newA * @param string $fileNamePrefix * * @return array - * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException - * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException - * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException - * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException - * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException - * @throws \TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException + * @throws ExistingTargetFileNameException + * @throws ExistingTargetFolderException + * @throws InsufficientFolderAccessPermissionsException + * @throws InsufficientFolderWritePermissionsException + * @throws InvalidFileNameException */ - private function processFiles(Application $newApplication, array $fileIds, string $fieldName, int $fileStorage, $fileNamePrefix = ''): array + private function processFiles(Application $newApplication, array $fileIds, string $fieldName, int $fileStorage, string $fileNamePrefix = ''): array { $uploadUtility = new UploadFileUtility(); @@ -660,30 +604,28 @@ private function processFiles(Application $newApplication, array $fileIds, strin } /** - * @param string $filePath - * @param string $fileName - * @param \ITX\Jobapplications\Domain\Model\Application $domainObject - * @param int $fileStorage - * @param string $prefix + * @param string $filePath + * @param string $fileName + * @param Application $domainObject + * @param int $fileStorage + * @param string $prefix * - * @return FileInterface - * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException - * @throws \TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException - * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException - * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException - * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException + * @return File|FileInterface + * @throws ExistingTargetFileNameException + * @throws ExistingTargetFolderException + * @throws InsufficientFolderAccessPermissionsException + * @throws InsufficientFolderWritePermissionsException + * @throws InvalidFileNameException */ - private function handleFileUpload(string $filePath, string $fileName, - \ITX\Jobapplications\Domain\Model\Application $domainObject, int $fileStorage, string $prefix = ''): FileInterface + private function handleFileUpload(string $filePath, string $fileName, + Application $domainObject, int $fileStorage, string $prefix = ''): mixed { $folder = $this->applicationFileService->getApplicantFolder($domainObject); - /* @var \TYPO3\CMS\Core\Resource\StorageRepository $storageRepository */ - $storageRepository = $this->objectManager->get(StorageRepository::class); - - $storage = $storageRepository->findByUid($fileStorage); - if (!$storage instanceof ResourceStorageInterface) { + $storage = $this->storageRepository->findByUid($fileStorage); + if (!$storage instanceof ResourceStorageInterface) + { throw new \RuntimeException(sprintf("Resource storage with uid %d could not be found.", $fileStorage)); } @@ -698,7 +640,7 @@ private function handleFileUpload(string } //file name - $newFileName = (new \TYPO3\CMS\Core\Resource\Driver\LocalDriver)->sanitizeFileName($prefix.$fileName); + $newFileName = (new LocalDriver)->sanitizeFileName($prefix.$fileName); //build sys_file $movedNewFile = $storage->addFile($filePath, $targetFolder, $newFileName); @@ -714,7 +656,7 @@ private function handleFileUpload(string * @param int $iteration The current iteration * @param int $totalFiles The total amount of iterations */ - private function buildRelations(int $objectUid, int $fileUid, int $objectPid, string $field, $iteration = 0, $totalFiles = 1): void + private function buildRelations(int $objectUid, int $fileUid, int $objectPid, string $field, int $iteration = 0, int $totalFiles = 1): void { /** @var ConnectionPool $database */ $database = GeneralUtility::makeInstance(ConnectionPool::class); @@ -744,8 +686,33 @@ private function buildRelations(int $objectUid, int $fileUid, int $objectPid, st ->update( "tx_jobapplications_domain_model_application", ["files" => $totalFiles], [ - 'uid' => $newStorageUid + 'uid' => $objectUid ]); } } + + public function injectApplicationRepository(ApplicationRepository $applicationRepository): void + { + $this->applicationRepository = $applicationRepository; + } + + public function injectPersistenceManager(PersistenceManager $persistenceManager): void + { + $this->persistenceManager = $persistenceManager; + } + + public function injectApplicationFileService(ApplicationFileService $applicationFileService): void + { + $this->applicationFileService = $applicationFileService; + } + + public function injectPostingRepository(PostingRepository $postingRepository): void + { + $this->postingRepository = $postingRepository; + } + + public function injectStatusRepository(StatusRepository $statusRepository): void + { + $this->statusRepository = $statusRepository; + } } \ No newline at end of file diff --git a/Classes/Controller/BackendController.php b/Classes/Controller/BackendController.php index a995290..127ceff 100644 --- a/Classes/Controller/BackendController.php +++ b/Classes/Controller/BackendController.php @@ -25,6 +25,21 @@ namespace ITX\Jobapplications\Controller; + use Psr\Http\Message\ServerRequestInterface; + use TYPO3\CMS\Core\Pagination\SimplePagination; + use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + use ITX\Jobapplications\Domain\Model\Application; + use Psr\Http\Message\ResponseInterface; + use ITX\Jobapplications\Domain\Repository\ApplicationRepository; + use ITX\Jobapplications\Domain\Repository\PostingRepository; + use ITX\Jobapplications\Domain\Repository\ContactRepository; + use ITX\Jobapplications\Domain\Repository\StatusRepository; + use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; + use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator; + use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException; + use TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException; + use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager; + use ITX\Jobapplications\Service\ApplicationFileService; use ITX\Jobapplications\Domain\Model\Contact; use ITX\Jobapplications\Domain\Model\Posting; use ITX\Jobapplications\Domain\Model\Status; @@ -38,13 +53,12 @@ * * @package ITX\Jobapplications\Controller */ - class BackendController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController + class BackendController extends ActionController { /** * applicationRepository * * @var \ITX\Jobapplications\Domain\Repository\ApplicationRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $applicationRepository = null; @@ -52,7 +66,6 @@ class BackendController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControll * postingRepository * * @var \ITX\Jobapplications\Domain\Repository\PostingRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $postingRepository = null; @@ -60,7 +73,6 @@ class BackendController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControll * contactRepository * * @var \ITX\Jobapplications\Domain\Repository\ContactRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $contactRepository = null; @@ -68,7 +80,6 @@ class BackendController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControll * statusRepository * * @var \ITX\Jobapplications\Domain\Repository\StatusRepository - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $statusRepository = null; @@ -76,30 +87,38 @@ class BackendController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControll * persistenceManager * * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $persistenceManager; /** * @var \ITX\Jobapplications\Service\ApplicationFileService - * @TYPO3\CMS\Extbase\Annotation\Inject */ protected $applicationFileService; + protected GoogleIndexingApiConnector $connector; + + public function __construct(GoogleIndexingApiConnector $connector) + { + $this->connector = $connector; + } + /** * action listApplications * * @return void - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException + * @throws NoSuchArgumentException + * @throws UnknownObjectException + * @throws IllegalObjectTypeException */ public function listApplicationsAction() { + $page = $this->request->hasArgument('page') ? (int)$this->request->getArgument('page') : 1; + $itemsPerPage = 12; + $sessionData = $GLOBALS['BE_USER']->getSessionData('tx_jobapplications'); $contact = $this->getActiveBeContact(); - $dailyLogin = $sessionData['dailyLogin']; + $dailyLogin = $sessionData['dailyLogin'] ?? null; if ((empty($dailyLogin) && $contact instanceof Contact) || ($dailyLogin === false && $contact instanceof Contact)) { $this->redirect('dashboard'); @@ -117,10 +136,10 @@ public function listApplicationsAction() } else { - $selectedPosting = $sessionData['selectedPosting']; - $archivedSelected = $sessionData['archivedSelected']; - $selectedContact = $sessionData['selectedContact']; - $selectedStatus = $sessionData['selectedStatus']; + $selectedPosting = $sessionData['selectedPosting'] ?? null; + $archivedSelected = $sessionData['archivedSelected'] ?? null; + $selectedContact = $sessionData['selectedContact'] ?? null; + $selectedStatus = $sessionData['selectedStatus'] ?? null; } // Handling a status change, triggered in listApplications View @@ -149,9 +168,9 @@ public function listApplicationsAction() $applications = $this->applicationRepository->findByFilter($selectedContact, $selectedPosting, $selectedStatus, 0, 'crdate', 'DESC'); // Set posting-selectBox content dynamically based on selected contact - if ((empty($selectedPosting) && !empty($selectedContact))) + if (empty($selectedPosting) && $selectedContact !== null) { - $postingsFilter = $this->postingRepository->findByContact(intval($selectedContact)); + $postingsFilter = $this->postingRepository->findByContact($selectedContact); } else { @@ -169,11 +188,18 @@ public function listApplicationsAction() $sessionData['selectedStatus'] = $selectedStatus; $GLOBALS['BE_USER']->setAndSaveSessionData('tx_jobapplications', $sessionData); + $paginator = new QueryResultPaginator($applications, $page, $itemsPerPage); + + $pagination = new SimplePagination($paginator); + + $this->view->assign('paginator', $paginator); + $this->view->assign('pagination', $pagination); + $this->view->assign('applications', $paginator->getPaginatedItems()); + $this->view->assign('selectedPosting', $selectedPosting); $this->view->assign('archivedSelected', $archivedSelected); $this->view->assign('selectedContact', $selectedContact); $this->view->assign('selectedStatus', $selectedStatus); - $this->view->assign('applications', $applications); $this->view->assign('postings', $postingsFilter); $this->view->assign('contacts', $contactsFilter); $this->view->assign('statuses', $statusesFilter); @@ -196,13 +222,14 @@ private function getActiveBeContact() * * @param \ITX\Jobapplications\Domain\Model\Application $application * - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException + * @throws UnknownObjectException * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws IllegalObjectTypeException + * @throws NoSuchArgumentException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException */ - public function showApplicationAction(\ITX\Jobapplications\Domain\Model\Application $application) + public function showApplicationAction(Application $application) { $statusDatabaseOp = false; @@ -248,18 +275,14 @@ public function showApplicationAction(\ITX\Jobapplications\Domain\Model\Applicat $this->persistenceManager->persistAll(); } - // Fetch baseuri for f:uri to access Public folder - $baseUri = str_replace('typo3/', '', $this->request->getBaseUri()); - $this->view->assign('application', $application); - $this->view->assign('baseUri', $baseUri); } /** * Action for Backend Dashboard * */ - public function dashboardAction() + public function dashboardAction(): ResponseInterface { // Get data for counter of new applications with referenced contact $contact = $this->getActiveBeContact(); @@ -269,7 +292,7 @@ public function dashboardAction() $session = $backendUser->getSessionData('tx_jobapplications') ?? []; - if ((empty($session['dailyLogin']) && $contact instanceof Contact) || ($session['dailyLogin'] === false && $contact instanceof Contact)) + if ((isset($session['dailyLogin']) && $contact instanceof Contact) || (!isset($session['dailyLogin']) && $contact instanceof Contact)) { $session['dailyLogin'] = true; $backendUser->setAndSaveSessionData('tx_jobapplications', $session); @@ -287,16 +310,17 @@ public function dashboardAction() $this->view->assign('admin', $GLOBALS['BE_USER']->isAdmin()); $this->view->assign('newApps', count($newApps)); $this->view->assign('contact', $contact); + return $this->htmlResponse(); } /** * Action for settings page * * @throws InsufficientUserPermissionsException - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws NoSuchArgumentException * @throws \Exception */ - public function settingsAction() + public function settingsAction(): ResponseInterface { if (!$GLOBALS['BE_USER']->isAdmin()) { @@ -316,7 +340,6 @@ public function settingsAction() if ($this->request->hasArgument('batch_index')) { - $connector = new GoogleIndexingApiConnector(true); $postings = $this->postingRepository->findAllIncludingHiddenAndDeleted(); $removeCounter = 0; @@ -328,7 +351,7 @@ public function settingsAction() { if ($posting->isHidden() || $posting->isDeleted()) { - if (!$connector->updateGoogleIndex($posting->getUid(), true, $posting)) + if (!$this->connector->updateGoogleIndex($posting->getUid(), true, $posting)) { $error_bit = true; } @@ -337,17 +360,13 @@ public function settingsAction() $removeCounter++; } } + else if (!$this->connector->updateGoogleIndex($posting->getUid(), false, $posting)) + { + $error_bit = true; + } else { - if (!$connector->updateGoogleIndex($posting->getUid(), false, $posting)) - { - $error_bit = true; - } - else - { - $updateCounter++; - } - + $updateCounter++; } } if ($error_bit) @@ -362,5 +381,36 @@ public function settingsAction() } $this->view->assign('admin', $GLOBALS['BE_USER']->isAdmin()); + return $this->htmlResponse(); + } + + public function injectApplicationRepository(ApplicationRepository $applicationRepository): void + { + $this->applicationRepository = $applicationRepository; + } + + public function injectPostingRepository(PostingRepository $postingRepository): void + { + $this->postingRepository = $postingRepository; + } + + public function injectContactRepository(ContactRepository $contactRepository): void + { + $this->contactRepository = $contactRepository; + } + + public function injectStatusRepository(StatusRepository $statusRepository): void + { + $this->statusRepository = $statusRepository; + } + + public function injectPersistenceManager(PersistenceManager $persistenceManager): void + { + $this->persistenceManager = $persistenceManager; + } + + public function injectApplicationFileService(ApplicationFileService $applicationFileService): void + { + $this->applicationFileService = $applicationFileService; } } \ No newline at end of file diff --git a/Classes/Controller/ContactController.php b/Classes/Controller/ContactController.php index 36f53d2..f0c732d 100644 --- a/Classes/Controller/ContactController.php +++ b/Classes/Controller/ContactController.php @@ -2,6 +2,9 @@ namespace ITX\Jobapplications\Controller; + use ITX\Jobapplications\Domain\Repository\ContactRepository; + use Psr\Http\Message\ResponseInterface; + use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; /*************************************************************** @@ -30,16 +33,9 @@ /** * ContactController */ - class ContactController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController + class ContactController extends ActionController { - - /** - * contactRepository - * - * @var \ITX\Jobapplications\Domain\Repository\ContactRepository - * @TYPO3\CMS\Extbase\Annotation\Inject - */ - protected $contactRepository = null; + protected ContactRepository $contactRepository; /** * Initializes the view before invoking an action method. @@ -63,9 +59,9 @@ public function initializeView(ViewInterface $view) * * @param ITX\Jobapplications\Domain\Model\Contact * - * @return void + * @return ResponseInterface */ - public function listAction() + public function listAction(): ResponseInterface { $contacts = []; $selectedContactsStr = $this->settings["selectedContacts"]; @@ -80,5 +76,12 @@ public function listAction() } $this->view->assign('contacts', $contactObjects); + + return $this->htmlResponse(); + } + + public function injectContactRepository(ContactRepository $contactRepository): void + { + $this->contactRepository = $contactRepository; } } diff --git a/Classes/Controller/PostingController.php b/Classes/Controller/PostingController.php index 23e8a30..d740e74 100644 --- a/Classes/Controller/PostingController.php +++ b/Classes/Controller/PostingController.php @@ -3,15 +3,30 @@ namespace ITX\Jobapplications\Controller; use ITX\Jobapplications\Domain\Model\Constraint; + use ITX\Jobapplications\Domain\Model\Location; + use ITX\Jobapplications\Domain\Model\Posting; + use ITX\Jobapplications\Domain\Repository\LocationRepository; + use ITX\Jobapplications\Domain\Repository\PostingRepository; + use ITX\Jobapplications\Event\DisplayPostingEvent; use ITX\Jobapplications\PageTitle\JobsPageTitleProvider; + use Psr\Http\Message\ResponseInterface; + use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; + use TYPO3\CMS\Core\Context\Context; + use TYPO3\CMS\Core\Error\Http\PageNotFoundException; use TYPO3\CMS\Core\Http\ImmediateResponseException; use TYPO3\CMS\Core\Page\PageRenderer; - use TYPO3\CMS\Core\Utility\DebugUtility; + use TYPO3\CMS\Core\Pagination\SimplePagination; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; + use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; + use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator; + use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException; + use TYPO3\CMS\Extbase\Persistence\QueryInterface; + use TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException; use TYPO3\CMS\Frontend\Controller\ErrorController; /*************************************************************** @@ -40,47 +55,28 @@ /** * PostingController */ - class PostingController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController + class PostingController extends ActionController { + protected PostingRepository $postingRepository; + protected LocationRepository $locationRepository; + protected PageRenderer $pageRenderer; - /** - * postingRepository - * - * @var \ITX\Jobapplications\Domain\Repository\PostingRepository - * @TYPO3\CMS\Extbase\Annotation\Inject - */ - protected $postingRepository = null; - - /** - * locationRepository - * - * @var \ITX\Jobapplications\Domain\Repository\LocationRepository - * @TYPO3\CMS\Extbase\Annotation\Inject - */ - protected $locationRepository = null; - - /** - * signalSlotDispatcher - * - * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher - * @TYPO3\CMS\Extbase\Annotation\Inject - */ - protected $signalSlotDispatcher; + public function __construct(PageRenderer $pageRenderer) + { + $this->pageRenderer = $pageRenderer; + } /** * initialize show action * * @param void */ - public function initializeShowAction() + public function initializeShowAction(): void { // If application form an posting are on the same page, the posting object is part of the application plugin. - if (!$this->request->hasArgument("posting")) + if (!$this->request->hasArgument("posting") && isset($_REQUEST["tx_jobapplications_applicationform"]["posting"])) { - if (isset($_REQUEST["tx_jobapplications_applicationform"]["posting"])) - { - $this->request->setArgument("posting", $_REQUEST["tx_jobapplications_applicationform"]["posting"]); - } + $this->request->setArgument("posting", $_REQUEST["tx_jobapplications_applicationform"]["posting"]); } } @@ -102,13 +98,15 @@ public function initializeView(ViewInterface $view) } /** - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException - * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException - * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException - * @throws \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException + * @throws InvalidQueryException + * @throws UnknownClassException + * @throws NoSuchArgumentException */ - public function listAction(Constraint $constraint = null): void + public function listAction(Constraint $constraint = null): ResponseInterface { + $page = $this->request->hasArgument('page') ? (int)$this->request->getArgument('page') : 1; + $itemsPerPage = $this->settings['itemsOnPage'] ?? 9; + // Plugin selected categories $category_str = $this->settings["categories"]; $categories = !empty($category_str) ? explode(",", $category_str) : []; @@ -118,13 +116,13 @@ public function listAction(Constraint $constraint = null): void switch ($this->settings['list']['ordering']['order']) { case 'descending': - $order = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING; + $order = QueryInterface::ORDER_DESCENDING; break; case 'ascending': - $order = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING; + $order = QueryInterface::ORDER_ASCENDING; break; default: - $order = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING; + $order = QueryInterface::ORDER_DESCENDING; } // Get repository configuration from typoscript @@ -140,13 +138,6 @@ public function listAction(Constraint $constraint = null): void // Make the actual repository call $postings = $this->postingRepository->findByFilter($categories, $repositoryConfiguration, $constraint, $orderBy, $order); - // SignalSlotDispatcher BeforePostingAssign - $changedPostings = $this->signalSlotDispatcher->dispatch(__CLASS__, "BeforePostingAssign", ["postings" => $postings]); - if (count($changedPostings["postings"]) > 0) - { - $postings = $changedPostings['postings']; - } - // Determines whether user tried to filter $isFiltering = false; @@ -155,44 +146,56 @@ public function listAction(Constraint $constraint = null): void $isFiltering = true; } - $this->view->assign('postings', $postings); + $paginator = new QueryResultPaginator($postings, $page, $itemsPerPage); + + $pagination = new SimplePagination($paginator); + + $this->view->assign('postings', $paginator->getPaginatedItems()); + $this->view->assign('paginator', $paginator); + $this->view->assign('pagination', $pagination); $this->view->assign('isFiltering', $isFiltering); $this->view->assign('filterOptions', $filterOptions); $this->view->assign('constraint', $constraint); + + return $this->htmlResponse(); } /** * @param array $categories * * @return array - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + * @throws InvalidQueryException */ private function getCachedFilterOptions(array $categories): array { $contentObj = $this->configurationManager->getContentObject(); - if ($contentObj === null) { + if ($contentObj === null) + { throw new \RuntimeException("Could not retrieve content object. Make sure to call this with a plugin."); } $pageId = $contentObj->data['pid']; - if (!MathUtility::canBeInterpretedAsInteger($pageId)) { + if (!MathUtility::canBeInterpretedAsInteger($pageId)) + { throw new \RuntimeException("Page id $pageId is not valid."); } $cacheKey = "filterOptions-$pageId"; /** @var FrontendInterface $cache */ - $cache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('jobapplications_cache'); + $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('jobapplications_cache'); + $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language'); + $languageId = $languageAspect->getId(); - // If $entry is false, it hasn't been cached. Calculate the value and store it in the cache: - if (($entry = $cache->get($cacheKey)) === false) + // If $entry is false, or language key does not exist it hasn't been cached. Calculate the value and store it in the cache: + if (($entry = $cache->get($cacheKey)) === false || !key_exists($languageId, $entry)) { - $entry = $this->getFilterOptions($categories); + $entry = $this->getFilterOptions($categories, $languageId); $cache->set($cacheKey, $entry, [], null); } - return $entry; + return $entry[$languageId]; } /** @@ -201,32 +204,32 @@ private function getCachedFilterOptions(array $categories): array * Override for customization. * * @param $categories array categories + * @param $languageId int * * @return array - * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + * @throws InvalidQueryException */ - public function getFilterOptions(array $categories): array + public function getFilterOptions(array $categories, $languageId): array { return [ - 'division' => $this->postingRepository->findAllDivisions($categories), - 'careerLevel' => $this->postingRepository->findAllCareerLevels($categories), - 'employmentType' => $this->postingRepository->findAllEmploymentTypes($categories), - 'location' => $this->locationRepository->findAll($categories)->toArray(), + $languageId => [ + 'division' => $this->postingRepository->findAllDivisions($categories, $languageId), + 'careerLevel' => $this->postingRepository->findAllCareerLevels($categories, $languageId), + 'employmentType' => $this->postingRepository->findAllEmploymentTypes($categories, $languageId), + 'locations' => $this->locationRepository->findAll($categories)->toArray() + ] ]; } /** - * @param \ITX\Jobapplications\Domain\Model\Posting|null $posting + * @param Posting|null $posting * + * @return ResponseInterface * @throws ImmediateResponseException + * @throws PageNotFoundException * @throws \JsonException - * @throws \TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException - * @throws \TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException - * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException - * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException - * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException */ - public function showAction(\ITX\Jobapplications\Domain\Model\Posting $posting = null): void + public function showAction(?Posting $posting = null): ResponseInterface { if ($posting === null) { @@ -238,126 +241,152 @@ public function showAction(\ITX\Jobapplications\Domain\Model\Posting $posting = $titleProvider = GeneralUtility::makeInstance(JobsPageTitleProvider::class); + // Pagetitle Templating + $title = $this->settings["pageTitle"]; + if ($title !== "") + { + $title = str_replace("%postingTitle%", $posting->getTitle(), $title); + } + else + { + $title = $posting->getTitle(); + } + + $titleProvider->setTitle($title); + + $this->addGoogleJobsDataToPage($posting); + + // Event DisplayPostingEvent + /** @var DisplayPostingEvent $event */ + $event = $this->eventDispatcher->dispatch(new DisplayPostingEvent($posting)); + $posting = $event->getPosting(); + + $this->view->assign('posting', $posting); + + return $this->htmlResponse(); + } + + /** + * This function generates the Google Jobs structured on page data. + * This can be overriden if any field customizations are done. + */ + protected function addGoogleJobsDataToPage(Posting $posting): void + { /** @var ExtensionConfiguration $extconf */ - $extconf = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class); + $extconf = GeneralUtility::makeInstance(ExtensionConfiguration::class); - //Google Jobs + $companyName = $extconf->get('jobapplications', 'companyName'); + + if (empty($companyName) || $this->settings['enableGoogleJobs'] !== "1") + { + return; + } $hiringOrganization = [ "@type" => "Organization", - "name" => $extconf->get('jobapplications', 'companyName') + "name" => $companyName ]; - if (!empty($hiringOrganization['name']) && $this->settings['enableGoogleJobs'] == "1") + $logo = $extconf->get('jobapplications', 'logo'); + if (!empty($logo)) { - $logo = $extconf->get('jobapplications', 'logo'); - if (!empty($logo)) - { - $hiringOrganization["logo"] = $logo; - } + $hiringOrganization["logo"] = $logo; + } - $employmentTypes = []; + $employmentTypes = []; - foreach ($posting->getDeserializedEmploymentTypes() as $employmentType) + foreach ($posting->getDeserializedEmploymentTypes() as $employmentType) + { + switch ($employmentType) { - switch ($employmentType) - { - case "fulltime": - $employmentTypes[] = "FULL_TIME"; - break; - case "parttime": - $employmentTypes[] = "PART_TIME"; - break; - case "contractor": - $employmentTypes[] = "CONTRACTOR"; - break; - case "temporary": - $employmentTypes[] = "TEMPORARY"; - break; - case "intern": - $employmentTypes[] = "INTERN"; - break; - case "volunteer": - $employmentTypes[] = "VOLUNTEER"; - break; - case "perdiem": - $employmentTypes[] = "PER_DIEM"; - break; - case "other": - $employmentTypes[] = "OTHER"; - break; - default: - $employmentTypes = []; - } + case "fulltime": + $employmentTypes[] = "FULL_TIME"; + break; + case "parttime": + $employmentTypes[] = "PART_TIME"; + break; + case "contractor": + $employmentTypes[] = "CONTRACTOR"; + break; + case "temporary": + $employmentTypes[] = "TEMPORARY"; + break; + case "intern": + $employmentTypes[] = "INTERN"; + break; + case "volunteer": + $employmentTypes[] = "VOLUNTEER"; + break; + case "perdiem": + $employmentTypes[] = "PER_DIEM"; + break; + case "other": + $employmentTypes[] = "OTHER"; + break; + default: + $employmentTypes = []; } + } - $googleJobsJSON = [ - "@context" => "http://schema.org", - "@type" => "JobPosting", - "datePosted" => $posting->getDatePosted()->format("c"), - "description" => $posting->getCompanyDescription()."
".$posting->getJobDescription()."
" - .$posting->getRoleDescription()."
".$posting->getSkillRequirements() - ."
".$posting->getBenefits(), - "jobLocation" => [ - "@type" => "Place", - "address" => [ - "streetAddress" => $posting->getLocation()->getAddressStreetAndNumber(), - "addressLocality" => $posting->getLocation()->getAddressCity(), - "postalCode" => $posting->getLocation()->getAddressPostCode(), - "addressCountry" => $posting->getLocation()->getAddressCountry() - ] - ], - "title" => $posting->getTitle(), - "employmentType" => $employmentTypes + $arrayLocations = []; + /** @var Location $location */ + foreach($posting->getLocations() as $location) { + $arrayLocations[] = [ + "@type" => "Place", + "address" => [ + "streetAddress" => $location->getAddressStreetAndNumber(), + "addressLocality" => $location->getAddressCity(), + "postalCode" => $location->getAddressPostCode(), + "addressCountry" => $location->getAddressCountry() + ] ]; + } - $googleJobsJSON["hiringOrganization"] = $hiringOrganization; - - if (!empty($posting->getBaseSalary())) - { - $currency = $logo = $extconf->get('jobapplications', 'currency') ?: "EUR"; - $googleJobsJSON["baseSalary"] = [ - "@type" => "MonetaryAmount", - "currency" => $currency, - "value" => [ - "@type" => "QuantitativeValue", - "value" => preg_replace('/\D/', '', $posting->getBaseSalary()), - "unitText" => "YEAR" - ] - ]; - } - - if ($posting->getEndtime() instanceof \DateTime) - { - $googleJobsJSON["validThrough"] = $posting->getEndtime()->format("c"); - } - - $googleJobs = ""; + $googleJobsJSON = [ + "@context" => "http://schema.org", + "@type" => "JobPosting", + "datePosted" => $posting->getDatePosted()->format("c"), + "description" => $posting->getCompanyDescription()."
".$posting->getJobDescription()."
" + .$posting->getRoleDescription()."
".$posting->getSkillRequirements() + ."
".$posting->getBenefits(), + "jobLocation" => $arrayLocations, + "title" => $posting->getTitle(), + "employmentType" => $employmentTypes + ]; - $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); - $pageRenderer->addHeaderData($googleJobs); - } + $googleJobsJSON["hiringOrganization"] = $hiringOrganization; - // Pagetitle Templating - $title = $this->settings["pageTitle"]; - if ($title !== "") + if (!empty($posting->getBaseSalary())) { - $title = str_replace("%postingTitle%", $posting->getTitle(), $title); + $currency = $logo = $extconf->get('jobapplications', 'currency') ?: "EUR"; + $googleJobsJSON["baseSalary"] = [ + "@type" => "MonetaryAmount", + "currency" => $currency, + "value" => [ + "@type" => "QuantitativeValue", + "value" => preg_replace('/\D/', '', $posting->getBaseSalary()), + "unitText" => "YEAR" + ] + ]; } - else + + if ($posting->getEndtime() instanceof \DateTime) { - $title = $posting->getTitle(); + $googleJobsJSON["validThrough"] = $posting->getEndtime()->format("c"); } - $titleProvider->setTitle($title); + $googleJobs = ""; - // SignalSlotDispatcher BeforePostingShowAssign - $changedPosting = $this->signalSlotDispatcher->dispatch(__CLASS__, "BeforePostingShowAssign", ["posting" => $posting]); - if ($changedPosting["posting"] instanceof Posting) - { - $posting = $changedPosting['posting']; - } + $this->pageRenderer->addHeaderData($googleJobs); + } - $this->view->assign('posting', $posting); + public function injectPostingRepository(PostingRepository $postingRepository): void + { + $this->postingRepository = $postingRepository; + } + + public function injectLocationRepository(LocationRepository $locationRepository): void + { + $this->locationRepository = $locationRepository; } } diff --git a/Classes/Domain/Model/Application.php b/Classes/Domain/Model/Application.php index d44894b..923f09a 100644 --- a/Classes/Domain/Model/Application.php +++ b/Classes/Domain/Model/Application.php @@ -1,7 +1,9 @@ files = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); - $this->cv = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); - $this->coverLetter = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); - $this->testimonials = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); - $this->otherFiles = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->files = new ObjectStorage(); + $this->cv = new ObjectStorage(); + $this->coverLetter = new ObjectStorage(); + $this->testimonials = new ObjectStorage(); + $this->otherFiles = new ObjectStorage(); } /** @@ -381,7 +385,7 @@ public function getCv() * * @return void */ - public function setCv(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $cv) + public function setCv(ObjectStorage $cv) { $this->cv = $cv; } @@ -403,7 +407,7 @@ public function getCoverLetter() * * @return void */ - public function setCoverLetter(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $coverLetter) + public function setCoverLetter(ObjectStorage $coverLetter) { $this->coverLetter = $coverLetter; } @@ -425,7 +429,7 @@ public function getTestimonials() * * @return void */ - public function setTestimonials(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $testimonials) + public function setTestimonials(ObjectStorage $testimonials) { $this->testimonials = $testimonials; } @@ -447,7 +451,7 @@ public function getOtherFiles() * * @return void */ - public function setOtherFiles(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $otherFiles) + public function setOtherFiles(ObjectStorage $otherFiles) { $this->otherFiles = $otherFiles; } @@ -690,4 +694,20 @@ public function setFiles($files): void { $this->files = $files; } + + /** + * @return bool + */ + public function isAnonymized(): bool + { + return $this->anonymized; + } + + /** + * @param bool $anonymized + */ + public function setAnonymized(bool $anonymized): void + { + $this->anonymized = $anonymized; + } } diff --git a/Classes/Domain/Model/Constraint.php b/Classes/Domain/Model/Constraint.php index fb69aec..14accae 100644 --- a/Classes/Domain/Model/Constraint.php +++ b/Classes/Domain/Model/Constraint.php @@ -23,7 +23,7 @@ class Constraint /** @var array */ - protected $location = []; + protected $locations = []; /** * @return array @@ -76,16 +76,16 @@ public function setEmploymentType(array $employmentType): void /** * @return array */ - public function getLocation(): array + public function getLocations(): array { - return $this->location; + return $this->locations; } /** - * @param array $location + * @param array $locations */ - public function setLocation(array $location): void + public function setLocations(array $locations): void { - $this->location = $location; + $this->locations = $locations; } } \ No newline at end of file diff --git a/Classes/Domain/Model/Contact.php b/Classes/Domain/Model/Contact.php index f2bc107..c9c2e99 100644 --- a/Classes/Domain/Model/Contact.php +++ b/Classes/Domain/Model/Contact.php @@ -2,6 +2,10 @@ namespace ITX\Jobapplications\Domain\Model; + use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + use TYPO3\CMS\Extbase\Domain\Model\FileReference; + use TYPO3\CMS\Core\Http\ApplicationType; + /*************************************************************** * Copyright notice * @@ -28,7 +32,7 @@ /** * A Contact is the person who handles the application process for this posting. */ - class Contact extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity + class Contact extends AbstractEntity { /** @@ -190,7 +194,7 @@ public function getPhoto() /** * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $photo */ - public function setPhoto(\TYPO3\CMS\Extbase\Domain\Model\FileReference $photo) + public function setPhoto(FileReference $photo) { $this->photo = $photo; } @@ -200,8 +204,7 @@ public function setPhoto(\TYPO3\CMS\Extbase\Domain\Model\FileReference $photo) */ public function getBeUser() { - if (TYPO3_MODE === 'BE') - { + if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()) { return $this->beUser; } diff --git a/Classes/Domain/Model/Location.php b/Classes/Domain/Model/Location.php index 05e26a7..c27dec8 100644 --- a/Classes/Domain/Model/Location.php +++ b/Classes/Domain/Model/Location.php @@ -1,7 +1,8 @@ addressCountry = $addressCountry; } - /** - * Sets the address - * - * @param string $address - * - * @return void - */ - public function setAddress($address) - { - $this->address = $address; - } - /** * Returns the latitude * diff --git a/Classes/Domain/Model/Posting.php b/Classes/Domain/Model/Posting.php index e1551da..aaa71df 100644 --- a/Classes/Domain/Model/Posting.php +++ b/Classes/Domain/Model/Posting.php @@ -2,6 +2,9 @@ namespace ITX\Jobapplications\Domain\Model; + use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + use TYPO3\CMS\Extbase\Persistence\ObjectStorage; + use TYPO3\CMS\Extbase\Domain\Model\FileReference; /*************************************************************** * Copyright notice * @@ -24,11 +27,10 @@ * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ - /** * A Job Posting has a location and a contact person. */ - class Posting extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity + class Posting extends AbstractEntity { /** @var boolean */ protected $hidden; @@ -141,11 +143,11 @@ class Posting extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity protected $companyInformation = ''; /** - * location + * locations * - * @var \ITX\Jobapplications\Domain\Model\Location + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\ITX\Jobapplications\Domain\Model\Location> */ - protected $location = null; + protected $locations = null; /** * contact @@ -198,7 +200,7 @@ public function __construct() */ protected function initStorageObjects() { - $this->categories = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->categories = new ObjectStorage(); } /** @@ -518,25 +520,25 @@ public function setCompanyInformation($companyInformation) } /** - * Returns the location + * Returns the locations * - * @return \ITX\Jobapplications\Domain\Model\Location location + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\ITX\Jobapplications\Domain\Model\Location> $locations */ - public function getLocation() + public function getLocations() { - return $this->location; + return $this->locations; } /** * Sets the location * - * @param \ITX\Jobapplications\Domain\Model\Location $location + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\ITX\Jobapplications\Domain\Model\Location> $locations * * @return void */ - public function setLocation(\ITX\Jobapplications\Domain\Model\Location $location) + public function setLocations(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $locations) { - $this->location = $location; + $this->locations = $locations; } /** @@ -556,7 +558,7 @@ public function getContact() * * @return void */ - public function setContact(\ITX\Jobapplications\Domain\Model\Contact $contact) + public function setContact(Contact $contact) { $this->contact = $contact; } @@ -578,7 +580,7 @@ public function getDetailViewImage() * * @return void */ - public function setDetailViewImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $detailViewImage) + public function setDetailViewImage(FileReference $detailViewImage) { $this->detailViewImage = $detailViewImage; } @@ -600,7 +602,7 @@ public function getListViewImage() * * @return void */ - public function setListViewImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $listViewImage) + public function setListViewImage(FileReference $listViewImage) { $this->listViewImage = $listViewImage; } @@ -616,7 +618,7 @@ public function getCategories() /** * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category> $categories */ - public function setCategories(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $categories): void + public function setCategories(ObjectStorage $categories): void { $this->categories = $categories; } diff --git a/Classes/Domain/Model/Status.php b/Classes/Domain/Model/Status.php index 961e831..583cb13 100644 --- a/Classes/Domain/Model/Status.php +++ b/Classes/Domain/Model/Status.php @@ -2,6 +2,7 @@ namespace ITX\Jobapplications\Domain\Model; + use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; /*************************************************************** @@ -30,7 +31,7 @@ /** * Status */ - class Status extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity + class Status extends AbstractEntity { /** * name @@ -87,7 +88,7 @@ public function getFollowers() /** * @param ObjectStorage $followers */ - public function setFollowers(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $followers) + public function setFollowers(ObjectStorage $followers) { $this->followers = $followers; } diff --git a/Classes/Domain/Model/TtContent.php b/Classes/Domain/Model/TtContent.php index 1413f5f..ca113e9 100644 --- a/Classes/Domain/Model/TtContent.php +++ b/Classes/Domain/Model/TtContent.php @@ -1,7 +1,8 @@ createQuery(); $query->getQuerySettings()->setRespectStoragePage(false) @@ -97,7 +99,7 @@ public function findAll() ->setRespectStoragePage(false) ->setIgnoreEnableFields(true); $query->setOrderings([ - "crdate" => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING + "crdate" => QueryInterface::ORDER_DESCENDING ]); return $query->execute(); @@ -128,13 +130,14 @@ public function findNewApplicationsByContact(int $contact) /** * Finds applications which are older than or equal the given timestamp * - * @param $timestamp int - * @param $status bool + * @param $timestamp int + * @param $status bool + * @param bool $notAnonymized * * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException */ - public function findOlderThan(int $timestamp, bool $status = false) + public function findOlderThan(int $timestamp, bool $status = false, bool $notAnonymized = false) { $query = $this->createQuery(); $query->getQuerySettings()->setRespectStoragePage(false)->setIgnoreEnableFields(true); @@ -142,6 +145,11 @@ public function findOlderThan(int $timestamp, bool $status = false) $andArray = []; $andArray[] = $query->lessThanOrEqual("crdate", $timestamp); + if ($notAnonymized) + { + $andArray[] = $query->equals("anonymized", 0); + } + if ($status) { $andArray[] = $query->equals("status.isEndStatus", 1); diff --git a/Classes/Domain/Repository/ContactRepository.php b/Classes/Domain/Repository/ContactRepository.php index d7bb6c7..0981c71 100644 --- a/Classes/Domain/Repository/ContactRepository.php +++ b/Classes/Domain/Repository/ContactRepository.php @@ -28,7 +28,7 @@ /** * The repository for Contacts */ - class ContactRepository extends \ITX\Jobapplications\Domain\Repository\JobapplicationsRepository + class ContactRepository extends JobapplicationsRepository { /** * @param array $uids diff --git a/Classes/Domain/Repository/JobapplicationsRepository.php b/Classes/Domain/Repository/JobapplicationsRepository.php index 96b80eb..4847bac 100644 --- a/Classes/Domain/Repository/JobapplicationsRepository.php +++ b/Classes/Domain/Repository/JobapplicationsRepository.php @@ -24,6 +24,8 @@ namespace ITX\Jobapplications\Domain\Repository; + use TYPO3\CMS\Extbase\Persistence\Repository; + use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -32,7 +34,7 @@ * * Parent Repository for other repos */ - abstract class JobapplicationsRepository extends \TYPO3\CMS\Extbase\Persistence\Repository + abstract class JobapplicationsRepository extends Repository { /** * Helper function for building the sql for categories @@ -43,7 +45,7 @@ abstract class JobapplicationsRepository extends \TYPO3\CMS\Extbase\Persistence\ * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder */ - public function buildCategoriesToSQL(array $categories, \TYPO3\CMS\Core\Database\Query\QueryBuilder $qb) + public function buildCategoriesToSQL(array $categories, QueryBuilder $qb) { $statement = ""; for ($i = 0, $iMax = count($categories); $i < $iMax; $i++) diff --git a/Classes/Domain/Repository/LocationRepository.php b/Classes/Domain/Repository/LocationRepository.php index d235c8c..120646b 100644 --- a/Classes/Domain/Repository/LocationRepository.php +++ b/Classes/Domain/Repository/LocationRepository.php @@ -30,7 +30,7 @@ /** * The repository for Locations */ - class LocationRepository extends \ITX\Jobapplications\Domain\Repository\JobapplicationsRepository + class LocationRepository extends JobapplicationsRepository { /** * Returns all objects of this repository. diff --git a/Classes/Domain/Repository/PostingRepository.php b/Classes/Domain/Repository/PostingRepository.php index 2333f91..cfbb6ad 100644 --- a/Classes/Domain/Repository/PostingRepository.php +++ b/Classes/Domain/Repository/PostingRepository.php @@ -4,6 +4,7 @@ use ITX\Jobapplications\Domain\Model\Constraint; use ITX\Jobapplications\Domain\Repository\RepoHelpers; + use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Utility\DebugUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\QueryInterface; @@ -36,20 +37,20 @@ /** * The repository for Postings */ - class PostingRepository extends \ITX\Jobapplications\Domain\Repository\JobapplicationsRepository + class PostingRepository extends JobapplicationsRepository { /** * Helper function for finding postings by category * * @param $category array * - * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @return array|QueryResultInterface */ public function findByCategory(array $categories) { $query = $this->createQuery(); - $qb = parent::getQueryBuilder("tx_jobapplications_domain_model_posting"); + $qb = $this->getQueryBuilder("tx_jobapplications_domain_model_posting"); $qb ->select("*") @@ -57,7 +58,7 @@ public function findByCategory(array $categories) ->join("tx_jobapplications_domain_model_posting", "sys_category_record_mm", "sys_category_record_mm", "tx_jobapplications_domain_model_posting.uid = sys_category_record_mm.uid_foreign") ->andWhere($qb->expr()->in('pid', $query->getQuerySettings()->getStoragePageIds())); - $qb = parent::buildCategoriesToSQL($categories, $qb); + $qb = $this->buildCategoriesToSQL($categories, $qb); $query->statement($qb->getSQL()); @@ -67,27 +68,34 @@ public function findByCategory(array $categories) /** * Gets all divisions * - * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @param array|null $categories + * @param int $languageUid + * + * @return array */ - public function findAllDivisions(array $categories = null) + public function findAllDivisions(array $categories = null, int $languageUid): array { $qb = $this->getQueryBuilder("tx_jobapplications_domain_model_posting"); $query = $this->createQuery(); + $query->getQuerySettings()->setLanguageOverlayMode(false); + if (count($categories) === 0) { $qb - ->select("division") - ->groupBy("division") + ->select("division", "sys_language_uid") + ->groupBy("division", "sys_language_uid") ->from("tx_jobapplications_domain_model_posting") - ->andWhere($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())); + ->andWhere($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())) + ->andWhere($qb->expr()->eq("sys_language_uid", $languageUid)); } else { $qb - ->select("division") - ->groupBy("division") + ->select("division", "sys_language_uid") + ->groupBy("division", "sys_language_uid") ->from("tx_jobapplications_domain_model_posting") ->andWhere($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())) + ->andWhere($qb->expr()->eq("sys_language_uid", $languageUid)) ->join("tx_jobapplications_domain_model_posting", "sys_category_record_mm", "sys_category_record_mm", $qb->expr()->eq("tx_jobapplications_domain_model_posting.uid", "sys_category_record_mm.uid_foreign")) @@ -103,28 +111,35 @@ public function findAllDivisions(array $categories = null) } /** - * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @param array|null $categories + * @param int $languageUid + * + * @return array */ - public function findAllCareerLevels(array $categories = null) + public function findAllCareerLevels(array $categories = null, int $languageUid): array { $qb = $this->getQueryBuilder("tx_jobapplications_domain_model_posting"); $query = $this->createQuery(); + $query->getQuerySettings()->setLanguageOverlayMode(false); + if (count($categories) === 0) { $qb - ->select("career_level AS careerLevel") - ->groupBy("careerLevel") + ->select("career_level AS careerLevel", "sys_language_uid") + ->groupBy("careerLevel", "sys_language_uid") ->from("tx_jobapplications_domain_model_posting") - ->where($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())); + ->where($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())) + ->andWhere($qb->expr()->eq("sys_language_uid", $languageUid)); } else { $qb - ->select("career_level AS careerLevel") - ->groupBy("careerLevel") + ->select("career_level AS careerLevel", "sys_language_uid") + ->groupBy("careerLevel", "sys_language_uid") ->from("tx_jobapplications_domain_model_posting") ->where($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())) + ->andWhere($qb->expr()->eq("sys_language_uid", $languageUid)) ->join("tx_jobapplications_domain_model_posting", "sys_category_record_mm", "sys_category_record_mm", $qb->expr()->eq("tx_jobapplications_domain_model_posting.uid", "sys_category_record_mm.uid_foreign")) @@ -140,29 +155,36 @@ public function findAllCareerLevels(array $categories = null) } /** + * @param array|null $categories + * @param int $languageUid + * * @return array */ - public function findAllEmploymentTypes(array $categories = null) + public function findAllEmploymentTypes(array $categories = null, int $languageUid) { $qb = $this->getQueryBuilder("tx_jobapplications_domain_model_posting"); $query = $this->createQuery(); + $query->getQuerySettings()->setLanguageOverlayMode(false); + if (count($categories) === 0) { $qb - ->select("employment_type AS employmentType") - ->groupBy("employmentType") + ->select("employment_type AS employmentType", "sys_language_uid") + ->groupBy("employmentType", "sys_language_uid") ->from("tx_jobapplications_domain_model_posting") ->andWhere($qb->expr()->in('pid', $query->getQuerySettings()->getStoragePageIds())) + ->andWhere($qb->expr()->eq("sys_language_uid", $languageUid)) ->orderBy('employmentType', QueryInterface::ORDER_ASCENDING); } else { $qb - ->select("employment_type AS employmentType") - ->groupBy("employmentType") + ->select("employment_type AS employmentType", "sys_language_uid") + ->groupBy("employmentType", "sys_language_uid") ->from("tx_jobapplications_domain_model_posting") ->where($qb->expr()->in("pid", $query->getQuerySettings()->getStoragePageIds())) + ->andWhere($qb->expr()->eq("sys_language_uid", $languageUid)) ->join("tx_jobapplications_domain_model_posting", "sys_category_record_mm", "sys_category_record_mm", $qb->expr()->eq("tx_jobapplications_domain_model_posting.uid", "sys_category_record_mm.uid_foreign")) @@ -198,7 +220,7 @@ public function findAllEmploymentTypes(array $categories = null) /** * @param $categories * - * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @return array|QueryResultInterface */ public function findAllCategories($categories) { @@ -236,7 +258,7 @@ public function findAllCategories($categories) */ public function findByFilter(array $categories, array $repositoryConfig, Constraint $constraint = null, $orderBy = 'date_posted', - $order = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING) + $order = QueryInterface::ORDER_DESCENDING) { $query = $this->createQuery(); @@ -331,7 +353,7 @@ public function findByFilter(array $categories, array $repositoryConfig, Constra * * @return array|QueryResultInterface */ - public function findByContact(int $contact, string $orderBy = "title", string $order = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING) + public function findByContact(int $contact, string $orderBy = "title", string $order = QueryInterface::ORDER_ASCENDING) { $query = $this->createQuery(); $query->getQuerySettings()->setRespectStoragePage(false) @@ -348,7 +370,7 @@ public function findByContact(int $contact, string $orderBy = "title", string $o * * @return QueryResultInterface|array */ - public function findAllWithOrderIgnoreEnable(string $orderBy = "title", string $order = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING) + public function findAllWithOrderIgnoreEnable(string $orderBy = "title", string $order = QueryInterface::ORDER_ASCENDING) { $query = $this->createQuery(); $query->getQuerySettings()->setRespectStoragePage(false)->setIgnoreEnableFields(true); @@ -361,7 +383,7 @@ public function findAllWithOrderIgnoreEnable(string $orderBy = "title", string $ } /** - * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @return array|QueryResultInterface */ public function findAllIgnoreStoragePage() { diff --git a/Classes/Domain/Repository/StatusRepository.php b/Classes/Domain/Repository/StatusRepository.php index c052a44..eefc9f2 100644 --- a/Classes/Domain/Repository/StatusRepository.php +++ b/Classes/Domain/Repository/StatusRepository.php @@ -32,7 +32,7 @@ /** * The repository for Applications */ - class StatusRepository extends \ITX\Jobapplications\Domain\Repository\JobapplicationsRepository + class StatusRepository extends JobapplicationsRepository { /** * Finds all with option of specifiying order @@ -75,8 +75,8 @@ public function findNewStatus() */ public function generateStatus(string $statusFile, string $statusMmFile, int $pid, int $langUid) { - $file1 = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('EXT:jobapplications/Resources/Private/Sql/'.$statusFile); - $file2 = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('EXT:jobapplications/Resources/Private/Sql/'.$statusMmFile); + $file1 = GeneralUtility::getFileAbsFileName('EXT:jobapplications/Resources/Private/Sql/'.$statusFile); + $file2 = GeneralUtility::getFileAbsFileName('EXT:jobapplications/Resources/Private/Sql/'.$statusMmFile); $queryDropStatus = $this->createQuery(); $queryDropStatus->statement('DROP TABLE tx_jobapplications_domain_model_status'); diff --git a/Classes/Event/BeforeApplicationPersisted.php b/Classes/Event/BeforeApplicationPersisted.php new file mode 100644 index 0000000..687e707 --- /dev/null +++ b/Classes/Event/BeforeApplicationPersisted.php @@ -0,0 +1,35 @@ +application = $application; + } + + /** + * @return Application + */ + public function getApplication(): Application + { + return $this->application; + } + + /** + * @param Application $application + */ + public function setApplication(Application $application): void + { + $this->application = $application; + } + } \ No newline at end of file diff --git a/Classes/Event/DisplayPostingEvent.php b/Classes/Event/DisplayPostingEvent.php new file mode 100644 index 0000000..0778188 --- /dev/null +++ b/Classes/Event/DisplayPostingEvent.php @@ -0,0 +1,33 @@ +posting = $posting; + } + + /** + * @return Posting + */ + public function getPosting(): Posting + { + return $this->posting; + } + + /** + * @param Posting $posting + */ + public function setPosting(Posting $posting): void + { + $this->posting = $posting; + } + } \ No newline at end of file diff --git a/Classes/Hooks/TCEmainHook.php b/Classes/Hooks/TCEmainHook.php index 3347dcc..f40e5de 100644 --- a/Classes/Hooks/TCEmainHook.php +++ b/Classes/Hooks/TCEmainHook.php @@ -23,6 +23,22 @@ */ class TCEmainHook { + protected GoogleIndexingApiConnector $connector; + protected ApplicationFileService $applicationFileService; + protected ApplicationRepository $applicationRepository; + protected DataMapper $dataMapper; + + public function __construct() + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + + $this->connector = $objectManager->get(GoogleIndexingApiConnector::class); + $this->applicationFileService = $objectManager->get(ApplicationFileService::class); + $this->applicationRepository = $objectManager->get(ApplicationRepository::class); + $this->dataMapper = $objectManager->get(DataMapper::class); + } + /** * @param $status * @param $table @@ -32,12 +48,12 @@ class TCEmainHook * * @throws \Exception */ - public function processDatamap_afterDatabaseOperations($status, $table, $id, array $fieldArray, \TYPO3\CMS\Core\DataHandling\DataHandler &$pObj) + public function processDatamap_afterDatabaseOperations($status, $table, $id, array $fieldArray, DataHandler $pObj): void { if ($table === 'tx_jobapplications_domain_model_posting') { - $enabled = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class) + $enabled = GeneralUtility::makeInstance(ExtensionConfiguration::class) ->get('jobapplications', 'indexing_api'); if ($enabled !== "1") @@ -47,19 +63,13 @@ public function processDatamap_afterDatabaseOperations($status, $table, $id, arr if ($status === "new") { - /** @var DataMapper $dataMapper */ - $objectManager = GeneralUtility::makeInstance(ObjectManager::class); - $dataMapper = $objectManager->get(DataMapper::class); - $uid = $pObj->substNEWwithIDs[$id]; $fieldArray['uid'] = $uid; - $posting = $dataMapper->map(Posting::class, [$fieldArray]); - - $connector = new GoogleIndexingApiConnector(); + $posting = $this->dataMapper->map(Posting::class, [$fieldArray]); - $connector->updateGoogleIndex($uid, false, $posting); + $this->connector->updateGoogleIndex($uid, false, $posting); } } } @@ -73,11 +83,11 @@ public function processDatamap_afterDatabaseOperations($status, $table, $id, arr * * @throws \Exception */ - public function processDatamap_postProcessFieldArray($command, $table, $uid, $value, \TYPO3\CMS\Core\DataHandling\DataHandler &$pObj) + public function processDatamap_postProcessFieldArray($command, $table, $uid, $value, DataHandler $pObj) { if ($table === "tx_jobapplications_domain_model_posting") { - $enabled = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class) + $enabled = GeneralUtility::makeInstance(ExtensionConfiguration::class) ->get('jobapplications', 'indexing_api'); if ($enabled !== "1") { @@ -86,14 +96,13 @@ public function processDatamap_postProcessFieldArray($command, $table, $uid, $va if ($command === "update") { - $connector = new GoogleIndexingApiConnector(); if ($value['hidden'] === '1') { - $connector->updateGoogleIndex($uid, true); + $this->connector->updateGoogleIndex($uid, true); } else { - $connector->updateGoogleIndex($uid); + $this->connector->updateGoogleIndex($uid, false); } } } @@ -114,43 +123,32 @@ public function processCmdmap_deleteAction($table, $uid, array $record, &$record { if ($table === "tx_jobapplications_domain_model_posting") { - $enabled = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class) + $enabled = GeneralUtility::makeInstance(ExtensionConfiguration::class) ->get('jobapplications', 'indexing_api'); if ($enabled !== "1") { return; } - $connector = new GoogleIndexingApiConnector(); - $connector->updateGoogleIndex($uid, true); + $this->connector->updateGoogleIndex($uid, true); } if ($table === "tx_jobapplications_domain_model_application") { - /** @var ObjectManager $objectManager */ - $objectManager = GeneralUtility::makeInstance(ObjectManager::class); - /** @var ApplicationFileService $fileService */ - $fileService = $objectManager->get(ApplicationFileService::class); - /** @var ApplicationRepository $applicationRepository */ - $applicationRepository = $objectManager->get(ApplicationRepository::class); - if ($record['hidden'] === 1) { - - /** @var DataMapper $dataMapper */ - $dataMapper = $objectManager->get(DataMapper::class); - $applications = $dataMapper->map(Application::class, [$record]); + $applications = $this->dataMapper->map(Application::class, [$record]); $application = $applications[0]; } else { /** @var Application $application */ - $application = $applicationRepository->findByUid($uid); + $application = $this->applicationRepository->findByUid($uid); } - $path = $fileService->getApplicantFolder($application); - $fileStorage = $fileService->getFileStorage($application); - $fileService->deleteApplicationFolder($path, $fileStorage); + $path = $this->applicationFileService->getApplicantFolder($application); + $fileStorage = $this->applicationFileService->getFileStorage($application); + $this->applicationFileService->deleteApplicationFolder($path, $fileStorage); } } } \ No newline at end of file diff --git a/Classes/Service/ApplicationFileService.php b/Classes/Service/ApplicationFileService.php index 138d9c8..7b5da22 100644 --- a/Classes/Service/ApplicationFileService.php +++ b/Classes/Service/ApplicationFileService.php @@ -2,13 +2,17 @@ namespace ITX\Jobapplications\Service; + use ITX\Jobapplications\Domain\Model\Application; + use TYPO3\CMS\Core\Resource\Driver\LocalDriver; use TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException; use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException; use TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException; + use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException; use TYPO3\CMS\Core\Resource\Exception\InvalidPathException; + use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\ResourceStorageInterface; - use TYPO3\CMS\Core\Utility\GeneralUtility; - use TYPO3\CMS\Extbase\Object\ObjectManager; + use TYPO3\CMS\Core\Resource\StorageRepository; + use TYPO3\CMS\Extbase\Domain\Model\FileReference; /** * This file is part of the "jobapplications" Extension for TYPO3 CMS. @@ -20,18 +24,24 @@ class ApplicationFileService { public const APP_FILE_FOLDER = "applications/"; + protected StorageRepository $storageRepository; + + public function __construct(StorageRepository $storageRepository) + { + $this->storageRepository = $storageRepository; + } + /** * Helper function to generate the folder for an application * * @param $applicationObject * * @return string - * @throws \TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException + * @throws InvalidFileNameException */ - - public function getApplicantFolder(\ITX\Jobapplications\Domain\Model\Application $applicationObject): string + public function getApplicantFolder(Application $applicationObject): string { - return self::APP_FILE_FOLDER.(new \TYPO3\CMS\Core\Resource\Driver\LocalDriver) + return self::APP_FILE_FOLDER.(new LocalDriver) ->sanitizeFileName($applicationObject->getFirstName()."_".$applicationObject->getLastName() ."_".hash("md5", $applicationObject->getFirstName()."|" .$applicationObject->getLastName() @@ -45,35 +55,45 @@ public function getApplicantFolder(\ITX\Jobapplications\Domain\Model\Application * * @return string */ - public function getFileStorage(\ITX\Jobapplications\Domain\Model\Application $applicationObject): ?int + public function getFileStorage(Application $applicationObject): ?int { - if ($applicationObject->getFiles()->count() > 0) { - /** @var \TYPO3\CMS\Extbase\Domain\Model\FileReference $file */ + if ($applicationObject->getFiles() !== null && $applicationObject->getFiles()->count() > 0) + { + /** @var FileReference $file */ $file = $applicationObject->getFiles()->toArray()[0]; + return $file->getOriginalResource()->getStorage()->getUid(); } - if ($applicationObject->getCv() !== null && $applicationObject->getCv()->count() > 0) { - /** @var \TYPO3\CMS\Extbase\Domain\Model\FileReference $file */ + if ($applicationObject->getCv() !== null && $applicationObject->getCv()->count() > 0) + { + /** @var FileReference $file */ $file = $applicationObject->getCv()->toArray()[0]; + return $file->getOriginalResource()->getStorage()->getUid(); } - if ($applicationObject->getCoverLetter() !== null && $applicationObject->getCoverLetter()->count() > 0) { - /** @var \TYPO3\CMS\Extbase\Domain\Model\FileReference $file */ + if ($applicationObject->getCoverLetter() !== null && $applicationObject->getCoverLetter()->count() > 0) + { + /** @var FileReference $file */ $file = $applicationObject->getCoverLetter()->toArray()[0]; + return $file->getOriginalResource()->getStorage()->getUid(); } - if ($applicationObject->getTestimonials() !== null && $applicationObject->getTestimonials()->count() > 0) { - /** @var \TYPO3\CMS\Extbase\Domain\Model\FileReference $file */ + if ($applicationObject->getTestimonials() !== null && $applicationObject->getTestimonials()->count() > 0) + { + /** @var FileReference $file */ $file = $applicationObject->getTestimonials()->toArray()[0]; + return $file->getOriginalResource()->getStorage()->getUid(); } - if ($applicationObject->getOtherFiles() !== null && $applicationObject->getOtherFiles()->count() > 0) { - /** @var \TYPO3\CMS\Extbase\Domain\Model\FileReference $file */ + if ($applicationObject->getOtherFiles() !== null && $applicationObject->getOtherFiles()->count() > 0) + { + /** @var FileReference $file */ $file = $applicationObject->getOtherFiles()->toArray()[0]; + return $file->getOriginalResource()->getStorage()->getUid(); } @@ -83,32 +103,32 @@ public function getFileStorage(\ITX\Jobapplications\Domain\Model\Application $ap /** * Deletes the entire Folder * - * @param $folderPath string + * @param $folderPath string * @param $fileStorage int|null * * @throws InsufficientFolderAccessPermissionsException */ public function deleteApplicationFolder(string $folderPath, ?int $fileStorage): void { - if ($fileStorage === null) { + if ($fileStorage === null) + { return; } - $objectManager = GeneralUtility::makeInstance(ObjectManager::class); - - /* @var \TYPO3\CMS\Core\Resource\StorageRepository $storageRepository */ - $storageRepository = $objectManager->get(\TYPO3\CMS\Core\Resource\StorageRepository::class); - $storage = $storageRepository->findByUid($fileStorage); - if (!$storage instanceof ResourceStorageInterface) { + $storage = $this->storageRepository->findByUid($fileStorage); + if (!$storage instanceof ResourceStorageInterface) + { throw new \RuntimeException(sprintf("Resource storage with uid %d could not be found.", $fileStorage)); } + /** @var Folder|null $folder */ + $folder = null; if ($storage->hasFolder($folderPath)) { $folder = $storage->getFolder($folderPath); } - if ($folder instanceof \TYPO3\CMS\Core\Resource\Folder) + if ($folder instanceof Folder) { try { diff --git a/Classes/Task/AnonymizeApplications.php b/Classes/Task/AnonymizeApplications.php deleted file mode 100644 index c94a495..0000000 --- a/Classes/Task/AnonymizeApplications.php +++ /dev/null @@ -1,108 +0,0 @@ -get(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class); - $applicationRepository = $objectManager->get(\ITX\Jobapplications\Domain\Repository\ApplicationRepository::class); - $applicationFileService = $objectManager->get(\ITX\Jobapplications\Service\ApplicationFileService::class); - - // Calculate Timestamp for how old the application must be to give to the repo - $now = new \DateTime(); - $timestamp = $now->modify("-".$this->days." days")->getTimestamp(); - - if ($status = 1) - { - $applications = $applicationRepository->findNotAnonymizedOlderThan($timestamp, true); - } - else - { - $applications = $applicationRepository->findNotAnonymizedOlderThan($timestamp); - } - - $resultCount = count($applications); - - /* @var \ITX\Jobapplications\Domain\Model\Application $application */ - foreach ($applications as $application) - { - // Actual anonymization + deleting application files - - /* @var \ITX\Jobapplications\Service\ApplicationFileService $applicationFileService */ - $fileStorage = $applicationFileService->getFileStorage($application); - - $applicationFileService->deleteApplicationFolder($applicationFileService->getApplicantFolder($application), $fileStorage); - - $application->setFirstName($anonymizeChars); - $application->setLastName($anonymizeChars); - $application->setAddressStreetAndNumber($anonymizeChars); - $application->setAddressAddition($anonymizeChars); - $application->setAddressPostCode(0); - $application->setEmail("anonymized@anonymized.anonymized"); - $application->setPhone($anonymizeChars); - $application->setMessage($anonymizeChars); - $application->setArchived(true); - $application->setSalutation(""); - $application->setSalaryExpectation($anonymizeChars); - $application->setEarliestDateOfJoining(new \DateTime("@0")); - - $applicationRepository->update($application); - } - - if ($resultCount > 0) - { - $persistenceManager->persistAll(); - } - - $this->logger->info('[ITX\\Jobapplications\\Task\\AnonymizeApplications]: '.$resultCount.' Applications anonymized.'); - - return true; - } - } \ No newline at end of file diff --git a/Classes/Task/CleanUpApplications.php b/Classes/Task/CleanUpApplications.php deleted file mode 100644 index 264f676..0000000 --- a/Classes/Task/CleanUpApplications.php +++ /dev/null @@ -1,89 +0,0 @@ -get(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class); - $applicationRepository = $objectManager->get(\ITX\Jobapplications\Domain\Repository\ApplicationRepository::class); - /** @var ApplicationFileService $applicationFileService */ - $applicationFileService = $objectManager->get(\ITX\Jobapplications\Service\ApplicationFileService::class); - - $now = new \DateTime(); - $timestamp = $now->modify("-".$this->days." days")->getTimestamp(); - - if ($status = 1) - { - $applications = $applicationRepository->findOlderThan($timestamp, true); - } - else - { - $applications = $applicationRepository->findOlderThan($timestamp); - } - - $resultCount = count($applications); - - foreach ($applications as $application) - { - $fileStorage = $applicationFileService->getFileStorage($application); - $applicationRepository->remove($application); - - $applicationFileService->deleteApplicationFolder($applicationFileService->getApplicantFolder($application), $fileStorage); - } - - if ($resultCount > 0) - { - $persistenceManager->persistAll(); - } - - $this->logger->info('[ITX\\Jobapplications\\Task\\CleanUpApplications]: '.$resultCount.' Applications deleted.'); - - return true; - } - } \ No newline at end of file diff --git a/Classes/Task/CleanUpApplicationsAdditionalFieldProvider.php b/Classes/Task/CleanUpApplicationsAdditionalFieldProvider.php deleted file mode 100644 index 73695a7..0000000 --- a/Classes/Task/CleanUpApplicationsAdditionalFieldProvider.php +++ /dev/null @@ -1,134 +0,0 @@ -days; - $taskInfo['task_status'] = $task->status; - - $currentSchedulerModuleAction = $schedulerModule->getCurrentAction(); - $additionalFields = []; - // Write the code for the field - $fieldID = 'task_days'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:jobapplications/Resources/Private/Language/locallang_backend.xlf:task.days.label', - 'cshKey' => 'csh', - 'cshLabel' => $fieldID - ]; - - $fieldID = 'task_status'; - if ($taskInfo[$fieldID] == 1) - { - $fieldCode = ''; - } - else - { - $fieldCode = ''; - } - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:jobapplications/Resources/Private/Language/locallang_backend.xlf:task.status.label', - 'cshKey' => 'csh', - 'cshLabel' => $fieldID - ]; - - return $additionalFields; - } - - /** - * This method checks any additional data that is relevant to the specific task - * If the task class is not relevant, the method is expected to return TRUE - * - * @param array $submittedData Reference to the array containing the data submitted by the user - * @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module) - * - * @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise - */ - public function validateAdditionalFields(array &$submittedData, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $schedulerModule) - { - $submittedData['days'] = trim($submittedData['days']); - if (empty($submittedData['days'])) - { - // @extensionScannerIgnoreLine - $this->addMessage( - $this->getLanguageService()->sL('LLL:EXT:jobapplications/Resources/Private/Language/locallang_backend.xlf:task.days.empty'), - AbstractMessage::ERROR - ); - $result = false; - } - else - { - $result = true; - } - - return $result; - } - - /** - * @return LanguageService|null - */ - protected function getLanguageService(): ?LanguageService - { - return $GLOBALS['LANG'] ?? null; - } - - /** - * This method is used to save any additional input into the current task object - * if the task class matches - * - * @param array $submittedData Array containing the data submitted by the user - * @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the current task object - */ - public function saveAdditionalFields(array $submittedData, \TYPO3\CMS\Scheduler\Task\AbstractTask $task) - { - $task->days = $submittedData['days']; - $task->status = $submittedData['status']; - } - } \ No newline at end of file diff --git a/Classes/Traits/PageRendererTrait.php b/Classes/Traits/PageRendererTrait.php new file mode 100644 index 0000000..d6bd440 --- /dev/null +++ b/Classes/Traits/PageRendererTrait.php @@ -0,0 +1,33 @@ +registerUniversalTagAttributes(); + } + + /** + * Registers all standard and HTML5 universal attributes. + * Should be used inside registerArguments(); + * + * @return void + * @api + */ + protected function registerUniversalTagAttributes() + { + parent::registerUniversalTagAttributes(); + $this->registerArgument( + 'forceClosingTag', + 'boolean', + 'If TRUE, forces the created tag to use a closing tag. If FALSE, allows self-closing tags.', + false, + false + ); + $this->registerArgument( + 'hideIfEmpty', + 'boolean', + 'Hide the tag completely if there is no tag content', + false, + false + ); + $this->registerTagAttribute( + 'contenteditable', + 'string', + 'Specifies whether the contents of the element are editable.' + ); + $this->registerTagAttribute( + 'contextmenu', + 'string', + 'The value of the id attribute on the menu with which to associate the element as a context menu.' + ); + $this->registerTagAttribute( + 'draggable', + 'string', + 'Specifies whether the element is draggable.' + ); + $this->registerTagAttribute( + 'dropzone', + 'string', + 'Specifies what types of content can be dropped on the element, and instructs the UA about which ' . + 'actions to take with content when it is dropped on the element.' + ); + $this->registerTagAttribute( + 'translate', + 'string', + 'Specifies whether an element’s attribute values and contents of its children are to be translated ' . + 'when the page is localized, or whether to leave them unchanged.' + ); + $this->registerTagAttribute( + 'spellcheck', + 'string', + 'Specifies whether the element represents an element whose contents are subject to spell checking and ' . + 'grammar checking.' + ); + $this->registerTagAttribute( + 'hidden', + 'string', + 'Specifies that the element represents an element that is not yet, or is no longer, relevant.' + ); + } + + /** + * Renders the provided tag with the given name and any + * (additional) attributes not already provided as arguments. + * + * @param string $tagName + * @param mixed $content + * @param array $attributes + * @param array $nonEmptyAttributes + * @return string + */ + protected function renderTag( + $tagName, + $content = null, + array $attributes = [], + array $nonEmptyAttributes = ['id', 'class'] + ) { + $trimmedContent = trim((string) $content); + $forceClosingTag = (boolean) $this->arguments['forceClosingTag']; + if (true === empty($trimmedContent) && true === (boolean) $this->arguments['hideIfEmpty']) { + return ''; + } + if ('none' === $tagName || true === empty($tagName)) { + // skip building a tag if special keyword "none" is used, or tag name is empty + return $trimmedContent; + } + $this->tag->setTagName($tagName); + $this->tag->addAttributes($attributes); + $this->tag->forceClosingTag($forceClosingTag); + if (null !== $content) { + $this->tag->setContent($trimmedContent); + } + // process some attributes differently - if empty, remove the property: + foreach ($nonEmptyAttributes as $propertyName) { + $value = $this->arguments[$propertyName]; + if (true === empty($value)) { + $this->tag->removeAttribute($propertyName); + } else { + $this->tag->addAttribute($propertyName, $value); + } + } + return $this->tag->render(); + } + + /** + * Renders the provided tag and optionally appends or prepends + * it to the main tag's content depending on 'mode' which can + * be one of 'none', 'append' or 'prepend' + * + * @param string $tagName + * @param array $attributes + * @param boolean $forceClosingTag + * @param string $mode + * @return string + */ + protected function renderChildTag($tagName, $attributes = [], $forceClosingTag = false, $mode = 'none') + { + $tagBuilder = clone $this->tag; + $tagBuilder->reset(); + $tagBuilder->setTagName($tagName); + $tagBuilder->addAttributes($attributes); + $tagBuilder->forceClosingTag($forceClosingTag); + $childTag = $tagBuilder->render(); + if ('append' === $mode || 'prepend' === $mode) { + $content = $this->tag->getContent(); + if ('append' === $mode) { + $content = $content . $childTag; + } else { + $content = $childTag . $content; + } + $this->tag->setContent($content); + } + return $childTag; + } +} diff --git a/Classes/Updates/MakeLocationsMultiple.php b/Classes/Updates/MakeLocationsMultiple.php new file mode 100644 index 0000000..4e53013 --- /dev/null +++ b/Classes/Updates/MakeLocationsMultiple.php @@ -0,0 +1,155 @@ +getConnectionForTable($this->tablePosting); + /** @var QueryBuilder $queryBuilderPostings */ + $queryBuilderPostings = $connectionPostings->createQueryBuilder(); + $queryBuilderPostings->getRestrictions()->removeAll(); + + // Get Mm table + $connectionMm = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable($this->tableMm); + + // Get all postings where location is not 0 and locations is 0 + $statement = $queryBuilderPostings->select('uid', $this->oldFieldName) + ->from($this->tablePosting) + ->where( + $queryBuilderPostings->expr()->neq($this->oldFieldName, 0), + $queryBuilderPostings->expr()->eq($this->newFieldName, 0) + ) + ->execute(); + + while ($record = $statement->fetch()) + { + // Create mm entry + /** @var QueryBuilder $queryBuilderMm */ + $queryBuilderMm = $connectionMm->createQueryBuilder(); + $queryBuilderMm->getRestrictions()->removeAll(); + $queryBuilderMm->insert($this->tableMm) + ->values([ + 'uid_local' => (int)$record['uid'], + 'uid_foreign' => (int)$record[$this->oldFieldName], + 'sorting' => 1, + 'sorting_foreign' => 0 + ]) + ->execute(); + + // Update locations field + /** @var QueryBuilder $queryBuilderPostings */ + $queryBuilderPostings = $connectionPostings->createQueryBuilder(); + + $queryBuilderPostings->update($this->tablePosting) + ->where( + $queryBuilderPostings->expr()->eq( + 'uid', + $queryBuilderPostings->createNamedParameter($record['uid'], \PDO::PARAM_INT) + ) + ) + ->set('locations', 1); + //$databaseQueries[] = $queryBuilderPostings->getSQL(); + $queryBuilderPostings->execute(); + } + + return true; + } + + public function updateNecessary(): bool + { + $updateNeeded = false; + + if ($this->checkIfNeeded()) + { + $updateNeeded = true; + } + + return $updateNeeded; + } + + public function getPrerequisites(): array + { + return [ + DatabaseUpdatedPrerequisite::class + ]; + } + + /** + * Check if there are postings entries with locations + * + * @return bool + * @throws \InvalidArgumentException + */ + protected function checkIfNeeded(): bool + { + /** @var $connectionPool ConnectionPool */ + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + $connection = $connectionPool->getConnectionForTable($this->tablePosting); + + // Stop if there is no location field + $tableColumns = $connection->getSchemaManager()->listTableColumns($this->tablePosting); + if (!isset($tableColumns[$this->oldFieldName])) + { + return false; + } + + $queryBuilder = $connectionPool->getQueryBuilderForTable($this->tablePosting); + $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + // Are there postings with associated locations? + $numberOfEntries = $queryBuilder + ->count('uid') + ->from($this->tablePosting) + ->where( + $queryBuilder->expr()->neq($this->oldFieldName, $queryBuilder->createNamedParameter(0)) + ) + ->execute() + ->fetchOne(); + + return $numberOfEntries > 0; + } + } \ No newline at end of file diff --git a/Classes/Utility/GoogleIndexingApiConnector.php b/Classes/Utility/GoogleIndexingApiConnector.php index 6b92c5b..5a74a41 100644 --- a/Classes/Utility/GoogleIndexingApiConnector.php +++ b/Classes/Utility/GoogleIndexingApiConnector.php @@ -27,15 +27,16 @@ use ITX\Jobapplications\Domain\Model\Posting; use ITX\Jobapplications\Domain\Repository\PostingRepository; use ITX\Jobapplications\Domain\Repository\TtContentRepository; + use JsonException; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; + use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Http\RequestFactory; + use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Messaging\FlashMessage; - use TYPO3\CMS\Core\Messaging\FlashMessageQueue; use TYPO3\CMS\Core\Messaging\FlashMessageService; use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\Category; - use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extbase\Persistence\Generic\QueryResult; /** @@ -45,33 +46,39 @@ */ class GoogleIndexingApiConnector { - /** @var ObjectManager */ - protected $objectManager; + protected array $googleConfig; - /** @var array */ - protected $googleConfig; - - /** @var RequestFactory */ - protected $requestFactory; - - /** @var array */ - protected $backendConfiguration; + protected array $backendConfiguration; /** @var boolean */ - protected $supressFlashMessages; + protected bool $supressFlashMessages; + + protected RequestFactory $requestFactory; + protected TtContentRepository $ttContentRepository; + protected PostingRepository $postingRepository; + protected FlashMessageService $flashMessageService; + protected FlexFormService $flexFormService; /** * GoogleIndexingApiConnector constructor. * - * @param bool $supressFlashMessages + * @throws JsonException */ - public function __construct($supressFlashMessages = false) + public function __construct(RequestFactory $requestFactory, TtContentRepository $ttContentRepository, + PostingRepository $postingRepository, FlashMessageService $flashMessageService, + FlexFormService $flexFormService) { - $this->backendConfiguration = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ExtensionConfiguration::class) - ->get('jobapplications'); + $this->requestFactory = $requestFactory; + $this->ttContentRepository = $ttContentRepository; + $this->postingRepository = $postingRepository; + $this->flashMessageService = $flashMessageService; + $this->flexFormService = $flexFormService; + + $this->backendConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('jobapplications'); if ($this->backendConfiguration['key_path'] !== '') { - $fileName = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($this->backendConfiguration['key_path']); + $fileName = GeneralUtility::getFileAbsFileName($this->backendConfiguration['key_path']); if (file_exists($fileName)) { $this->googleConfig = json_decode(file_get_contents( @@ -79,35 +86,30 @@ public function __construct($supressFlashMessages = false) ), true, 512, JSON_THROW_ON_ERROR); } } + } - $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class); - $this->requestFactory = $this->objectManager->get(RequestFactory::class); - - $this->supressFlashMessages = $supressFlashMessages; + public function setSupressFlashMessages(bool $shouldSupress) + { + $this->supressFlashMessages = $shouldSupress; } /** * Sends requests to Google Indexing API * - * @param $uid - * @param bool $delete - * @param Posting $specificPosting + * @param $uid + * @param bool $delete + * @param Posting|null $specificPosting * * @return bool * @throws \Exception */ - public function updateGoogleIndex($uid, $delete = false, $specificPosting = null): ?bool + public function updateGoogleIndex($uid, bool $delete, Posting $specificPosting = null): ?bool { - if (\TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext()->isDevelopment() && $this->backendConfiguration['indexing_api_dev'] === "0") + if (Environment::getContext()->isDevelopment() && $this->backendConfiguration['indexing_api_dev'] === "0") { return false; } - /** @var PostingRepository $postingRepository */ - $postingRepository = $this->objectManager->get(\ITX\Jobapplications\Domain\Repository\PostingRepository::class); - /** @var TtContentRepository $ttContentRepository */ - $ttContentRepository = $this->objectManager->get(\ITX\Jobapplications\Domain\Repository\TtContentRepository::class); - /** @var Posting $posting */ if ($specificPosting instanceof Posting) { @@ -115,7 +117,7 @@ public function updateGoogleIndex($uid, $delete = false, $specificPosting = null } else { - $query = $postingRepository->createQuery(); + $query = $this->postingRepository->createQuery(); $query->getQuerySettings()->setIgnoreEnableFields(true) ->setRespectStoragePage(false); $query->matching( @@ -135,7 +137,7 @@ public function updateGoogleIndex($uid, $delete = false, $specificPosting = null $uriBuilder = new FrontendUriBuilder(); /** @var QueryResult $contentElements */ - $contentElements = $ttContentRepository->findByListType("jobapplications_frontend"); + $contentElements = $this->ttContentRepository->findByListType("jobapplications_frontend"); $contentElements = $contentElements->toArray(); @@ -160,7 +162,7 @@ public function updateGoogleIndex($uid, $delete = false, $specificPosting = null } else { - $result = $this->makeRequest($url); + $result = $this->makeRequest($url, false); } if ($result && $delete === false) @@ -183,29 +185,26 @@ public function updateGoogleIndex($uid, $delete = false, $specificPosting = null * @param string $header * @param bool $error */ - private function sendFlashMessage(string $msg, string $header = "", bool $error = false) + private function sendFlashMessage(string $msg, string $header = "", bool $error = false): void { $debug = $this->backendConfiguration['indexing_api_debug']; - if ($debug === "0" || $this->suppressFlashMessages) + if ($debug === "0") { return; } - $type = \TYPO3\CMS\Core\Messaging\FlashMessage::OK; + $type = AbstractMessage::OK; if ($error) { - $type = \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING; + $type = AbstractMessage::WARNING; } /** @var FlashMessage $message */ - $message = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessage::class, $msg, $header, $type, true); + $message = GeneralUtility::makeInstance(FlashMessage::class, $msg, $header, $type, true); - /** @var FlashMessageService $flashMessageService */ - $flashMessageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class); - /** @var FlashMessageQueue $messageQueue */ - $messageQueue = $flashMessageService->getMessageQueueByIdentifier(); + $messageQueue = $this->flashMessageService->getMessageQueueByIdentifier(); // @extensionScannerIgnoreLine $messageQueue->addMessage($message); } @@ -215,16 +214,14 @@ private function sendFlashMessage(string $msg, string $header = "", bool $error * * @param $contentElements array * @param $posting Posting + * + * @return mixed */ - private function findBestPluginPageFit($contentElements, $posting) + private function findBestPluginPageFit(array $contentElements, Posting $posting): mixed { - - /** @var FlexFormService $flexformService */ - $flexformService = $this->objectManager->get(FlexFormService::class); - $postingCategories = $posting->getCategories()->toArray(); - $fallback = $flexformService->convertFlexFormContentToArray($contentElements[0]->getPiFlexform(), "lDEF")['settings']['detailViewUid']; + $fallback = $this->flexFormService->convertFlexFormContentToArray($contentElements[0]->getPiFlexform())['settings']['detailViewUid']; $result = null; @@ -235,12 +232,12 @@ private function findBestPluginPageFit($contentElements, $posting) { foreach ($contentElements as $contentElement) { - $flexformArray = $flexformService->convertFlexFormContentToArray($contentElement->getPiFlexform(), "lDEF"); + $flexformArray = $this->flexFormService->convertFlexFormContentToArray($contentElement->getPiFlexform()); $categories = explode(",", $flexformArray['settings']['categories']); $postingCategoryUid = $postingCategory->getUid(); - if (in_array($postingCategoryUid, $categories)) + if (in_array($postingCategoryUid, $categories, true)) { if (count($categories) === 1) { @@ -260,7 +257,7 @@ private function findBestPluginPageFit($contentElements, $posting) { foreach ($contentElements as $contentElement) { - $flexformArray = $flexformService->convertFlexFormContentToArray($contentElement->getPiFlexform(), "lDEF"); + $flexformArray = $this->flexFormService->convertFlexFormContentToArray($contentElement->getPiFlexform(), "lDEF"); $categories = explode(",", $flexformArray['settings']['categories']); @@ -280,8 +277,9 @@ private function findBestPluginPageFit($contentElements, $posting) * @param bool $deleteInsteadOfUpdate * * @return bool true success, false something went wrong + * @throws JsonException */ - public function makeRequest(string $url, $deleteInsteadOfUpdate = false): ?bool + public function makeRequest(string $url, bool $deleteInsteadOfUpdate): ?bool { $accessToken = ""; @@ -341,7 +339,7 @@ public function makeRequest(string $url, $deleteInsteadOfUpdate = false): ?bool "Content-Type" => "application/json", 'Authorization' => "Bearer ".$accessToken ], - "body" => json_encode($actualBody), + "body" => json_encode($actualBody, JSON_THROW_ON_ERROR), "http_errors" => false ]; @@ -358,11 +356,12 @@ public function makeRequest(string $url, $deleteInsteadOfUpdate = false): ?bool } /** - * Authenticates with google oauth api + * Authenticates with Google OAuth API * * @return string Bearer token + * @throws JsonException */ - private function makeAuthReq() + private function makeAuthReq(): string { // Authentication $jwtHeader = [ @@ -370,7 +369,7 @@ private function makeAuthReq() "typ" => "JWT" ]; - $jwtHeaderBase64 = base64_encode(json_encode($jwtHeader)); + $jwtHeaderBase64 = base64_encode(json_encode($jwtHeader, JSON_THROW_ON_ERROR)); $jwtClaimSet = [ "iss" => $this->googleConfig["client_email"], @@ -380,7 +379,7 @@ private function makeAuthReq() "iat" => time() ]; - $jwtClaimSetBase64 = base64_encode(json_encode($jwtClaimSet)); + $jwtClaimSetBase64 = base64_encode(json_encode($jwtClaimSet, JSON_THROW_ON_ERROR)); $signatureInput = $jwtHeaderBase64.".".$jwtClaimSetBase64; @@ -399,7 +398,7 @@ private function makeAuthReq() $signatureSignedBase64 = base64_encode($signatureSigned); - $token .= $signatureInput.".".$signatureSignedBase64; + $token = $signatureInput.".".$signatureSignedBase64; $accessTokenRequestData = [ "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer", @@ -410,7 +409,7 @@ private function makeAuthReq() "headers" => [ "Content-Type" => "application/json" ], - "body" => json_encode($accessTokenRequestData), + "body" => json_encode($accessTokenRequestData, JSON_THROW_ON_ERROR), "http_errors" => false ]; @@ -424,20 +423,18 @@ private function makeAuthReq() return ""; } - else - { - $accessRepsonseJson = json_decode($accessResponse->getBody()->getContents(), true); - $accessToken = $accessRepsonseJson["access_token"]; - $sessionData = [ - "indexingAccessToken" => [ - "token" => $accessToken, - "validUntil" => time() + (int)$accessRepsonseJson["expires_in"] - ] - ]; + $accessRepsonseJson = json_decode($accessResponse->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); - $GLOBALS["BE_USER"]->setAndSaveSessionData("tx_jobapplications", $sessionData); - } + $accessToken = $accessRepsonseJson["access_token"]; + $sessionData = [ + "indexingAccessToken" => [ + "token" => $accessToken, + "validUntil" => time() + (int)$accessRepsonseJson["expires_in"] + ] + ]; + + $GLOBALS["BE_USER"]->setAndSaveSessionData("tx_jobapplications", $sessionData); if (!$accessToken) { diff --git a/Classes/Utility/Mail/MailFluid.php b/Classes/Utility/Mail/MailFluid.php deleted file mode 100644 index 9bf2786..0000000 --- a/Classes/Utility/Mail/MailFluid.php +++ /dev/null @@ -1,206 +0,0 @@ -view->assign('normalizedParams', NormalizedParams::createFromServerParams($_SERVER)); - $this->format(self::FORMAT_BOTH); - } - - /** - * @inheritDoc - */ - public function setRecipient(array $addresses, string $name = ''): MailInterface - { - foreach ($addresses as $key => $address) - { - $addressObject = $this->addressArrayToObject($key, $address); - $this->addTo($addressObject); - } - - return $this; - } - - /** - * @param $key - * @param $address - * - * @return Address - */ - private function addressArrayToObject($key, $address): Address - { - $addressObject = null; - if (is_int($key)) - { - $addressObject = new Address($address); - } - else - { - $addressObject = new Address($key, $address); - } - - return $addressObject; - } - - /** - * @inheritDoc - */ - public function setSubject(string $subject): MailInterface - { - return $this->subject($subject); - } - - /** - * @inheritDoc - */ - public function setReply(array $addresses, $name = ''): MailInterface - { - foreach ($addresses as $key => $address) - { - $addressObject = $this->addressArrayToObject($key, $address); - - $this->addReplyTo($addressObject); - } - - return $this; - } - - /** - * @inheritDoc - */ - public function setContent(string $contents, $objects = []): MailInterface - { - $assign = array_merge([ - 'text' => $contents - ], $objects); - - return $this->assignMultiple($assign); - } - - /** - * @inheritDoc - */ - public function addAttachment(string $url): MailInterface - { - return $this->attachFromPath($url); - } - - /** - * @inheritDoc - * @throws TransportExceptionInterface - */ - public function send(): bool - { - /** @var Mailer $mailer */ - $mailer = GeneralUtility::makeInstance(Mailer::class); - - $mailer->send($this); - - return true; - } - - /** - * @inheritDoc - */ - public function setBlindcopies(array $addresses): MailInterface - { - foreach ($addresses as $key => $address) - { - $addressObject = $this->addressArrayToObject($key, $address); - - $this->addBcc($addressObject); - } - - return $this; - } - - /** - * @inheritDoc - */ - public function setTo(array $addresses, string $name = ''): MailInterface - { - foreach ($addresses as $key => $address) - { - $addressObject = $this->addressArrayToObject($key, $address); - - $this->addTo($addressObject); - } - - return $this; - } - - /** - * @inheritDoc - */ - public function setFrom(array $addresses, $name = ''): MailInterface - { - foreach ($addresses as $key => $address) - { - $addressObject = $this->addressArrayToObject($key, $address); - - $this->addFrom($addressObject); - } - - return $this; - } - - /** - * @inheritDoc - */ - public function setContentType(string $contentType): MailInterface - { - if ($contentType === self::CONTENT_TYPE_TEXT) - { - $contentType = 'plain'; - } - $this->format($contentType); - - return $this; - } - } \ No newline at end of file diff --git a/Classes/Utility/Mail/MailInterface.php b/Classes/Utility/Mail/MailInterface.php deleted file mode 100644 index 8b0a62b..0000000 --- a/Classes/Utility/Mail/MailInterface.php +++ /dev/null @@ -1,115 +0,0 @@ -to($addresses, $name); - } - - /** - * Set the to addresses of this message. - * - * If multiple recipients will receive the message an array should be used. - * Example: array('receiver@domain.org', 'other@domain.org' => 'A name') - * - * If $name is passed and the first parameter is a string, this name will be - * associated with the address. - * - * @param string|array $addresses - * @param string $name optional - * - * @return \TYPO3\CMS\Core\Mail\MailMessage - */ - public function setTo($addresses, $name = ''): MailInterface - { - parent::setTo($addresses, $name); - - return $this; - } - - /** - * Set the from address of this message. - * - * You may pass an array of addresses if this message is from multiple people. - * - * If $name is passed and the first parameter is a string, this name will be - * associated with the address. - * - * @param string|array $addresses - * @param string $name optional - * - * @return \TYPO3\CMS\Core\Mail\MailMessage - */ - public function setFrom($addresses, $name = ''): MailInterface - { - parent::setFrom($addresses, $name); - - return $this; - } - - /** - * @inheritDoc - */ - public function setBlindcopies(array $addresses): MailInterface - { - $this->setBcc($addresses); - - return $this; - } - - /** - * @inheritDoc - */ - public function setReply(array $addresses): MailInterface - { - $this->setReplyTo($addresses); - - return $this; - } - - /** - * @inheritDoc - * - * Objects and Content Type are ignored - */ - public function setContent(string $content, $objects = []): MailInterface - { - $this->setBody($content); - - return $this; - } - - /** - * @inheritDoc - */ - public function addAttachment(string $url): MailInterface - { - return $this->attach(\Swift_Attachment::fromPath($url)); - } - - /** - * @inheritDoc - */ - public function setContentType($contentType): MailInterface - { - switch ($contentType) - { - case self::CONTENT_TYPE_TEXT: - parent::setContentType('text/plain'); - break; - default: - parent::setContentType('text/html'); - break; - } - - return $this; - } - - /** - * Set the subject of this message. - * - * @param string $subject - * - * @return $this - */ - public function setSubject($subject): MailInterface - { - parent::setSubject($subject); - - return $this; - } - - public function send(): bool - { - $result = parent::send(); - - return $result !== 0; - } - } \ No newline at end of file diff --git a/Classes/Utility/UploadFileUtility.php b/Classes/Utility/UploadFileUtility.php index 7fa3486..f35ca90 100644 --- a/Classes/Utility/UploadFileUtility.php +++ b/Classes/Utility/UploadFileUtility.php @@ -70,7 +70,7 @@ public function handleFile(UploadedFile $file): string $renameResult = rename($filePath.reset($files), $filePath.$file->getClientFilename()); if (!$renameResult) { - throw new \RuntimeException("Could not rename file: ".$filePath.files[0]); + throw new \RuntimeException("Could not rename file: ".$filePath.$files[0]); } } else @@ -85,14 +85,13 @@ public function handleFile(UploadedFile $file): string * @param string $uniqueIdentifier * * @return string - * @throws IdentifierNotValidException */ public function getFilePath(string $uniqueIdentifier): string { $filePath = $this->fileTempPath.$uniqueIdentifier.DIRECTORY_SEPARATOR; if (strlen($uniqueIdentifier) !== 23) { - throw new IdentifierNotValidException('Invalid identifier'); + throw new \RuntimeException('Invalid identifier'); } if (!GeneralUtility::validPathStr($filePath)) @@ -125,7 +124,7 @@ public function getFilePath(string $uniqueIdentifier): string * @param string $uniqueIdentifier * * @return string - * @throws IdentifierNotValidException + * @throws \RuntimeException */ public function getFileName(string $uniqueIdentifier): string { @@ -133,7 +132,7 @@ public function getFileName(string $uniqueIdentifier): string if (strlen($uniqueIdentifier) !== 23) { - throw new IdentifierNotValidException('Invalid identifier'); + throw new \RuntimeException('Invalid identifier'); } if (!GeneralUtility::validPathStr($filePath)) diff --git a/Classes/ViewHelpers/Condition/InArrayViewhelper.php b/Classes/ViewHelpers/Condition/InArrayViewhelper.php new file mode 100644 index 0000000..e824e21 --- /dev/null +++ b/Classes/ViewHelpers/Condition/InArrayViewhelper.php @@ -0,0 +1,46 @@ +registerArgument('haystack', 'mixed', 'View helper haystack ', true); + $this->registerArgument('needle', 'string', 'View helper needle', true); + } + + // php in_array viewhelper + public function render() + { + + $needle = $this->arguments['needle']; + $haystack = $this->arguments['haystack']; + + if (!is_array($haystack)) + { + return $this->renderElseChild(); + } + + if (in_array($needle, $haystack)) + { + return $this->renderThenChild(); + } + else + { + return $this->renderElseChild(); + } + } + } \ No newline at end of file diff --git a/Classes/ViewHelpers/Format/ReplaceStringViewHelper.php b/Classes/ViewHelpers/Format/ReplaceStringViewHelper.php new file mode 100644 index 0000000..17c47b3 --- /dev/null +++ b/Classes/ViewHelpers/Format/ReplaceStringViewHelper.php @@ -0,0 +1,34 @@ +registerArgument('substring', 'string', 'View helper substring ', TRUE); + $this->registerArgument('content', 'string', 'View helper content', TRUE); + $this->registerArgument('replacement', 'string', 'View helper replacement', TRUE); + } + + /** + * Render method + * + * @return string + */ + public function render() + { + return str_replace($this->arguments['substring'], $this->arguments['replacement'], $this->arguments['content']); + } + } \ No newline at end of file diff --git a/Classes/ViewHelpers/GroupByCategoryViewHelper.php b/Classes/ViewHelpers/GroupByCategoryViewHelper.php index 47b0e2f..acc4b26 100644 --- a/Classes/ViewHelpers/GroupByCategoryViewHelper.php +++ b/Classes/ViewHelpers/GroupByCategoryViewHelper.php @@ -7,11 +7,13 @@ * See LICENSE.txt that was shipped with this package. */ + use Closure; use ITX\Jobapplications\Domain\Model\Posting; use TYPO3\CMS\Extbase\Domain\Model\Category; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; + use TYPO3Fluid\Fluid\Core\ViewHelper\Exception; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; /** @@ -32,13 +34,13 @@ class GroupByCategoryViewHelper extends AbstractViewHelper /** * @param array $arguments - * @param \Closure $renderChildrenClosure + * @param Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * * @return string * @throws ViewHelper\Exception */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) { $templateVariableContainer = $renderingContext->getVariableProvider(); if (!isset($arguments['postings'])) @@ -47,9 +49,10 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl } if (is_object($arguments['postings']) && !$arguments['postings'] instanceof \Traversable) { - throw new ViewHelper\Exception('GroupByCategoryViewHelper only supports arrays and objects implementing \Traversable interface', 1248728393); + throw new Exception('GroupByCategoryViewHelper only supports arrays and objects implementing \Traversable interface', 1248728393); } + $iterationData = []; if (isset($arguments['iteration'])) { $iterationData = [ diff --git a/Classes/ViewHelpers/Page/Header/MetaViewHelper.php b/Classes/ViewHelpers/Page/Header/MetaViewHelper.php new file mode 100644 index 0000000..0c37437 --- /dev/null +++ b/Classes/ViewHelpers/Page/Header/MetaViewHelper.php @@ -0,0 +1,85 @@ +registerTagAttribute('name', 'string', 'Name property of meta tag'); + $this->registerTagAttribute('http-equiv', 'string', 'Property: http-equiv'); + $this->registerTagAttribute('property', 'string', 'Property of meta tag'); + $this->registerTagAttribute('content', 'string', 'Content of meta tag'); + $this->registerTagAttribute('scheme', 'string', 'Property: scheme'); + $this->registerTagAttribute('lang', 'string', 'Property: lang'); + $this->registerTagAttribute('dir', 'string', 'Property: dir'); + } + + /** + * Render method + * + * @return void + */ + public function render() + { + $content = $this->arguments['content']; + if (!empty($content)) + { + $pageRenderer = static::getPageRenderer(); + + $properties = []; + $type = 'name'; + $name = $this->tag->getAttribute('name'); + if (!empty($this->tag->getAttribute('property'))) + { + $type = 'property'; + $name = $this->tag->getAttribute('property'); + } + elseif (!empty($this->tag->getAttribute('http-equiv'))) + { + $type = 'http-equiv'; + $name = $this->tag->getAttribute('http-equiv'); + } + foreach (['http-equiv', 'property', 'scheme', 'lang', 'dir'] as $propertyName) + { + if (!empty($this->tag->getAttribute($propertyName))) + { + $properties[$propertyName] = $this->tag->getAttribute($propertyName); + } + } + $pageRenderer->setMetaTag($type, $name, $content, $properties); + } + } + } diff --git a/Classes/Widgets/Provider/ApplicationsPerPostingBarChartProvider.php b/Classes/Widgets/Provider/ApplicationsPerPostingBarChartProvider.php index 6bb76f8..ec117c8 100644 --- a/Classes/Widgets/Provider/ApplicationsPerPostingBarChartProvider.php +++ b/Classes/Widgets/Provider/ApplicationsPerPostingBarChartProvider.php @@ -24,6 +24,9 @@ namespace ITX\Jobapplications\Widgets\Provider; + use TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface; + use TYPO3\CMS\Core\Utility\GeneralUtility; + use ITX\Jobapplications\Domain\Repository\PostingRepository; use ITX\Jobapplications\Domain\Model\Posting; use ITX\Jobapplications\Domain\Repository\ApplicationRepository; use TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget; @@ -34,30 +37,29 @@ * * @package ITX\Jobapplications\Widgets */ - class ApplicationsPerPostingBarChartProvider implements \TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface + class ApplicationsPerPostingBarChartProvider implements ChartDataProviderInterface { /** @var array */ - protected $labels = []; + protected array $labels = []; - public function getChartData(): array - { - /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectmanager */ - $objectmanager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class); - - /** @var \ITX\Jobapplications\Domain\Repository\PostingRepository $postingRepo */ - $postingRepo = $objectmanager->get(\ITX\Jobapplications\Domain\Repository\PostingRepository::class); + protected ApplicationRepository $applicationRepository; + protected PostingRepository $postingRepository; - /** @var ApplicationRepository $applicationRepo */ - $applicationRepo = $objectmanager->get(ApplicationRepository::class); + public function __construct(PostingRepository $postingRepository, ApplicationRepository $applicationRepository) { + $this->postingRepository = $postingRepository; + $this->applicationRepository = $applicationRepository; + } - $postings = $postingRepo->findAllIncludingHiddenAndDeleted(); + public function getChartData(): array + { + $postings = $this->postingRepository->findAllIncludingHiddenAndDeleted(); $data = []; /** @var Posting $posting */ foreach ($postings as $posting) { - $applicationCount = $applicationRepo->findByPostingIncludingHiddenAndDeleted($posting->getUid())->count(); + $applicationCount = $this->applicationRepository->findByPostingIncludingHiddenAndDeleted($posting->getUid())->count(); $this->labels[] = $posting->getTitle(); $data[] = $applicationCount; } diff --git a/Classes/Widgets/Provider/BackendModuleButtonProvider.php b/Classes/Widgets/Provider/BackendModuleButtonProvider.php index de558fa..1cc5f68 100644 --- a/Classes/Widgets/Provider/BackendModuleButtonProvider.php +++ b/Classes/Widgets/Provider/BackendModuleButtonProvider.php @@ -24,6 +24,8 @@ namespace ITX\Jobapplications\Widgets\Provider; + use TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider; + use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; use TYPO3\CMS\Backend\Routing\UriBuilder; @@ -32,16 +34,14 @@ * * @package ITX\Jobapplications\Widgets\Provider */ - class BackendModuleButtonProvider extends \TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider + class BackendModuleButtonProvider extends ButtonProvider { /** * BackendModuleButtonProvider constructor. * - * @param string $title - * @param string $link * @param string $target */ - public function __construct(string $title, string $link, string $target = '') + public function __construct(string $target = '') { if (!$GLOBALS['BE_USER']->check('modules', 'web_JobapplicationsBackend')) { @@ -49,7 +49,7 @@ public function __construct(string $title, string $link, string $target = '') } /** @var UriBuilder $uriBuilder */ - $uriBuilder = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(UriBuilder::class); + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); try { diff --git a/Classes/Widgets/Provider/PostingsActiveProvider.php b/Classes/Widgets/Provider/PostingsActiveProvider.php index 0c59633..8dcf52e 100644 --- a/Classes/Widgets/Provider/PostingsActiveProvider.php +++ b/Classes/Widgets/Provider/PostingsActiveProvider.php @@ -24,6 +24,7 @@ namespace ITX\Jobapplications\Widgets\Provider; + use ITX\Jobapplications\Domain\Repository\PostingRepository; use TYPO3\CMS\Dashboard\Widgets\NumberWithIconDataProviderInterface; /** @@ -33,14 +34,15 @@ */ class PostingsActiveProvider implements NumberWithIconDataProviderInterface { - public function getNumber(): int - { - /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectmanager */ - $objectmanager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class); + protected PostingRepository $postingRepository; - /** @var \ITX\Jobapplications\Domain\Repository\PostingRepository $postingRepo */ - $postingRepo = $objectmanager->get(\ITX\Jobapplications\Domain\Repository\PostingRepository::class); + public function __construct(PostingRepository $postingRepository) + { + $this->postingRepository = $postingRepository; + } - return $postingRepo->findAllIgnoreStoragePage()->count(); + public function getNumber(): int + { + return $this->postingRepository->findAllIgnoreStoragePage()->count(); } } \ No newline at end of file diff --git a/Configuration/FlexForms/frontend.xml b/Configuration/FlexForms/frontend.xml index b825d26..6e157bf 100644 --- a/Configuration/FlexForms/frontend.xml +++ b/Configuration/FlexForms/frontend.xml @@ -28,6 +28,17 @@ + + + + + input + 10 + trim,int,required + 9 + + + diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 283dd83..b294d1e 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -1,13 +1,36 @@ -# Variant 1, widget identifier as attribute services: + _defaults: + autowire: true + autoconfigure: true + public: false + + ITX\Jobapplications\: + resource: '../Classes/*' + exclude: + - '../Classes/Domain/Model/*' + + ITX\Jobapplications\Command\AnonymizeApplicationsCommand: + tags: + - name: 'console.command' + command: 'jobapplications:anonymizeApplicationsCommand' + description: 'This command anonymizes applications and deletes the application''s files.' + # not required, defaults to false + hidden: false + + ITX\Jobapplications\Command\CleanUpApplicationsCommand: + tags: + - name: 'console.command' + command: 'jobapplications:cleanupApplicationsCommand' + description: 'This command deletes applications and deletes the application''s files.' + # not required, defaults to false + hidden: false + ITX\Jobapplications\Widgets\Provider\PostingsActiveProvider: ITX\Jobapplications\Widgets\Provider\ApplicationsPerPostingBarChartProvider: ITX\Jobapplications\Widgets\Provider\BackendModuleButtonProvider: arguments: - $title: '' - $link: '' $target: '' dashboard.widget.postingsActive: diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index 06d9b24..5a3ddc4 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -1,5 +1,5 @@ 'first_name,last_name,email,phone,address_street_and_number,address_addition,address_city,address_country,salary_expectation', 'iconfile' => 'EXT:jobapplications/Resources/Public/Icons/Extension.svg' ], - 'interface' => [ - 'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, salutation, first_name, last_name, email, phone, address_street_and_number, address_addition, address_post_code, address_city, address_country, salary_expectation, message, earliest_date_of_joining, files, cv, cover_letter, testimonials, other_files, privacy_agreement, posting, archived, status', - ], 'types' => [ - '1' => ['showitem' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, salutation, first_name, last_name, email, phone, address_street_and_number, address_addition, address_post_code, address_city, address_country, salary_expectation, message ,earliest_date_of_joining, files, cv, cover_letter, testimonials, other_files, privacy_agreement, posting, archived, status, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access, starttime, endtime'], + '1' => ['showitem' => 'sys_language_uid,l10n_parent,l10n_diffsource,hidden,salutation,first_name,last_name,email,phone,address_street_and_number,address_addition,address_post_code,address_city,address_country,salary_expectation,message,earliest_date_of_joining,files,cv,cover_letter,testimonials,other_files,privacy_agreement,posting,archived,status,--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access,starttime,endtime'], ], 'columns' => [ 'crdate' => [ @@ -40,22 +37,11 @@ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'special' => 'languages', - 'items' => [ - [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', - -1, - 'flags-multiple' - ] - ], - 'default' => 0, + 'type' => 'language' ], ], 'l10n_parent' => [ 'displayCond' => 'FIELD:sys_language_uid:>:0', - 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', 'config' => [ 'type' => 'select', @@ -267,32 +253,32 @@ 'types' => [ '0' => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ] ] @@ -316,32 +302,32 @@ 'types' => [ '0' => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ] ] @@ -365,32 +351,32 @@ 'types' => [ '0' => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ] ] @@ -414,32 +400,32 @@ 'types' => [ '0' => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ] ], @@ -463,32 +449,32 @@ 'types' => [ '0' => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ], \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [ 'showitem' => ' - --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, --palette--;;filePalette' ] ] @@ -505,7 +491,7 @@ 'type' => 'check', 'items' => [ '1' => [ - '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' + '0' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enabled' ] ], 'default' => 0, @@ -529,7 +515,7 @@ 'type' => 'check', 'items' => [ '1' => [ - '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' + '0' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enabled' ] ], 'default' => 0, @@ -545,7 +531,14 @@ 'minitems' => 0, 'maxitems' => 1, ], - ] - + ], + 'anonymized' => [ + 'exclude' => true, + 'label' => 'Anonymized', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle' + ], + ], ], ]; diff --git a/Configuration/TCA/tx_jobapplications_domain_model_contact.php b/Configuration/TCA/tx_jobapplications_domain_model_contact.php index 5e2ab38..fefd4e3 100644 --- a/Configuration/TCA/tx_jobapplications_domain_model_contact.php +++ b/Configuration/TCA/tx_jobapplications_domain_model_contact.php @@ -19,33 +19,19 @@ 'searchFields' => 'last_name,email,phone,division', 'iconfile' => 'EXT:jobapplications/Resources/Public/Icons/Extension.svg' ], - 'interface' => [ - 'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, first_name, last_name, email, phone, division, photo, be_user', - ], 'types' => [ - '1' => ['showitem' => 'sys_language_uid, first_name, last_name, email, phone, division, photo, be_user, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access, hidden, l10n_parent, l10n_diffsource, starttime, endtime'], + '1' => ['showitem' => 'sys_language_uid,first_name,last_name,email,phone,division,photo,be_user,--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access,hidden,l10n_parent,l10n_diffsource,starttime,endtime'], ], 'columns' => [ 'sys_language_uid' => [ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'special' => 'languages', - 'items' => [ - [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', - -1, - 'flags-multiple' - ] - ], - 'default' => 0, + 'type' => 'language' ], ], 'l10n_parent' => [ 'displayCond' => 'FIELD:sys_language_uid:>:0', - 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', 'config' => [ 'type' => 'select', @@ -173,7 +159,8 @@ 'showAllLocalizationLink' => true, 'showSynchronizationLink' => true ], - 'foreign_types' => [ + 'maxitems' => 1, + 'overrideChildTca' => ['types' => [ '0' => [ 'showitem' => ' --palette--;;imageoverlayPalette, @@ -204,8 +191,7 @@ --palette--;;imageoverlayPalette, --palette--;;filePalette' ] - ], - 'maxitems' => 1 + ]] ], $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ), diff --git a/Configuration/TCA/tx_jobapplications_domain_model_location.php b/Configuration/TCA/tx_jobapplications_domain_model_location.php index 3a88498..52352a2 100644 --- a/Configuration/TCA/tx_jobapplications_domain_model_location.php +++ b/Configuration/TCA/tx_jobapplications_domain_model_location.php @@ -19,33 +19,19 @@ 'searchFields' => 'name,address,latitude,londitude', 'iconfile' => 'EXT:jobapplications/Resources/Public/Icons/Extension.svg' ], - 'interface' => [ - 'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, name, address_street_and_number, address_addition, address_post_code, address_city, address_country, latitude, londitude', - ], 'types' => [ - '1' => ['showitem' => 'name, address_street_and_number, address_addition, address_post_code, address_city, address_country, latitude, londitude, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access, sys_language_uid, hidden, starttime, endtime, l10n_parent, l10n_diffsource'], + '1' => ['showitem' => 'name,address_street_and_number,address_addition,address_post_code,address_city,address_country,latitude,londitude,--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access,sys_language_uid,hidden,starttime,endtime,l10n_parent,l10n_diffsource'], ], 'columns' => [ 'sys_language_uid' => [ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'special' => 'languages', - 'items' => [ - [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', - -1, - 'flags-multiple' - ] - ], - 'default' => 0, + 'type' => 'language' ], ], 'l10n_parent' => [ 'displayCond' => 'FIELD:sys_language_uid:>:0', - 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', 'config' => [ 'type' => 'select', diff --git a/Configuration/TCA/tx_jobapplications_domain_model_posting.php b/Configuration/TCA/tx_jobapplications_domain_model_posting.php index ec17ec8..e21ffbf 100644 --- a/Configuration/TCA/tx_jobapplications_domain_model_posting.php +++ b/Configuration/TCA/tx_jobapplications_domain_model_posting.php @@ -22,30 +22,16 @@ 'searchFields' => 'title,career_level,division,employment_type,terms_of_employment,company_description,job_description,role_description,skill_requirements,benefits,base_salary,required_documents,company_information', 'iconfile' => 'EXT:jobapplications/Resources/Public/Icons/Extension.svg' ], - 'interface' => [ - 'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, starttime, endtime, title, date_posted, career_level, division, employment_type, terms_of_employment, company_description, job_description, role_description, skill_requirements, benefits, base_salary, required_documents, company_information, detail_view_image, list_view_image, location, contact', - ], 'columns' => [ 'sys_language_uid' => [ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'special' => 'languages', - 'items' => [ - [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', - -1, - 'flags-multiple' - ] - ], - 'default' => 0, + 'type' => 'language' ], ], 'l10n_parent' => [ 'displayCond' => 'FIELD:sys_language_uid:>:0', - 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', 'config' => [ 'type' => 'select', @@ -331,7 +317,8 @@ 'showAllLocalizationLink' => true, 'showSynchronizationLink' => true ], - 'foreign_types' => [ + 'maxitems' => 1, + 'overrideChildTca' => ['types' => [ '0' => [ 'showitem' => ' --palette--;;imageoverlayPalette, @@ -362,8 +349,7 @@ --palette--;;imageoverlayPalette, --palette--;;filePalette' ] - ], - 'maxitems' => 1 + ]] ], $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ), @@ -381,7 +367,8 @@ 'showAllLocalizationLink' => true, 'showSynchronizationLink' => true ], - 'foreign_types' => [ + 'maxitems' => 1, + 'overrideChildTca' => ['types' => [ '0' => [ 'showitem' => ' --palette--;;imageoverlayPalette, @@ -412,20 +399,22 @@ --palette--;;imageoverlayPalette, --palette--;;filePalette' ] - ], - 'maxitems' => 1 + ]] ], $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ), ], - 'location' => [ + 'locations' => [ 'exclude' => true, - 'label' => 'LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.location', + 'label' => 'LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.locations', 'config' => [ 'type' => 'select', - 'renderType' => 'selectSingle', + 'renderType' => 'selectMultipleSideBySide', 'foreign_table' => 'tx_jobapplications_domain_model_location', - 'maxitems' => 1 + 'foreign_table_where' => 'tx_jobapplications_domain_model_location.sys_language_uid IN (0,-1) ORDER BY tx_jobapplications_domain_model_location.name ASC', + 'MM' => 'tx_jobapplications_postings_locations_mm', + 'size' => 3, + 'autoSizeMax' => 5, ], ], 'contact' => [ @@ -437,6 +426,7 @@ 'foreign_table' => 'tx_jobapplications_domain_model_contact', 'minitems' => 0, 'maxitems' => 1, + 'allowNonIdValues' => true ], ], 'slug' => [ @@ -459,36 +449,24 @@ ], 'types' => [ '1' => [ - 'showitem' => ' - --palette--;;mainInfo, - --palette--;;relations, - --palette--;;dates, - base_salary, - --palette--;;circumstances, - --div--;LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.title.advanced, - --palette--;;general, - --div--;LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.title.texts, - company_description, job_description, role_description, skill_requirements, benefits, - required_documents, company_information, - --div--;LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.title.images, - --palette--;;images' + 'showitem' => '--palette--;;mainInfo,--palette--;;relations,--palette--;;dates,--palette--;;circumstances,--div--;LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.title.advanced,--palette--;;general,--div--;LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.title.texts,company_description,job_description,role_description,skill_requirements,benefits,required_documents,company_information,--div--;LLL:EXT:jobapplications/Resources/Private/Language/locallang_db.xlf:tx_jobapplications_domain_model_posting.title.images,--palette--;;images' ], ], 'palettes' => [ 'general' => [ - 'showitem' => 'sys_language_uid, hidden, slug, l10n_parent, l10n_diffsource', + 'showitem' => 'sys_language_uid, hidden, --linebreak--, slug, l10n_parent, l10n_diffsource', ], 'mainInfo' => [ 'showitem' => 'title, division' ], 'circumstances' => [ - 'showitem' => 'career_level, employment_type, terms_of_employment' + 'showitem' => 'base_salary, career_level, --linebreak--, employment_type, terms_of_employment' ], 'dates' => [ 'showitem' => 'date_posted, starttime ,endtime' ], 'relations' => [ - 'showitem' => 'location, contact' + 'showitem' => 'contact, --linebreak--, locations,' ], 'images' => [ 'showitem' => 'detail_view_image, list_view_image' diff --git a/Configuration/TCA/tx_jobapplications_domain_model_status.php b/Configuration/TCA/tx_jobapplications_domain_model_status.php index c8af110..de31b7e 100644 --- a/Configuration/TCA/tx_jobapplications_domain_model_status.php +++ b/Configuration/TCA/tx_jobapplications_domain_model_status.php @@ -20,11 +20,8 @@ 'searchFields' => 'name, followers', 'iconfile' => 'EXT:jobapplications/Resources/Public/Icons/Extension.svg' ], - 'interface' => [ - 'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, name, is_end_status, is_new_status, followers', - ], 'types' => [ - '1' => ['showitem' => 'name, is_end_status, is_new_status, followers, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access, hidden, sys_language_uid, starttime, endtime, l10n_parent, l10n_diffsource'], + '1' => ['showitem' => 'name,is_end_status,is_new_status,followers,--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access,hidden,sys_language_uid,starttime,endtime,l10n_parent,l10n_diffsource'], ], 'columns' => [ 'crdate' => [ @@ -41,22 +38,11 @@ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'config' => [ - 'type' => 'select', - 'renderType' => 'selectSingle', - 'special' => 'languages', - 'items' => [ - [ - 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', - -1, - 'flags-multiple' - ] - ], - 'default' => 0, + 'type' => 'lanuage' ], ], 'l10n_parent' => [ 'displayCond' => 'FIELD:sys_language_uid:>:0', - 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', 'config' => [ 'type' => 'select', @@ -141,9 +127,7 @@ 'config' => [ 'type' => 'check', 'items' => [ - '1' => [ - '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' - ] + ['LLL:EXT:lang/locallang_core.xlf:labels.enabled', ''] ], 'default' => 0, ] @@ -154,9 +138,7 @@ 'config' => [ 'type' => 'check', 'items' => [ - '1' => [ - '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' - ] + ['LLL:EXT:lang/locallang_core.xlf:labels.enabled', ''] ], 'default' => 0, ] diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript index 7026021..86bb088 100644 --- a/Configuration/TypoScript/constants.typoscript +++ b/Configuration/TypoScript/constants.typoscript @@ -8,12 +8,9 @@ plugin.tx_jobapplications.settings { # cat=plugin.tx_jobapplications; type=boolean; label=LLL:EXT:jobapplications/Resources/Private/Language/locallang_backend.xlf:legacy_upload legacy_upload = 1 - # cat=plugin.tx_jobapplications; type=options[html,text,both]; label=Send Email as HTML or Text or Both + # cat=plugin.tx_jobapplications; type=options[html,plain,both]; label=Send Email as HTML or Text or Both emailContentType = both - # cat=plugin.tx_jobapplications; type=int; label=Maximum number of items on one page - maxItemsOnPage = 9 - # cat=plugin.tx_jobapplications; type=string; label=Default contact (mail address) for unsolicited application form mail defaultContactMailAddress = address@address.de # cat=plugin.tx_jobapplications; type=string; label=Default contact (first name) for unsolicited application form mail diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 3274577..af182bc 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -1,11 +1,3 @@ -plugin.tx_jobapplications.settings.enableGoogleJobs = {$plugin.tx_jobapplications.settings.enableGoogleJobs} -plugin.tx_jobapplications.settings.emailContentType = {$plugin.tx_jobapplications.settings.emailContentType} -plugin.tx_jobapplications.settings.legacy_upload = {$plugin.tx_jobapplications.settings.legacy_upload} -plugin.tx_jobapplications.settings.defaultContactMailAddress = {$plugin.tx_jobapplications.settings.defaultContactMailAddress} -plugin.tx_jobapplications.settings.defaultContactFirstName = {$plugin.tx_jobapplications.settings.defaultContactFirstName} -plugin.tx_jobapplications.settings.defaultContactLastName = {$plugin.tx_jobapplications.settings.defaultContactLastName} -plugin.tx_jobapplications.settings.maxItemsOnPage = {$plugin.tx_jobapplications.settings.maxItemsOnPage} - plugin.tx_jobapplications { view { templateRootPaths.0 = EXT:{extension.shortExtensionKey}/Resources/Private/Templates/ @@ -37,6 +29,12 @@ plugin.tx_jobapplications { settings { fileStorage = {$plugin.tx_jobapplications.settings.fileStorage} + enableGoogleJobs = {$plugin.tx_jobapplications.settings.enableGoogleJobs} + emailContentType = {$plugin.tx_jobapplications.settings.emailContentType} + legacy_upload = {$plugin.tx_jobapplications.settings.legacy_upload} + defaultContactMailAddress = {$plugin.tx_jobapplications.settings.defaultContactMailAddress} + defaultContactFirstName = {$plugin.tx_jobapplications.settings.defaultContactFirstName} + defaultContactLastName = {$plugin.tx_jobapplications.settings.defaultContactLastName} list { ordering { @@ -62,9 +60,9 @@ plugin.tx_jobapplications { relationType = contains } - location { - relation = location - relationType = equals + locations { + relation = locations + relationType = contains } } } diff --git a/Documentation/ChangeLog/Index.rst b/Documentation/ChangeLog/Index.rst index a380656..fa18787 100644 --- a/Documentation/ChangeLog/Index.rst +++ b/Documentation/ChangeLog/Index.rst @@ -11,6 +11,27 @@ Changelog ========= +2.0.0 - [BREAKING] TYPO3 11 support and new features +---------------------------------------------------- +* [BREAKING] added static coding analysis via phpstan +* [FEATURE] upgrade to TYPO3 11. Removed support for older versions +* [BREAKING] removed vhs dependency + * make sure to compare the templates, if you have overrides +* [FIX] fixed position of traits +* [BREAKING] fixed filter and reworked pagination on postings list + * make sure to compare the templates and css, if you have overrides +* [FEATURE] added configurable page size via flexform to postings plugin +* [FIX] refactoring of google jobs structured data +* [BREAKING] replaced signal slots in favor of events + * please change your implementations if you used signal slots previously +* [FEATURE] use new language configuration in tca +* [FIX]: fixed problems of postings filter caching in multilanguage setup +* [BREAKING]: postings can have multiple locations now + * make sure to use the upgrade wizard to migrate entries in the location field to the new locations field and its corresponding mm table + * make sure to compare the templates, if you have overrides +* [FEATURE]: redesigned posting tca (more place for slugs, etc) +* [FIX]: fixed error where postings could not be translated when no contact was selected + 1.0.7 - Bugfixes ----------------- * [BUGFIX] fixed plugin text about available placeholder in mail text diff --git a/Documentation/Developer/Index.rst b/Documentation/Developer/Index.rst index 49d39c7..aca3c41 100644 --- a/Documentation/Developer/Index.rst +++ b/Documentation/Developer/Index.rst @@ -9,62 +9,58 @@ For Developers .. _developer-signal-slots: -Signal Slots -============ -There are Signal Slots implemented which mainly happen before postings or applications are being assigned to the view. +Events +====== +There are a few events provided which mainly happen before postings or applications are being assigned to the view. -You can simply find them by looking through the code. If you think you need more slots than are already provided feel free to +You can simply find them by taking a look into ITX\Jobapplications\Event. If you think you need more events than are already provided feel free to contact us via GitHub or :ref:`E-Mail `. Custom Filters ============== If you want to have custom filters you can do that by performing three steps. - #. Configure every filter you want in TypoScript settings, like the default config: - .. code-block:: php +#. Configure every filter you want in TypoScript settings, like the default config: + .. code-block:: php - filter { - repositoryConfiguration { - division { // Relation name: This is the name you should assign the form element property and the constraint property - relation = division // Define the relation a la Repository query e.g.: posting.contact.email - relationType = equals // choose equals, contains or in. This depends on the given relation - } + filter { + repositoryConfiguration { + division { // Relation name: This is the name you should assign the form element property and the constraint property + relation = division // Define the relation a la Repository query e.g.: posting.contact.email + relationType = equals // choose equals, contains or in. This depends on the given relation + } - careerLevel { - relation = careerLevel - relationType = equals - } + careerLevel { + relation = careerLevel + relationType = equals + } - employmentType { - relation = employmentType - relationType = contains - } + employmentType { + relation = employmentType + relationType = contains + } - location { - relation = location - relationType = contains - } - } - } + location { + relation = location + relationType = contains + } + } + } +#. Override the Constraint model, add your custom properties with getters and setters +#. Override the PostingController, by extending the Default one and override the function getFilterOptions() + This function defines what the filterOptions are. You can write queries to find the options automatically or supply constant ones. + The data generated by this function gets cached, so perfomance wont be a problem. Also remember to clear all caches after changing something in the filter options. + .. code-block:: php - #. Override the Constraint model, add your custom properties with getters and setters - #. Override the PostingController, by extending the Default one and override the function getFilterOptions() - This function defines what the filterOptions are. You can write queries to find the options automatically or supply constant ones. - The data generated by this function gets cached, so perfomance wont be a problem. Also remember to clear all caches after changing something - in the filter options. - .. code-block:: php + // Function returns an array, matching the configured filters from above + public function getFilterOptions($categories): array + { + return [ + 'division' => $this->postingRepository->findAllDivisions($categories), + 'careerLevel' => $this->postingRepository->findAllCareerLevels($categories), + 'employmentType' => $this->postingRepository->findAllEmploymentTypes($categories), + 'location' => $this->locationRepository->findAll($categories)->toArray(), + ]; + } - // Function returns an array, matching the configured filters from above - public function getFilterOptions($categories): array - { - return [ - 'division' => $this->postingRepository->findAllDivisions($categories), - 'careerLevel' => $this->postingRepository->findAllCareerLevels($categories), - 'employmentType' => $this->postingRepository->findAllEmploymentTypes($categories), - 'location' => $this->locationRepository->findAll($categories)->toArray(), - ]; - } - - #. Last but not least you have to edit the frontend template, so it includes your new filters. You can take the default filters as - an example, but basically you have access to the filter options and the user selected filter options in variable called constraint. - The controller is also preconfigured to work with both single- and multiselects. \ No newline at end of file +#. Last but not least you have to edit the frontend template, so it includes your new filters. You can take the default filters as an example, but basically you have access to the filter options and the user selected filter options in variable called constraint. The controller is also preconfigured to work with both single- and multiselects. diff --git a/Documentation/Installation/Index.rst b/Documentation/Installation/Index.rst index aa50408..70887cf 100644 --- a/Documentation/Installation/Index.rst +++ b/Documentation/Installation/Index.rst @@ -18,6 +18,7 @@ You can install it via composer by typing composer require itx/jobapplications or the Extension Manager in the Backend or install it locally for example directly from `Github <|project_repository|>`__. +Please make sure to switch to the Maintainance tool and push the buttons *analyse database structure* and *flush cache*. Include Static Typoscript ------------------------- diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst index 3c0b85e..503cfe7 100644 --- a/Documentation/Introduction/Index.rst +++ b/Documentation/Introduction/Index.rst @@ -31,7 +31,7 @@ Manage job postings * write your own page title for the detail page by using placeholders * use **frontend filters** for job posting attributes "Division", "Career Level", "Emplyoment Type" and "Location" * time at which date the posting should go live and possibly end - * support for **typo3 categories** + * support for **TYPO3 categories** * OpenGraph data automatically populated * Structured data for **Google Jobs integration** * **Google Indexing API Implementation** for Google Jobs: Sends crawl requests automatically when editing a job posting, so Google Jobs always has the latest data @@ -41,7 +41,7 @@ Application Form * **fully fledged application form** with standard fields like name, email, phone, address as well as optional fields like "Salary Expectation", "Earliest Date of Joining" and a "Message" field. * supports pdf file uploads - * have a four single file upload fields or one multi file upload field + * have four single file upload fields or one multi file upload field * privacy agreement checkbox, which links to your privacy agreement page * forward the applicant to a success page with a customized message which again supports placeholders for the applicants name diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg index bc4a029..aadcfcb 100644 --- a/Documentation/Settings.cfg +++ b/Documentation/Settings.cfg @@ -27,7 +27,7 @@ project = Jobapplications Extension # ... (recommended) version, displayed next to title (desktop) and in
-
    - +
  • @@ -113,7 +112,8 @@

-
+ +
diff --git a/Resources/Private/Backend/Templates/Backend/ShowApplication.html b/Resources/Private/Backend/Templates/Backend/ShowApplication.html index 85739de..10ba6ba 100644 --- a/Resources/Private/Backend/Templates/Backend/ShowApplication.html +++ b/Resources/Private/Backend/Templates/Backend/ShowApplication.html @@ -123,7 +123,7 @@
- + pdf download @@ -131,7 +131,7 @@
- + pdf download @@ -139,7 +139,7 @@
- + pdf download @@ -147,7 +147,7 @@
- + pdf download @@ -155,7 +155,7 @@
- + pdf {file.originalResource.name} download diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf index 6e7c2c8..da82cda 100644 --- a/Resources/Private/Language/de.locallang.xlf +++ b/Resources/Private/Language/de.locallang.xlf @@ -79,9 +79,9 @@ List View Image Bild Listenansicht - - Location - Standort + + Locations + Standorte Contact diff --git a/Resources/Private/Language/de.locallang_backend.xlf b/Resources/Private/Language/de.locallang_backend.xlf index 3025ec6..4c85d57 100644 --- a/Resources/Private/Language/de.locallang_backend.xlf +++ b/Resources/Private/Language/de.locallang_backend.xlf @@ -107,6 +107,10 @@ Maximum message number of characters: Maximale Anzahl an Zeichen in Nachricht Feld: + + Number of postings per page: + Anzahl Stellenausschreibungen pro Seite: + Show message field? Zeige Nachricht-Feld? diff --git a/Resources/Private/Language/de.locallang_db.xlf b/Resources/Private/Language/de.locallang_db.xlf index e3794fb..3605bf6 100644 --- a/Resources/Private/Language/de.locallang_db.xlf +++ b/Resources/Private/Language/de.locallang_db.xlf @@ -75,9 +75,9 @@ List View Image Bild Listenansicht - - Location - Standort + + Locations + Standorte Contact diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 3aea9ff..6dd15d5 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -60,8 +60,8 @@ List View Image - - Location + + Locations Contact diff --git a/Resources/Private/Language/locallang_backend.xlf b/Resources/Private/Language/locallang_backend.xlf index 04d36ba..76755b4 100644 --- a/Resources/Private/Language/locallang_backend.xlf +++ b/Resources/Private/Language/locallang_backend.xlf @@ -93,6 +93,9 @@ Maximum message number of characters: + + Number of postings per page: + Enter the message that will be shown to the applicant after submitting the application diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf index ffcd058..a4e9e98 100644 --- a/Resources/Private/Language/locallang_db.xlf +++ b/Resources/Private/Language/locallang_db.xlf @@ -57,8 +57,8 @@ List View Image - - Location + + Locations Contact diff --git a/Resources/Private/Partials/Pagination.html b/Resources/Private/Partials/Pagination.html new file mode 100644 index 0000000..341bd00 --- /dev/null +++ b/Resources/Private/Partials/Pagination.html @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/Resources/Private/Partials/Posting/ListItem.html b/Resources/Private/Partials/Posting/ListItem.html index 2bb7646..fbe6c1d 100644 --- a/Resources/Private/Partials/Posting/ListItem.html +++ b/Resources/Private/Partials/Posting/ListItem.html @@ -20,11 +20,14 @@
- + :
- {posting.location.name} + + {location.name}{f:if(condition: '!{iterator.isLast}', then: ', ')} + +
diff --git a/Resources/Private/Partials/Posting/MetaTags.html b/Resources/Private/Partials/Posting/MetaTags.html index 434460f..b775170 100644 --- a/Resources/Private/Partials/Posting/MetaTags.html +++ b/Resources/Private/Partials/Posting/MetaTags.html @@ -1,10 +1,11 @@ + xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" + xmlns:jobs="http://typo3.org/ns/ITX/Jobapplications/ViewHelpers"> - - - + + + - + \ No newline at end of file diff --git a/Resources/Private/Partials/Posting/Properties.html b/Resources/Private/Partials/Posting/Properties.html index dafe3b5..a3626be 100644 --- a/Resources/Private/Partials/Posting/Properties.html +++ b/Resources/Private/Partials/Posting/Properties.html @@ -159,25 +159,27 @@
- +
- +
-

- - {posting.location.name}
-
- {posting.location.addressStreetAndNumber}
- - {posting.location.addressAddition}
-
- {posting.location.addressPostCode} {posting.location.addressCity}
- {posting.location.addressCountry} -

+ +

+ + {location.name}
+
+ {location.addressStreetAndNumber}
+ + {location.addressAddition}
+
+ {location.addressPostCode} {location.addressCity}
+ {location.addressCountry} +

+
diff --git a/Resources/Private/Templates/Application/New.html b/Resources/Private/Templates/Application/New.html index c8c7bf9..e484773 100644 --- a/Resources/Private/Templates/Application/New.html +++ b/Resources/Private/Templates/Application/New.html @@ -1,12 +1,10 @@ - + - - - - - + + +
diff --git a/Resources/Private/Templates/Application/Success.html b/Resources/Private/Templates/Application/Success.html index b28f8bb..bdd427f 100644 --- a/Resources/Private/Templates/Application/Success.html +++ b/Resources/Private/Templates/Application/Success.html @@ -1,21 +1,20 @@ + xmlns:jobs="http://typo3.org/ns/ITX/Jobapplications/ViewHelpers"> - - - + + + - + - + diff --git a/Resources/Private/Templates/Mail/JobsApplicantMail.html b/Resources/Private/Templates/Mail/JobsApplicantMail.html index 63003b0..4431f94 100644 --- a/Resources/Private/Templates/Mail/JobsApplicantMail.html +++ b/Resources/Private/Templates/Mail/JobsApplicantMail.html @@ -1,7 +1,6 @@ + xmlns:jobs="http://typo3.org/ns/ITX/Jobapplications/ViewHelpers"> @@ -14,7 +13,7 @@ - + {titleBar} @@ -27,8 +26,8 @@ - - + + @@ -38,9 +37,9 @@ - + - + {msg} diff --git a/Resources/Private/Templates/Mail/JobsApplicantMail.txt b/Resources/Private/Templates/Mail/JobsApplicantMail.txt index dc3af1f..48011f6 100644 --- a/Resources/Private/Templates/Mail/JobsApplicantMail.txt +++ b/Resources/Private/Templates/Mail/JobsApplicantMail.txt @@ -6,9 +6,9 @@ - - - + + + @@ -17,8 +17,8 @@ - - + + {titleBar} \ No newline at end of file diff --git a/Resources/Private/Templates/Posting/List.html b/Resources/Private/Templates/Posting/List.html index 22ba988..90f9670 100644 --- a/Resources/Private/Templates/Posting/List.html +++ b/Resources/Private/Templates/Posting/List.html @@ -3,11 +3,9 @@ - - - - - + + +
@@ -25,9 +23,9 @@

- + - {division} + {division} @@ -37,9 +35,9 @@

- + - {careerLevel} + {careerLevel} @@ -51,20 +49,20 @@

- +

-
-