<?php
declare(strict_types=1);
namespace App\Controller;
use App\Api\GoogleCaptchaApi;
use App\Assert\BadRequestAssert;
use App\Entity\Job;
use App\Entity\JobApplicationInterface;
use App\Enum\LocationEnum;
use App\Factory\JobApplicationFactory;
use App\Form\Type\JobApplicationFormType;
use App\Generator\TokenGeneratorInterface;
use App\Repository\JobRepositoryInterface;
use App\Service\EmailSender;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\String\Slugger\AsciiSlugger;
use Twig\Environment;
class CareerController extends AbstractController
{
private EntityManagerInterface $entityManager;
private JobApplicationFactory $jobApplicationFactory;
private JobRepositoryInterface $jobRepository;
private TokenGeneratorInterface $tokenGenerator;
private Environment $twig;
private EmailSender $emailSender;
private GoogleCaptchaApi $googleCaptchaApi;
private TranslatorInterface $translator;
public function __construct(
EntityManagerInterface $entityManager,
JobApplicationFactory $jobApplicationFactory,
JobRepositoryInterface $jobRepository,
TokenGeneratorInterface $tokenGenerator,
Environment $twig,
EmailSender $emailSender,
GoogleCaptchaApi $googleCaptchaApi,
TranslatorInterface $translator
) {
$this->entityManager = $entityManager;
$this->jobApplicationFactory = $jobApplicationFactory;
$this->jobRepository = $jobRepository;
$this->tokenGenerator = $tokenGenerator;
$this->twig = $twig;
$this->emailSender = $emailSender;
$this->googleCaptchaApi = $googleCaptchaApi;
$this->translator = $translator;
}
/**
* @Route("/career", name="career_index")
*/
public function index(): Response
{
$jobs = $this->jobRepository->findActive();
return $this->render('page/career/index/index.html.twig', [
'jobs' => $jobs,
'filters' => LocationEnum::toChoices(),
]);
}
/**
* @Route("/career/{job}", name="career_show")
*/
public function show(Request $request, Job $job): Response
{
$jobApplication = $this->jobApplicationFactory->createForJob($job);
$form = $this->createForm(JobApplicationFormType::class, $jobApplication);
$form->handleRequest($request);
if ($form->isSubmitted()) {
$captchaResponse = $this->googleCaptchaApi->getResponse(
$request->request->get('g-recaptcha-response') ?? "",
$request->getClientIp()
);
// Capture the time the form was initially rendered
$formRenderedAt = $request->request->get('form_rendered_at');
// Calculate the time difference in seconds between when the form was displayed and when it was submitted
$interactionTime = time() - intval($formRenderedAt);
// Capture the value from the honeypot field
$honeypotFieldValue = $request->request->get('email_repeat');
if (!$this->googleCaptchaApi->isValid($captchaResponse, $interactionTime, $honeypotFieldValue)) {
$form->addError(new FormError('Invalid captcha!'));
} elseif ($form->isValid()) {
/** @var JobApplicationInterface $jobApplication */
$jobApplication = $form->getData();
/** @var UploadedFile $uploadedFile */
$uploadedFile = $form->get('cvFile')->getData();
BadRequestAssert::same($uploadedFile->getError(), UPLOAD_ERR_OK);
$path = sprintf(
'%s/public/uploads/%s/',
$this->getParameter('kernel.project_dir'),
$this->getParameter('app.directory.cvs')
);
if (!is_dir($path) && !mkdir($path, 0775, true) && !is_dir($path)) {
throw new RuntimeException(sprintf('Directory "%s" was not created', $path));
}
$fileName = sprintf(
'%s.%s',
$this->tokenGenerator->generate(),
$uploadedFile->getExtension() ?: $uploadedFile->getClientOriginalExtension()
);
$slugger = new AsciiSlugger();
$slug = $slugger
->slug(strtolower($jobApplication->getName()))
->toString();
$slug = substr($slug, 0, max(255 - strlen($fileName) - 1, 0));
if (!empty($slug)) {
$fileName = sprintf('%s-%s', $slug, $fileName);
}
$uploadedFile->move($path, $fileName);
$jobApplication->setCvFilePath($fileName);
$jobApplication->setCreatedAt(Carbon::now());
$this->entityManager->persist($jobApplication);
$this->entityManager->flush();
$this->sendEmail(
$jobApplication,
sprintf('%s/%s', $path, $fileName),
$request->getUri(),
$captchaResponse
);
$translatedMessage = $this->translator->trans('careers.upload_success', [], 'messages');
$this->addFlash('success', "$translatedMessage");
return $this->renderForm('page/career/show/show.html.twig', [
'job' => $job,
'form' => $form,
]);
}
}
return $this->renderForm('page/career/show/show.html.twig', [
'job' => $job,
'form' => $form,
]);
}
private function sendEmail(
JobApplicationInterface $jobApplication,
string $fullPath,
string $url,
array $captchaResponse
): void {
$job = $jobApplication->getJob();
$recipient = $job->getLocation()->getMessageEmail();
if (empty($recipient)) {
return;
}
$content = $this->twig->render(
'email/job_application.html.twig',
['jobApplication' => $jobApplication]
);
$email = (new Email())
->from(new Address('info@icftechnology.com', 'ICFTechnology.com')) // TODO: .env
->to($recipient)
->replyTo(new Address($jobApplication->getEmail(), $jobApplication->getName()))
->subject('Job application contact form')
->attachFromPath($fullPath)
->html($content);
$this->emailSender->send($email, $url, $captchaResponse);
}
}