custom/plugins/CogiRecruiting/src/Storefront/Controller/RecruitingController.php line 187

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Cogi\CogiRecruiting\Storefront\Controller;
  3. use Cogi\CogiRecruiting\CogiRecruiting;
  4. use Cogi\CogiRecruiting\Core\Content\Application\ApplicationEntity;
  5. use Cogi\CogiRecruiting\Core\Content\Job\JobEntity;
  6. use Cogi\CogiRecruiting\Core\StorefrontMediaUploader;
  7. use Cogi\CogiTicketSystem\CogiTicketSystem;
  8. use Cogi\CogiTicketSystem\Core\Ticket\TicketEntity;
  9. use Shopware\Core\Checkout\Customer\CustomerEntity;
  10. use Shopware\Core\Content\Category\Exception\CategoryNotFoundException;
  11. use Shopware\Core\Content\Mail\Service\AbstractMailService;
  12. use Shopware\Core\Content\Mail\Service\MailService;
  13. use Shopware\Core\Content\MailTemplate\MailTemplateEntity;
  14. use Shopware\Core\Content\Media\Exception\IllegalFileNameException;
  15. use Shopware\Core\Content\Media\Exception\UploadException;
  16. use Shopware\Core\Content\Media\MediaCollection;
  17. use Shopware\Core\Content\Media\MediaEntity;
  18. use Shopware\Core\Framework\Api\Context\SystemSource;
  19. use Shopware\Core\Framework\Context;
  20. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  21. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  22. use Symfony\Component\HttpFoundation\Session\Session;
  23. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  25. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  26. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  27. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  28. use Shopware\Core\Framework\Routing\Exception\MissingRequestParameterException;
  29. use Shopware\Core\Framework\Uuid\Uuid;
  30. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  31. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  32. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  33. use Shopware\Core\System\SystemConfig\SystemConfigService;
  34. use Shopware\Storefront\Controller\StorefrontController;
  35. use Shopware\Storefront\Page\GenericPageLoader;
  36. use Symfony\Component\HttpFoundation\RedirectResponse;
  37. use Symfony\Component\HttpFoundation\Request;
  38. use Symfony\Component\HttpFoundation\Response;
  39. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  40. use Symfony\Component\Routing\Annotation\Route;
  41. /**
  42.  * @RouteScope(scopes={"storefront"})
  43.  */
  44. class RecruitingController extends StorefrontController {
  45.     private $genericPageLoader;
  46.     /**
  47.      * @var EntityRepository
  48.      */
  49.     private $categoryRepository;
  50.     /**
  51.      * @var SystemConfigService
  52.      */
  53.     private $systemConfigService;
  54.     /**
  55.      * @var EntityRepository
  56.      */
  57.     private $jobRepository;
  58.     /**
  59.      * @var EntityRepository
  60.      */
  61.     private $applicationRepository;
  62.     /**
  63.      * @var EntityRepository
  64.      */
  65.     private $applicationElinkRepository;
  66.     /**
  67.      * @var EntityRepository
  68.      */
  69.     private $applicationCommentRepository;
  70.     /**
  71.      * @var EntityRepository
  72.      */
  73.     private $mailTemplateRepository;
  74.     /**
  75.      * @var EntityRepositoryInterface
  76.      */
  77.     private $mediaRepository;
  78.     /**
  79.      * @var StorefrontMediaUploader
  80.      */
  81.     private $mediaUploader;
  82.     /**
  83.      * @var AbstractMailService $mailService
  84.      */
  85.     private AbstractMailService $mailService;
  86.     /**
  87.      * HelpcenterOverviewController constructor.
  88.      * @param GenericPageLoader $genericPageLoader
  89.      * @param SystemConfigService $systemConfigService
  90.      * @param EntityRepository $categoryRepository
  91.      * @param EntityRepository $jobRepository
  92.      * @param EntityRepository $applicationRepository
  93.      * @param EntityRepository $applicationElinkRepository
  94.      * @param EntityRepository $applicationCommentRepository
  95.      * @param EntityRepositoryInterface $mediaRepository
  96.      * @param EntityRepository $jobEventRepository
  97.      * @param StorefrontMediaUploader $mediaUploader
  98.      * @param EntityRepository $mailTemplateRepository
  99.      * @param AbstractMailService $mailService
  100.      */
  101.     public function __construct(
  102.         GenericPageLoader         $genericPageLoader,
  103.         SystemConfigService       $systemConfigService,
  104.         EntityRepository $categoryRepository,
  105.         EntityRepository $jobRepository,
  106.         EntityRepository $applicationRepository,
  107.         EntityRepository $applicationElinkRepository,
  108.         EntityRepository $applicationCommentRepository,
  109.         EntityRepositoryInterface $mediaRepository,
  110.         EntityRepository $jobEventRepository,
  111.         StorefrontMediaUploader   $mediaUploader,
  112.         EntityRepository $mailTemplateRepository,
  113.         AbstractMailService               $mailService
  114.     ) {
  115.         $this->genericPageLoader $genericPageLoader;
  116.         $this->categoryRepository $categoryRepository;
  117.         $this->jobRepository $jobRepository;
  118.         $this->applicationRepository $applicationRepository;
  119.         $this->applicationElinkRepository $applicationElinkRepository;
  120.         $this->applicationCommentRepository $applicationCommentRepository;
  121.         $this->mediaRepository $mediaRepository;
  122.         $this->jobEventRepository $jobEventRepository;
  123.         $this->systemConfigService $systemConfigService;
  124.         $this->mediaUploader $mediaUploader;
  125.         $this->mailTemplateRepository $mailTemplateRepository;
  126.         $this->mailService $mailService;
  127.     }
  128.     /**
  129.      * @Route("/recruiting/{id}", name="frontend.cogirecruiting.overview", options={"seo"="true"}, methods={"GET"}, defaults={"id"=null})
  130.      * @param $id
  131.      * @param Request $request
  132.      * @param SalesChannelContext $salesChannelContext
  133.      * @param Context $context
  134.      * @return Response
  135.      * @throws CategoryNotFoundException
  136.      * @throws InconsistentCriteriaIdsException
  137.      * @throws MissingRequestParameterException
  138.      */
  139.     public function renderOverview($idRequest $requestSalesChannelContext $salesChannelContextContext $context) {
  140.         $page $this->genericPageLoader->load($request$salesChannelContext);
  141.         $criteria = new Criteria();
  142.         $criteria->addFilter(new EqualsFilter('active''1'));
  143.         $criteria->addSorting(new FieldSorting('position'FieldSorting::ASCENDING));
  144.         if ($id !== null) {
  145.             $criteria->addFilter(new EqualsFilter('categoryId'$id));
  146.         }
  147.         $jobs $this->jobRepository->search($criteria$context)->getEntities();
  148.         return $this->renderStorefront('@Storefront/storefront/page/recruiting/overview.html.twig', [
  149.             "page" => $page,
  150.             "categories" => $this->getCategories($context),
  151.             "jobs" => $jobs,
  152.             "jobsCount" => $jobs->count()
  153.         ]);
  154.     }
  155.     /**
  156.      * @Route("/recruiting/job/{id}", name="frontend.cogirecruiting.job", options={"seo"="true"}, methods={"GET"})
  157.      * @param $id
  158.      * @param Request $request
  159.      * @param SalesChannelContext $salesChannelContext
  160.      * @param Context $context
  161.      * @return Response
  162.      * @throws CategoryNotFoundException
  163.      * @throws InconsistentCriteriaIdsException
  164.      * @throws MissingRequestParameterException
  165.      */
  166.     public function renderJob($idRequest $requestSalesChannelContext $salesChannelContextContext $context) {
  167.         $page $this->genericPageLoader->load($request$salesChannelContext);
  168.         $criteria = new Criteria();
  169.         $criteria->addFilter(new EqualsFilter('active''1'), new EqualsFilter('id'$id));
  170.         $job $this->jobRepository->search($criteria$context);
  171.         $this->trackViews($context$request->getSession(), $job->first()->getId());
  172.         if ($job->count() !== 1) {
  173.             $this->addFlash('danger'$this->trans('cogi-recruiting.404'));
  174.             return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
  175.         }
  176.         $s $job->getEntities()->getElements();
  177.         /** @var JobEntity $job */
  178.         $job array_shift($s);
  179.         $mediaItems array_filter([$job->get('contactMediaId'), $job->get('previewMediaId')]);
  180.         if (count($mediaItems)) {
  181.             $media $this->mediaRepository->search(new Criteria($mediaItems), $context)->getElements();
  182.         }
  183.         $metaInformation $page->getMetaInformation();
  184.         $metaInformation->setMetaTitle($job->getTranslation('title'));
  185.         $metaInformation->setMetaDescription($job->getTranslation('shortDescription'));
  186.         $page->setMetaInformation($metaInformation);
  187.         return $this->renderStorefront("@Storefront/storefront/page/recruiting/job.html.twig", [
  188.             "page" => $page,
  189.             "job" => $job,
  190.             "media" => $media ?? []
  191.         ]);
  192.     }
  193.     /**
  194.      * @Route("/recruiting/api/apply", name="frontend.cogirecruiting.api.apply", options={"seo"="false"}, methods={"POST"})
  195.      * @param RequestDataBag $data
  196.      * @param Request $request
  197.      * @param SalesChannelContext $salesChannelContext
  198.      * @param Context $context
  199.      * @return RedirectResponse
  200.      * @throws IllegalFileNameException
  201.      * @throws UploadException
  202.      */
  203.     public function sendApply(RequestDataBag $dataRequest $requestSalesChannelContext $salesChannelContextContext $context) {
  204.         $salesChannelId $request->get('sw-sales-channel-id');
  205.         $criteria = new Criteria();
  206.         $criteria->addFilter(new EqualsFilter('active''1'), new EqualsFilter('id'$data->get('id')))->setLimit(1);
  207.         $job $this->jobRepository->search($criteria$context);
  208.         if ($job->count() !== 1) {
  209.             $this->addFlash('danger'$this->trans('cogi-recruiting.404'));
  210.             return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
  211.         }
  212.         $s $job->getEntities()->getElements();
  213.         /** @var JobEntity $job */
  214.         $job array_shift($s);
  215.         $allowedSize $this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingUploadMaxSize') * 1e+6;
  216.         $allowedTypes explode(","$this->systemConfigService->get('CogiRecruiting.config.UploadFileTypes'));
  217.         if (!empty($request->files->get('upload'))) {
  218.             $uploadItems $request->files->get('upload');
  219.         } else {
  220.             $uploadItems = [];
  221.         }
  222.         $profileImage $request->files->get('profile-image');
  223.         if ($job->get('mediaFolderId') === null) {
  224.             $this->addFlash('danger'$this->trans('cogi-recruiting.apply.notSetup') . ' Media Folder');
  225.             return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  226.         }
  227.         if ($this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingProfilePictureRequired') && $request->files->get('profile-image') === null) {
  228.             $this->addFlash('danger'$this->trans('cogi-recruiting.apply.noProfile'));
  229.             return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  230.         }
  231.         if (count($uploadItems) > (int)$this->systemConfigService->get('CogiRecruiting.config.UploadFileLimit')) {
  232.             $this->addFlash('danger'$this->trans('cogi-recruiting.apply.fileLimit'));
  233.             return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  234.         }
  235.         if ($this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingProfilePictureRequired')) {
  236.             // upload profile picture
  237.             if (!$this->checkSize($profileImage$allowedSize))
  238.                 return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  239.             if (!$this->checkExtension($profileImage, array('jpg''png')))
  240.                 return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  241.         }
  242.         // validate uploads
  243.         foreach ($uploadItems as $item) {
  244.             // Check allowed file size
  245.             if (!$this->checkSize($item$allowedSize))
  246.                 return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  247.             if (!$this->checkExtension($item$allowedTypes))
  248.                 return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  249.         }
  250.         if ($this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingProfilePictureRequired')) {
  251.             // upload profile image
  252.             $profileImageId $this->mediaUploader->upload($profileImage$job->get('mediaFolderId'), $context::createDefaultContext());
  253.         }
  254.         // upload uploads
  255.         $mediaIds = [];
  256.         foreach ($uploadItems as $item)
  257.             $mediaIds[] = $this->mediaUploader->upload($item$job->get('mediaFolderId'), $context::createDefaultContext());
  258.         $applicationId Uuid::randomHex();
  259.         // Insert application
  260.         $this->applicationRepository->create([[
  261.             'id' => $applicationId,
  262.             'jobId' => $data->get('id'),
  263.             'prename' => $data->get('prename'),
  264.             'name' => $data->get('name'),
  265.             'email' => $data->get('email'),
  266.             'telephone' => $data->get('telephone'null),
  267.             'message' => $data->get('message'),
  268.             'files' => $mediaIds,
  269.             'profilePictureId' => $profileImageId ?? null,
  270.         ]], $context::createDefaultContext());
  271.         // get application entity
  272.         $criteria = new Criteria([$applicationId]);
  273.         $application $this->applicationRepository->search($criteria$context)->get($applicationId);
  274.         if($this->systemConfigService->get('CogiRecruiting.config.sendApplicationMailAdmin')){
  275.             $email $this->systemConfigService->get('core.basicInformation.email');
  276.             if($job->getContactMail() !== null && $job->getContactMail() !== "")
  277.                 $email $job->getContactMail();
  278.             // send mail to admin!
  279.             $recipient = [
  280.                 $email => "Admin"
  281.             ];
  282.             $this->sendEmail($salesChannelIdCogiRecruiting::MAIL_TEMPLATE_ADMIN_NEW_APPLICATION$application$job$recipient);
  283.         }
  284.         if($this->systemConfigService->get('CogiRecruiting.config.sendApplicationMailApplicant')){
  285.             // send mail to applicant
  286.             $recipient = [
  287.                 $application->email => "{$application->prename} {$application->name}"
  288.             ];
  289.             $this->sendEmail($salesChannelIdCogiRecruiting::MAIL_TEMPLATE_APPLICATION_CONFIRMATION$application$job$recipient);
  290.         }
  291.         $this->addFlash('success'$this->trans('cogi-recruiting.apply.success'));
  292.         return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
  293.     }
  294.     private function checkSize($item$allowedSize) {
  295.         if ($item->getSize() > $allowedSize) {
  296.             $this->addFlash('danger'str_replace('$1'$item->getClientOriginalName(), str_replace('$2', ($allowedSize 1e+6), $this->trans('cogi-recruiting.apply.sizeLimit'))));
  297.             return false;
  298.         }
  299.         return true;
  300.     }
  301.     private function checkExtension($item$allowedTypes) {
  302.         if (!in_array(strtolower($item->getClientOriginalExtension()), $allowedTypes)) {
  303.             $these implode(', '$allowedTypes);
  304.             $this->addFlash('danger'str_replace('$1'strtolower($item->getClientOriginalExtension()), str_replace('$2'$these$this->trans('cogi-recruiting.apply.disallowedExtension'))));
  305.             return false;
  306.         }
  307.         return true;
  308.     }
  309.     public function getCategories($context) {
  310.         $criteria = new Criteria();
  311.         $criteria->addFilter(
  312.             new EqualsFilter('active'true)
  313.         );
  314.         return $this->categoryRepository->search($criteria$context)->getEntities();
  315.     }
  316.     protected function toBytes($str) {
  317.         $str trim($str);
  318.         $last strtolower($str[strlen($str) - 1]);
  319.         $val 0;
  320.         if (is_numeric($last)) {
  321.             $val = (int)$str;
  322.         } else {
  323.             $val = (int)substr($str0, -1);
  324.         }
  325.         switch ($last) {
  326.             case 'g':
  327.             case 'G':
  328.                 $val *= 1024;
  329.             case 'm':
  330.             case 'M':
  331.                 $val *= 1024;
  332.             case 'k':
  333.             case 'K':
  334.                 $val *= 1024;
  335.         }
  336.         return $val;
  337.     }
  338.     /**
  339.      * @Route("/recruiting/elink/{elinkId}/{applicationId}", name="frontend.cogirecruiting.elink", options={"seo"="false"}, methods={"GET"})
  340.      * @param $elinkId
  341.      * @param $applicationId
  342.      * @param Request $request
  343.      * @param SalesChannelContext $salesChannelContext
  344.      * @param Context $context
  345.      * @return Response
  346.      * @throws CategoryNotFoundException
  347.      * @throws InconsistentCriteriaIdsException
  348.      * @throws MissingRequestParameterException
  349.      */
  350.     public function renderElink($elinkId$applicationIdRequest $requestSalesChannelContext $salesChannelContextContext $context) {
  351.         $page $this->genericPageLoader->load($request$salesChannelContext);
  352.         $criteria = new Criteria();
  353.         $criteria->addFilter(new EqualsFilter('id'$elinkId));
  354.         $criteria->addFilter(new EqualsFilter('applicationId'$applicationId));
  355.         $criteria->setLimit(1);
  356.         $elink $this->applicationElinkRepository->search($criteria$context);
  357.         if ($elink->count() !== 1) {
  358.             $this->addFlash('danger'$this->trans('cogi-recruiting.elink.404'));
  359.             return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
  360.         }
  361.         $elink $elink->getElements();
  362.         $elink array_shift($elink);
  363.         if ($elink->get('createdAt')->format('Y-m-d H:i:s') === null) {
  364.             $this->addFlash('danger'$this->trans('cogi-recruiting.elink.pleaseReopen'));
  365.             return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
  366.         }
  367.         $expires strtotime($elink->get('createdAt')->format('Y-m-d H:i:s')) + (3600 $elink->get('expireHours'));
  368.         if (time() > $expires) {
  369.             $this->addFlash('danger'$this->trans('cogi-recruiting.elink.expired'));
  370.             return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
  371.         }
  372.         $criteria = new Criteria();
  373.         $criteria->addFilter(new EqualsFilter('id'$applicationId));
  374.         $criteria->addAssociation('job');
  375.         $application $this->applicationRepository->search($criteria$context);
  376.         if ($application->count() !== 1) {
  377.             $this->addFlash('danger'$this->trans('cogi-recruiting.elink.404'));
  378.             return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
  379.         }
  380.         $application $application->getElements();
  381.         $application array_shift($application);
  382.         $criteria = new Criteria();
  383.         $criteria->addFilter(new EqualsFilter('applicationId'$applicationId));
  384.         $applicationComments $this->applicationCommentRepository->search($criteria$context)->getElements();
  385.         $mediaItems $application->files;
  386.         if (count($mediaItems)) {
  387.             $media $this->mediaRepository->search(new Criteria($mediaItems), $context)->getElements();
  388.         }
  389.         return $this->renderStorefront('@Storefront/storefront/page/recruiting/elink.html.twig', [
  390.             "page" => $page,
  391.             "elink" => $elink,
  392.             "application" => $application,
  393.             "applicationComments" => $applicationComments,
  394.             "expires" => $expires,
  395.             "media" => $media
  396.         ]);
  397.     }
  398.     /**
  399.      * --------------
  400.      *
  401.      * Mail helper below!
  402.      *
  403.      *----------------
  404.      */
  405.     /**
  406.      * @param string $technicalName
  407.      * @return MailTemplateEntity|null
  408.      */
  409.     private function getMailTemplate(string $technicalName) {
  410.         $context = new Context(new SystemSource());
  411.         $criteria = new Criteria();
  412.         $criteria->addFilter(new EqualsFilter('mailTemplateType.technicalName'$technicalName));
  413.         $criteria->setLimit(1);
  414.         /** @var MailTemplateEntity|null $mailTemplate */
  415.         $mailTemplate $this->mailTemplateRepository->search($criteria$context)->first();
  416.         return $mailTemplate;
  417.     }
  418.     /**
  419.      * @param string $templateName
  420.      * @param ApplicationEntity $applicationEntity
  421.      * @param JobEntity $jobEntity
  422.      * @param array $recipients
  423.      */
  424.     private function sendEmail(string $salesChannelIdstring $templateNameApplicationEntity $applicationEntityJobEntity $jobEntity, array $recipients) {
  425.         $context Context::createDefaultContext();
  426.         $mailTemplate $this->getMailTemplate($templateName);
  427.         $config $this->systemConfigService->get('CogiRecruiting.config');
  428.         $data = new DataBag();
  429.         $data->set('recipients'$recipients);
  430.         $data->set('senderName'$mailTemplate->getSenderName());
  431.         $data->set('contentHtml'$mailTemplate->getContentHtml());
  432.         $data->set('contentPlain'$mailTemplate->getContentPlain());
  433.         $data->set('salesChannelId'$salesChannelId);
  434.         $data->set('subject'$mailTemplate->getSubject());
  435.         $templateData = [
  436.             'job' => $jobEntity,
  437.             'application' => $applicationEntity,
  438.         ];
  439.         $this->mailService->send($data->all(), $context$templateData);
  440.     }
  441.     private function trackViews(Context $contextSession $session$jobId)
  442.     {
  443.         $criteria = new Criteria();
  444.         $filters = [];
  445.         array_push($filters, new EqualsFilter('jobId'$jobId));
  446.         array_push($filters, new EqualsFilter('date'date("Y-m-d")));
  447.         $criteria->addFilter(new MultiFilter('AND'$filters));
  448.         $result $this->jobEventRepository->search($criteria$context)->getEntities();
  449.         $firstResult $result->first();
  450.         $ids = [];
  451.         if($session->has("CogiJobEventTracked")){
  452.             $ids $session->get("CogiJobEventTracked");
  453.         }
  454.         if(!in_array($jobId$ids)){
  455.             array_push($ids$jobId);
  456.         }
  457.         if (isset($firstResult)) {
  458.             if($session->has("CogiJobEventTracked")){
  459.                 return;
  460.             }
  461.             else{
  462.                 $rechner 1;
  463.                 $this->jobEventRepository->update([[
  464.                     'id' => $firstResult->getId(),
  465.                     'cogi_recruiting_job_id' => $jobId,
  466.                     'type' => 'view',
  467.                     'count' => strval($firstResult->getCount() + $rechner),
  468.                     'date' => $firstResult->getDate(),
  469.                 ]], $context::createDefaultContext());
  470.                 $session->set("CogiJobEventTracked"$ids);
  471.             }
  472.         } else {
  473.             $jobEventId Uuid::randomHex();
  474.             $this->jobEventRepository->create([[
  475.                 'id' => $jobEventId,
  476.                 'jobId' => $jobId,
  477.                 'type' => 'view',
  478.                 'count' => '1',
  479.                 'date' => date("Y-m-d"),
  480.             ]], $context::createDefaultContext());
  481.             $session->set("CogiJobEventTracked"$ids);
  482.         }
  483.     }
  484. }