<?php declare(strict_types=1);
namespace Cogi\CogiRecruiting\Storefront\Controller;
use Cogi\CogiRecruiting\CogiRecruiting;
use Cogi\CogiRecruiting\Core\Content\Application\ApplicationEntity;
use Cogi\CogiRecruiting\Core\Content\Job\JobEntity;
use Cogi\CogiRecruiting\Core\StorefrontMediaUploader;
use Cogi\CogiTicketSystem\CogiTicketSystem;
use Cogi\CogiTicketSystem\Core\Ticket\TicketEntity;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Content\Category\Exception\CategoryNotFoundException;
use Shopware\Core\Content\Mail\Service\AbstractMailService;
use Shopware\Core\Content\Mail\Service\MailService;
use Shopware\Core\Content\MailTemplate\MailTemplateEntity;
use Shopware\Core\Content\Media\Exception\IllegalFileNameException;
use Shopware\Core\Content\Media\Exception\UploadException;
use Shopware\Core\Content\Media\MediaCollection;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Framework\Api\Context\SystemSource;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\Framework\Routing\Exception\MissingRequestParameterException;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Page\GenericPageLoader;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Symfony\Component\Routing\Annotation\Route;
/**
* @RouteScope(scopes={"storefront"})
*/
class RecruitingController extends StorefrontController {
private $genericPageLoader;
/**
* @var EntityRepository
*/
private $categoryRepository;
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var EntityRepository
*/
private $jobRepository;
/**
* @var EntityRepository
*/
private $applicationRepository;
/**
* @var EntityRepository
*/
private $applicationElinkRepository;
/**
* @var EntityRepository
*/
private $applicationCommentRepository;
/**
* @var EntityRepository
*/
private $mailTemplateRepository;
/**
* @var EntityRepositoryInterface
*/
private $mediaRepository;
/**
* @var StorefrontMediaUploader
*/
private $mediaUploader;
/**
* @var AbstractMailService $mailService
*/
private AbstractMailService $mailService;
/**
* HelpcenterOverviewController constructor.
* @param GenericPageLoader $genericPageLoader
* @param SystemConfigService $systemConfigService
* @param EntityRepository $categoryRepository
* @param EntityRepository $jobRepository
* @param EntityRepository $applicationRepository
* @param EntityRepository $applicationElinkRepository
* @param EntityRepository $applicationCommentRepository
* @param EntityRepositoryInterface $mediaRepository
* @param EntityRepository $jobEventRepository
* @param StorefrontMediaUploader $mediaUploader
* @param EntityRepository $mailTemplateRepository
* @param AbstractMailService $mailService
*/
public function __construct(
GenericPageLoader $genericPageLoader,
SystemConfigService $systemConfigService,
EntityRepository $categoryRepository,
EntityRepository $jobRepository,
EntityRepository $applicationRepository,
EntityRepository $applicationElinkRepository,
EntityRepository $applicationCommentRepository,
EntityRepositoryInterface $mediaRepository,
EntityRepository $jobEventRepository,
StorefrontMediaUploader $mediaUploader,
EntityRepository $mailTemplateRepository,
AbstractMailService $mailService
) {
$this->genericPageLoader = $genericPageLoader;
$this->categoryRepository = $categoryRepository;
$this->jobRepository = $jobRepository;
$this->applicationRepository = $applicationRepository;
$this->applicationElinkRepository = $applicationElinkRepository;
$this->applicationCommentRepository = $applicationCommentRepository;
$this->mediaRepository = $mediaRepository;
$this->jobEventRepository = $jobEventRepository;
$this->systemConfigService = $systemConfigService;
$this->mediaUploader = $mediaUploader;
$this->mailTemplateRepository = $mailTemplateRepository;
$this->mailService = $mailService;
}
/**
* @Route("/recruiting/{id}", name="frontend.cogirecruiting.overview", options={"seo"="true"}, methods={"GET"}, defaults={"id"=null})
* @param $id
* @param Request $request
* @param SalesChannelContext $salesChannelContext
* @param Context $context
* @return Response
* @throws CategoryNotFoundException
* @throws InconsistentCriteriaIdsException
* @throws MissingRequestParameterException
*/
public function renderOverview($id, Request $request, SalesChannelContext $salesChannelContext, Context $context) {
$page = $this->genericPageLoader->load($request, $salesChannelContext);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', '1'));
$criteria->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
if ($id !== null) {
$criteria->addFilter(new EqualsFilter('categoryId', $id));
}
$jobs = $this->jobRepository->search($criteria, $context)->getEntities();
return $this->renderStorefront('@Storefront/storefront/page/recruiting/overview.html.twig', [
"page" => $page,
"categories" => $this->getCategories($context),
"jobs" => $jobs,
"jobsCount" => $jobs->count()
]);
}
/**
* @Route("/recruiting/job/{id}", name="frontend.cogirecruiting.job", options={"seo"="true"}, methods={"GET"})
* @param $id
* @param Request $request
* @param SalesChannelContext $salesChannelContext
* @param Context $context
* @return Response
* @throws CategoryNotFoundException
* @throws InconsistentCriteriaIdsException
* @throws MissingRequestParameterException
*/
public function renderJob($id, Request $request, SalesChannelContext $salesChannelContext, Context $context) {
$page = $this->genericPageLoader->load($request, $salesChannelContext);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', '1'), new EqualsFilter('id', $id));
$job = $this->jobRepository->search($criteria, $context);
$this->trackViews($context, $request->getSession(), $job->first()->getId());
if ($job->count() !== 1) {
$this->addFlash('danger', $this->trans('cogi-recruiting.404'));
return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
}
$s = $job->getEntities()->getElements();
/** @var JobEntity $job */
$job = array_shift($s);
$mediaItems = array_filter([$job->get('contactMediaId'), $job->get('previewMediaId')]);
if (count($mediaItems)) {
$media = $this->mediaRepository->search(new Criteria($mediaItems), $context)->getElements();
}
$metaInformation = $page->getMetaInformation();
$metaInformation->setMetaTitle($job->getTranslation('title'));
$metaInformation->setMetaDescription($job->getTranslation('shortDescription'));
$page->setMetaInformation($metaInformation);
return $this->renderStorefront("@Storefront/storefront/page/recruiting/job.html.twig", [
"page" => $page,
"job" => $job,
"media" => $media ?? []
]);
}
/**
* @Route("/recruiting/api/apply", name="frontend.cogirecruiting.api.apply", options={"seo"="false"}, methods={"POST"})
* @param RequestDataBag $data
* @param Request $request
* @param SalesChannelContext $salesChannelContext
* @param Context $context
* @return RedirectResponse
* @throws IllegalFileNameException
* @throws UploadException
*/
public function sendApply(RequestDataBag $data, Request $request, SalesChannelContext $salesChannelContext, Context $context) {
$salesChannelId = $request->get('sw-sales-channel-id');
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', '1'), new EqualsFilter('id', $data->get('id')))->setLimit(1);
$job = $this->jobRepository->search($criteria, $context);
if ($job->count() !== 1) {
$this->addFlash('danger', $this->trans('cogi-recruiting.404'));
return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
}
$s = $job->getEntities()->getElements();
/** @var JobEntity $job */
$job = array_shift($s);
$allowedSize = $this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingUploadMaxSize') * 1e+6;
$allowedTypes = explode(",", $this->systemConfigService->get('CogiRecruiting.config.UploadFileTypes'));
if (!empty($request->files->get('upload'))) {
$uploadItems = $request->files->get('upload');
} else {
$uploadItems = [];
}
$profileImage = $request->files->get('profile-image');
if ($job->get('mediaFolderId') === null) {
$this->addFlash('danger', $this->trans('cogi-recruiting.apply.notSetup') . ' Media Folder');
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
}
if ($this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingProfilePictureRequired') && $request->files->get('profile-image') === null) {
$this->addFlash('danger', $this->trans('cogi-recruiting.apply.noProfile'));
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
}
if (count($uploadItems) > (int)$this->systemConfigService->get('CogiRecruiting.config.UploadFileLimit')) {
$this->addFlash('danger', $this->trans('cogi-recruiting.apply.fileLimit'));
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
}
if ($this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingProfilePictureRequired')) {
// upload profile picture
if (!$this->checkSize($profileImage, $allowedSize))
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
if (!$this->checkExtension($profileImage, array('jpg', 'png')))
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
}
// validate uploads
foreach ($uploadItems as $item) {
// Check allowed file size
if (!$this->checkSize($item, $allowedSize))
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
if (!$this->checkExtension($item, $allowedTypes))
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
}
if ($this->systemConfigService->get('CogiRecruiting.config.CogiRecruitingProfilePictureRequired')) {
// upload profile image
$profileImageId = $this->mediaUploader->upload($profileImage, $job->get('mediaFolderId'), $context::createDefaultContext());
}
// upload uploads
$mediaIds = [];
foreach ($uploadItems as $item)
$mediaIds[] = $this->mediaUploader->upload($item, $job->get('mediaFolderId'), $context::createDefaultContext());
$applicationId = Uuid::randomHex();
// Insert application
$this->applicationRepository->create([[
'id' => $applicationId,
'jobId' => $data->get('id'),
'prename' => $data->get('prename'),
'name' => $data->get('name'),
'email' => $data->get('email'),
'telephone' => $data->get('telephone', null),
'message' => $data->get('message'),
'files' => $mediaIds,
'profilePictureId' => $profileImageId ?? null,
]], $context::createDefaultContext());
// get application entity
$criteria = new Criteria([$applicationId]);
$application = $this->applicationRepository->search($criteria, $context)->get($applicationId);
if($this->systemConfigService->get('CogiRecruiting.config.sendApplicationMailAdmin')){
$email = $this->systemConfigService->get('core.basicInformation.email');
if($job->getContactMail() !== null && $job->getContactMail() !== "")
$email = $job->getContactMail();
// send mail to admin!
$recipient = [
$email => "Admin"
];
$this->sendEmail($salesChannelId, CogiRecruiting::MAIL_TEMPLATE_ADMIN_NEW_APPLICATION, $application, $job, $recipient);
}
if($this->systemConfigService->get('CogiRecruiting.config.sendApplicationMailApplicant')){
// send mail to applicant
$recipient = [
$application->email => "{$application->prename} {$application->name}"
];
$this->sendEmail($salesChannelId, CogiRecruiting::MAIL_TEMPLATE_APPLICATION_CONFIRMATION, $application, $job, $recipient);
}
$this->addFlash('success', $this->trans('cogi-recruiting.apply.success'));
return $this->redirectToRoute('frontend.cogirecruiting.job', ["id" => $data->get('id')]);
}
private function checkSize($item, $allowedSize) {
if ($item->getSize() > $allowedSize) {
$this->addFlash('danger', str_replace('$1', $item->getClientOriginalName(), str_replace('$2', ($allowedSize / 1e+6), $this->trans('cogi-recruiting.apply.sizeLimit'))));
return false;
}
return true;
}
private function checkExtension($item, $allowedTypes) {
if (!in_array(strtolower($item->getClientOriginalExtension()), $allowedTypes)) {
$these = implode(', ', $allowedTypes);
$this->addFlash('danger', str_replace('$1', strtolower($item->getClientOriginalExtension()), str_replace('$2', $these, $this->trans('cogi-recruiting.apply.disallowedExtension'))));
return false;
}
return true;
}
public function getCategories($context) {
$criteria = new Criteria();
$criteria->addFilter(
new EqualsFilter('active', true)
);
return $this->categoryRepository->search($criteria, $context)->getEntities();
}
protected function toBytes($str) {
$str = trim($str);
$last = strtolower($str[strlen($str) - 1]);
$val = 0;
if (is_numeric($last)) {
$val = (int)$str;
} else {
$val = (int)substr($str, 0, -1);
}
switch ($last) {
case 'g':
case 'G':
$val *= 1024;
case 'm':
case 'M':
$val *= 1024;
case 'k':
case 'K':
$val *= 1024;
}
return $val;
}
/**
* @Route("/recruiting/elink/{elinkId}/{applicationId}", name="frontend.cogirecruiting.elink", options={"seo"="false"}, methods={"GET"})
* @param $elinkId
* @param $applicationId
* @param Request $request
* @param SalesChannelContext $salesChannelContext
* @param Context $context
* @return Response
* @throws CategoryNotFoundException
* @throws InconsistentCriteriaIdsException
* @throws MissingRequestParameterException
*/
public function renderElink($elinkId, $applicationId, Request $request, SalesChannelContext $salesChannelContext, Context $context) {
$page = $this->genericPageLoader->load($request, $salesChannelContext);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $elinkId));
$criteria->addFilter(new EqualsFilter('applicationId', $applicationId));
$criteria->setLimit(1);
$elink = $this->applicationElinkRepository->search($criteria, $context);
if ($elink->count() !== 1) {
$this->addFlash('danger', $this->trans('cogi-recruiting.elink.404'));
return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
}
$elink = $elink->getElements();
$elink = array_shift($elink);
if ($elink->get('createdAt')->format('Y-m-d H:i:s') === null) {
$this->addFlash('danger', $this->trans('cogi-recruiting.elink.pleaseReopen'));
return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
}
$expires = strtotime($elink->get('createdAt')->format('Y-m-d H:i:s')) + (3600 * $elink->get('expireHours'));
if (time() > $expires) {
$this->addFlash('danger', $this->trans('cogi-recruiting.elink.expired'));
return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $applicationId));
$criteria->addAssociation('job');
$application = $this->applicationRepository->search($criteria, $context);
if ($application->count() !== 1) {
$this->addFlash('danger', $this->trans('cogi-recruiting.elink.404'));
return $this->redirectToRoute('frontend.cogirecruiting.overview', []);
}
$application = $application->getElements();
$application = array_shift($application);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('applicationId', $applicationId));
$applicationComments = $this->applicationCommentRepository->search($criteria, $context)->getElements();
$mediaItems = $application->files;
if (count($mediaItems)) {
$media = $this->mediaRepository->search(new Criteria($mediaItems), $context)->getElements();
}
return $this->renderStorefront('@Storefront/storefront/page/recruiting/elink.html.twig', [
"page" => $page,
"elink" => $elink,
"application" => $application,
"applicationComments" => $applicationComments,
"expires" => $expires,
"media" => $media
]);
}
/**
* --------------
*
* Mail helper below!
*
*----------------
*/
/**
* @param string $technicalName
* @return MailTemplateEntity|null
*/
private function getMailTemplate(string $technicalName) {
$context = new Context(new SystemSource());
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('mailTemplateType.technicalName', $technicalName));
$criteria->setLimit(1);
/** @var MailTemplateEntity|null $mailTemplate */
$mailTemplate = $this->mailTemplateRepository->search($criteria, $context)->first();
return $mailTemplate;
}
/**
* @param string $templateName
* @param ApplicationEntity $applicationEntity
* @param JobEntity $jobEntity
* @param array $recipients
*/
private function sendEmail(string $salesChannelId, string $templateName, ApplicationEntity $applicationEntity, JobEntity $jobEntity, array $recipients) {
$context = Context::createDefaultContext();
$mailTemplate = $this->getMailTemplate($templateName);
$config = $this->systemConfigService->get('CogiRecruiting.config');
$data = new DataBag();
$data->set('recipients', $recipients);
$data->set('senderName', $mailTemplate->getSenderName());
$data->set('contentHtml', $mailTemplate->getContentHtml());
$data->set('contentPlain', $mailTemplate->getContentPlain());
$data->set('salesChannelId', $salesChannelId);
$data->set('subject', $mailTemplate->getSubject());
$templateData = [
'job' => $jobEntity,
'application' => $applicationEntity,
];
$this->mailService->send($data->all(), $context, $templateData);
}
private function trackViews(Context $context, Session $session, $jobId)
{
$criteria = new Criteria();
$filters = [];
array_push($filters, new EqualsFilter('jobId', $jobId));
array_push($filters, new EqualsFilter('date', date("Y-m-d")));
$criteria->addFilter(new MultiFilter('AND', $filters));
$result = $this->jobEventRepository->search($criteria, $context)->getEntities();
$firstResult = $result->first();
$ids = [];
if($session->has("CogiJobEventTracked")){
$ids = $session->get("CogiJobEventTracked");
}
if(!in_array($jobId, $ids)){
array_push($ids, $jobId);
}
if (isset($firstResult)) {
if($session->has("CogiJobEventTracked")){
return;
}
else{
$rechner = 1;
$this->jobEventRepository->update([[
'id' => $firstResult->getId(),
'cogi_recruiting_job_id' => $jobId,
'type' => 'view',
'count' => strval($firstResult->getCount() + $rechner),
'date' => $firstResult->getDate(),
]], $context::createDefaultContext());
$session->set("CogiJobEventTracked", $ids);
}
} else {
$jobEventId = Uuid::randomHex();
$this->jobEventRepository->create([[
'id' => $jobEventId,
'jobId' => $jobId,
'type' => 'view',
'count' => '1',
'date' => date("Y-m-d"),
]], $context::createDefaultContext());
$session->set("CogiJobEventTracked", $ids);
}
}
}